InstantSend overhaul (#1288)

* Multi-quorum InstantSend, complete refactoring
+ cleanup for IS and partial protobump

* more changes:
- allow InstantSend tx to have 10 inputs max
- store many unique tx hashes in mapVotedOutpoints
- more checks in AcceptToMemoryPoolWorker (moved from ProcessMessage + CTxLockRequest(tx).IsValid() )

* More changes:
- let multiple lock candidates compete for votes
- fail to vote on the same outpoint twice early

* More changes:
- notify CInstantSend on UpdatedBlockTip -> remove cs_main from CheckAndRemove()
- notify CInstantSend on SyncTransaction -> count expiration block starting from the block corresponding tx was confirmed instead of the block lock candidate/vote was created
- fixed few locks

* add comments about nConfirmedHeight

* Fix "Block vs Lock" edge case

* Fix "Block vs Lock" edge case, p2

* Fix issues:
- fix logic for locking inputs and notifying - see UpdateLockedTransaction, TryToFinalizeLockCandidate
- add missing hash inserting in ProcessTxLockVote
- add nMaxBlocks param to ResolveConflicts to limit max depth allowed to disconnect blocks recursively
- fix false positive mempool conflict
- add missing mutex locks
- fix fRequireUnspent logic in CTxLockRequest::IsValid
This commit is contained in:
UdjinM6 2017-01-29 12:22:14 +04:00 committed by GitHub
parent 9c5db04953
commit 60409df822
16 changed files with 1190 additions and 535 deletions

View File

@ -77,7 +77,6 @@ public:
consensus.nMasternodePaymentsIncreaseBlock = 158000; // actual historical value
consensus.nMasternodePaymentsIncreasePeriod = 576*30; // 17280 - actual historical value
consensus.nInstantSendKeepLock = 24;
consensus.nInstantSendReprocessBlocks = 15;
consensus.nBudgetPaymentsStartBlock = 328008; // actual historical value
consensus.nBudgetPaymentsCycleBlocks = 16616; // ~(60*24*30)/2.6, actual number of blocks per month is 200700 / 12 = 16725
consensus.nBudgetPaymentsWindowBlocks = 100;
@ -201,7 +200,6 @@ public:
consensus.nMasternodePaymentsIncreaseBlock = 46000;
consensus.nMasternodePaymentsIncreasePeriod = 576;
consensus.nInstantSendKeepLock = 6;
consensus.nInstantSendReprocessBlocks = 4;
consensus.nBudgetPaymentsStartBlock = 60000;
consensus.nBudgetPaymentsCycleBlocks = 50;
consensus.nBudgetPaymentsWindowBlocks = 10;
@ -308,7 +306,6 @@ public:
consensus.nMasternodePaymentsIncreaseBlock = 350;
consensus.nMasternodePaymentsIncreasePeriod = 10;
consensus.nInstantSendKeepLock = 6;
consensus.nInstantSendReprocessBlocks = 4;
consensus.nBudgetPaymentsStartBlock = 1000;
consensus.nBudgetPaymentsCycleBlocks = 50;
consensus.nBudgetPaymentsWindowBlocks = 10;

View File

@ -41,7 +41,6 @@ struct Params {
int nMasternodePaymentsIncreaseBlock;
int nMasternodePaymentsIncreasePeriod; // in blocks
int nInstantSendKeepLock; // in blocks
int nInstantSendReprocessBlocks;
int nBudgetPaymentsStartBlock;
int nBudgetPaymentsCycleBlocks;
int nBudgetPaymentsWindowBlocks;

View File

@ -2490,7 +2490,7 @@ void ThreadCheckDarkSendPool()
mnodeman.ProcessMasternodeConnections();
mnodeman.CheckAndRemove();
mnpayments.CheckAndRemove();
CleanTxLockCandidates();
instantsend.CheckAndRemove();
}
darkSendPool.CheckTimeout();

View File

@ -4,6 +4,7 @@
#include "dsnotificationinterface.h"
#include "darksend.h"
#include "instantx.h"
#include "governance.h"
#include "masternodeman.h"
#include "masternode-payments.h"
@ -21,7 +22,13 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindex)
{
mnodeman.UpdatedBlockTip(pindex);
darkSendPool.UpdatedBlockTip(pindex);
instantsend.UpdatedBlockTip(pindex);
mnpayments.UpdatedBlockTip(pindex);
governance.UpdatedBlockTip(pindex);
masternodeSync.UpdatedBlockTip(pindex);
}
void CDSNotificationInterface::SyncTransaction(const CTransaction &tx, const CBlock *pblock)
{
instantsend.SyncTransaction(tx, pblock);
}

View File

@ -17,6 +17,7 @@ public:
protected:
// CValidationInterface
void UpdatedBlockTip(const CBlockIndex *pindex);
void SyncTransaction(const CTransaction &tx, const CBlock *pblock);
private:
};

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,13 @@
#include "net.h"
#include "primitives/transaction.h"
class CTransaction;
class CTxLockVote;
class COutPointLock;
class CTxLockRequest;
class CTxLockCandidate;
class CInstantSend;
extern CInstantSend instantsend;
/*
At 15 signatures, 1/2 of the masternode network can be owned by
@ -20,106 +24,225 @@ class CTxLockCandidate;
### getting 5 of 10 signatures w/ 1000 nodes of 2900
(1000/2900.0)**5 = 0.004875397277841433
*/
static const int INSTANTSEND_SIGNATURES_REQUIRED = 6;
static const int INSTANTSEND_SIGNATURES_TOTAL = 10;
static const int INSTANTSEND_CONFIRMATIONS_REQUIRED = 6;
static const int DEFAULT_INSTANTSEND_DEPTH = 5;
static const int MIN_INSTANTSEND_PROTO_VERSION = 70204;
static const CAmount INSTANTSEND_MIN_FEE = 0.001 * COIN;
static const int MIN_INSTANTSEND_PROTO_VERSION = 70205;
extern bool fEnableInstantSend;
extern int nInstantSendDepth;
extern int nCompleteTXLocks;
extern std::map<uint256, CTransaction> mapLockRequestAccepted;
extern std::map<uint256, CTransaction> mapLockRequestRejected;
extern std::map<uint256, CTxLockVote> mapTxLockVotes;
extern std::map<COutPoint, uint256> mapLockedInputs;
class CInstantSend
{
private:
static const int ORPHAN_VOTE_SECONDS = 60;
// Keep track of current block index
const CBlockIndex *pCurrentBlockIndex;
void ProcessMessageInstantSend(CNode* pfrom, std::string& strCommand, CDataStream& vRecv);
// maps for AlreadyHave
std::map<uint256, CTxLockRequest> mapLockRequestAccepted; // tx hash - tx
std::map<uint256, CTxLockRequest> mapLockRequestRejected; // tx hash - tx
std::map<uint256, CTxLockVote> mapTxLockVotes; // vote hash - vote
std::map<uint256, CTxLockVote> mapTxLockVotesOrphan; // vote hash - vote
bool IsInstantSendTxValid(const CTransaction& txCandidate);
std::map<uint256, CTxLockCandidate> mapTxLockCandidates; // tx hash - lock candidate
bool ProcessTxLockRequest(CNode* pfrom, const CTransaction &tx);
std::map<COutPoint, std::set<uint256> > mapVotedOutpoints; // utxo - tx hash set
std::map<COutPoint, uint256> mapLockedOutpoints; // utxo - tx hash
int64_t CreateTxLockCandidate(const CTransaction &tx);
//track masternodes who voted with no txreq (for DOS protection)
std::map<COutPoint, int64_t> mapMasternodeOrphanVotes; // mn outpoint - time
//check if we need to vote on this transaction
void CreateTxLockVote(const CTransaction& tx, int nBlockHeight);
bool CreateTxLockCandidate(const CTxLockRequest& txLockRequest);
void Vote(CTxLockCandidate& txLockCandidate);
//process consensus vote message
bool ProcessTxLockVote(CNode *pnode, CTxLockVote& vote);
//process consensus vote message
bool ProcessTxLockVote(CNode* pfrom, CTxLockVote& vote);
void ProcessOrphanTxLockVotes();
bool IsEnoughOrphanVotesForTx(const CTxLockRequest& txLockRequest);
bool IsEnoughOrphanVotesForTxAndOutPoint(const uint256& txHash, const COutPoint& outpoint);
int64_t GetAverageMasternodeOrphanVoteTime();
void ProcessOrphanTxLockVotes();
void TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate);
void LockTransactionInputs(const CTxLockCandidate& txLockCandidate);
//update UI and notify external script if any
void UpdateLockedTransaction(const CTxLockCandidate& txLockCandidate);
bool ResolveConflicts(const CTxLockCandidate& txLockCandidate, int nMaxBlocks);
//update UI and notify external script if any
void UpdateLockedTransaction(const CTransaction& tx, bool fForceNotification = false);
bool IsInstantSendReadyToLock(const uint256 &txHash);
void LockTransactionInputs(const CTransaction& tx);
public:
CCriticalSection cs_instantsend;
// if two conflicting locks are approved by the network, they will cancel out
bool FindConflictingLocks(const CTransaction& tx);
void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv);
//try to resolve conflicting locks
void ResolveConflicts(const CTransaction& tx);
bool ProcessTxLockRequest(const CTxLockRequest& txLockRequest);
// keep transaction locks in memory for an hour
void CleanTxLockCandidates();
bool AlreadyHave(const uint256& hash);
// verify if transaction is currently locked
bool IsLockedInstandSendTransaction(const uint256 &txHash);
void AcceptLockRequest(const CTxLockRequest& txLockRequest);
void RejectLockRequest(const CTxLockRequest& txLockRequest);
bool HasTxLockRequest(const uint256& txHash);
bool GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLockRequestRet);
// get the actual uber og accepted lock signatures
int GetTransactionLockSignatures(const uint256 &txHash);
bool GetTxLockVote(const uint256& hash, CTxLockVote& txLockVoteRet);
// verify if transaction lock timed out
bool IsTransactionLockTimedOut(const uint256 &txHash);
bool GetLockedOutPointTxHash(const COutPoint& outpoint, uint256& hashRet);
int64_t GetAverageMasternodeOrphanVoteTime();
// verify if transaction is currently locked
bool IsLockedInstantSendTransaction(const uint256& txHash);
// get the actual uber og accepted lock signatures
int GetTransactionLockSignatures(const uint256& txHash);
// remove expired entries from maps
void CheckAndRemove();
// verify if transaction lock timed out
bool IsTxLockRequestTimedOut(const uint256& txHash);
void Relay(const uint256& txHash);
void UpdatedBlockTip(const CBlockIndex *pindex);
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
};
class CTxLockRequest : public CTransaction
{
private:
static const int TIMEOUT_SECONDS = 5 * 60;
static const CAmount MIN_FEE = 0.001 * COIN;
int64_t nTimeCreated;
public:
static const int MAX_INPUTS = 10;
CTxLockRequest() :
CTransaction(),
nTimeCreated(GetTime())
{}
CTxLockRequest(const CTransaction& tx) :
CTransaction(tx),
nTimeCreated(GetTime())
{}
bool IsValid(bool fRequireUnspent = true) const;
CAmount GetMinFee() const;
int GetMaxSignatures() const;
bool IsTimedOut() const;
};
class CTxLockVote
{
public:
CTxIn vinMasternode;
private:
uint256 txHash;
int nBlockHeight;
std::vector<unsigned char> vchMasterNodeSignature;
COutPoint outpoint;
COutPoint outpointMasternode;
std::vector<unsigned char> vchMasternodeSignature;
// local memory only
int64_t nOrphanExpireTime;
int nConfirmedHeight; // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1
int64_t nTimeCreated;
public:
CTxLockVote() :
txHash(),
outpoint(),
outpointMasternode(),
vchMasternodeSignature(),
nConfirmedHeight(-1),
nTimeCreated(GetTime())
{}
CTxLockVote(const uint256& txHashIn, const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn) :
txHash(txHashIn),
outpoint(outpointIn),
outpointMasternode(outpointMasternodeIn),
vchMasternodeSignature(),
nConfirmedHeight(-1),
nTimeCreated(GetTime())
{}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(txHash);
READWRITE(vinMasternode);
READWRITE(vchMasterNodeSignature);
READWRITE(nBlockHeight);
READWRITE(outpoint);
READWRITE(outpointMasternode);
READWRITE(vchMasternodeSignature);
}
uint256 GetHash() const;
uint256 GetTxHash() const { return txHash; }
COutPoint GetOutpoint() const { return outpoint; }
COutPoint GetMasternodeOutpoint() const { return outpointMasternode; }
int64_t GetTimeCreated() const { return nTimeCreated; }
bool IsValid(CNode* pnode) const;
void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; }
bool IsExpired(int nHeight) const;
bool Sign();
bool CheckSignature() const;
void Relay() const;
};
class COutPointLock
{
private:
COutPoint outpoint; // utxo
std::map<COutPoint, CTxLockVote> mapMasternodeVotes; // masternode outpoint - vote
public:
static const int SIGNATURES_REQUIRED = 6;
static const int SIGNATURES_TOTAL = 10;
COutPointLock(const COutPoint& outpointIn) :
outpoint(outpointIn),
mapMasternodeVotes()
{}
COutPoint GetOutpoint() const { return outpoint; }
bool AddVote(const CTxLockVote& vote);
bool HasMasternodeVoted(const COutPoint& outpointMasternodeIn);
int CountVotes() const { return mapMasternodeVotes.size(); }
bool IsReady() const { return CountVotes() >= SIGNATURES_REQUIRED; }
void Relay() const;
};
class CTxLockCandidate
{
private:
int nConfirmedHeight; // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1
public:
int nBlockHeight;
uint256 txHash;
std::vector<CTxLockVote> vecTxLockVotes;
int nExpirationBlock;
int nTimeout;
CTxLockCandidate(const CTxLockRequest& txLockRequestIn) :
nConfirmedHeight(-1),
txLockRequest(txLockRequestIn),
mapOutPointLocks()
{}
uint256 GetHash() const { return txHash; }
CTxLockRequest txLockRequest;
std::map<COutPoint, COutPointLock> mapOutPointLocks;
bool IsAllVotesValid();
void AddVote(const CTxLockVote& vote);
int CountVotes();
uint256 GetHash() const { return txLockRequest.GetHash(); }
void AddOutPointLock(const COutPoint& outpoint);
bool AddVote(const CTxLockVote& vote);
bool IsAllOutPointsReady() const;
bool HasMasternodeVoted(const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn);
int CountVotes() const;
void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; }
bool IsExpired(int nHeight) const;
void Relay() const;
};
#endif

View File

@ -941,6 +941,18 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
return nSigOps;
}
int GetUTXOHeight(const COutPoint& outpoint)
{
LOCK(cs_main);
CCoins coins;
if(!pcoinsTip->GetCoins(outpoint.hash, coins) ||
(unsigned int)outpoint.n>=coins.vout.size() ||
coins.vout[outpoint.n].IsNull()) {
return -1;
}
return coins.nHeight;
}
int GetInputAge(const CTxIn &txin)
{
CCoinsView viewDummy;
@ -966,7 +978,7 @@ int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin)
int nResult = GetInputAge(txin);
if(nResult < 0) return -1;
if (nResult < 6 && IsLockedInstandSendTransaction(nTXHash))
if (nResult < 6 && instantsend.IsLockedInstantSendTransaction(nTXHash))
return nInstantSendDepth + nResult;
return nResult;
@ -974,7 +986,7 @@ int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin)
int GetIXConfirmations(const uint256 &nTXHash)
{
if (IsLockedInstandSendTransaction(nTXHash))
if (instantsend.IsLockedInstantSendTransaction(nTXHash))
return nInstantSendDepth;
return 0;
@ -1088,14 +1100,19 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
if (pool.exists(hash))
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
// ----------- InstantSend transaction scanning -----------
// If this is a Transaction Lock Request check to see if it's valid
if(instantsend.HasTxLockRequest(hash) && !CTxLockRequest(tx).IsValid())
return state.DoS(10, error("AcceptToMemoryPool : CTxLockRequest %s is invalid", hash.ToString()),
REJECT_INVALID, "bad-txlockrequest");
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
if(mapLockedInputs.count(txin.prevout) && mapLockedInputs[txin.prevout] != tx.GetHash()) {
return state.DoS(0,
error("AcceptToMemoryPool : conflicts with existing transaction lock: %s", reason),
REJECT_INVALID, "tx-lock-conflict");
}
// Check for conflicts with a completed Transaction Lock
BOOST_FOREACH(const CTxIn &txin, tx.vin)
{
uint256 hashLocked;
if(instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hash != hashLocked)
return state.DoS(10, error("AcceptToMemoryPool : Transaction %s conflicts with completed Transaction Lock %s",
hash.ToString(), hashLocked.ToString()),
REJECT_INVALID, "tx-txlock-conflict");
}
// Check for conflicts with in-memory transactions
@ -1109,6 +1126,18 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
const CTransaction *ptxConflicting = pool.mapNextTx[txin.prevout].ptx;
if (!setConflicts.count(ptxConflicting->GetHash()))
{
// InstantSend txes are not replacable
if(instantsend.HasTxLockRequest(ptxConflicting->GetHash())) {
// this tx conflicts with a Transaction Lock Request candidate
return state.DoS(0, error("AcceptToMemoryPool : Transaction %s conflicts with Transaction Lock Request %s",
hash.ToString(), ptxConflicting->GetHash().ToString()),
REJECT_INVALID, "tx-txlockreq-mempool-conflict");
} else if (instantsend.HasTxLockRequest(hash)) {
// this tx is a tx lock request and it conflicts with a normal tx
return state.DoS(0, error("AcceptToMemoryPool : Transaction Lock Request %s conflicts with transaction %s",
hash.ToString(), ptxConflicting->GetHash().ToString()),
REJECT_INVALID, "txlockreq-tx-mempool-conflict");
}
// Allow opt-out of transaction replacement by setting
// nSequence >= maxint-1 on all inputs.
//
@ -3701,24 +3730,33 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
REJECT_INVALID, "bad-cb-multiple");
// DASH : CHECK TRANSACTIONS FOR INSTANT SEND
// DASH : CHECK TRANSACTIONS FOR INSTANTSEND
if(sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) {
// We should never accept block which conflicts with completed transaction lock,
// that's why this is in CheckBlock unlike coinbase payee/amount.
// Require other nodes to comply, send them some data in case they are missing it.
BOOST_FOREACH(const CTransaction& tx, block.vtx) {
// skip coinbase, it has no inputs
if (tx.IsCoinBase()) continue;
// LOOK FOR TRANSACTION LOCK IN OUR MAP OF INPUTS
// LOOK FOR TRANSACTION LOCK IN OUR MAP OF OUTPOINTS
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
if(mapLockedInputs.count(txin.prevout) && mapLockedInputs[txin.prevout] != tx.GetHash()) {
uint256 hashLocked;
if(instantsend.GetLockedOutPointTxHash(txin.prevout, hashLocked) && hashLocked != tx.GetHash()) {
// Every node which relayed this block to us must invalidate it
// but they probably need more data.
// Relay corresponding transaction lock request and all its votes
// to let other nodes complete the lock.
instantsend.Relay(hashLocked);
mapRejectedBlocks.insert(make_pair(block.GetHash(), GetTime()));
LogPrintf("CheckBlock(DASH): found conflicting transaction with transaction lock %s %s\n", mapLockedInputs[txin.prevout].ToString(), tx.GetHash().ToString());
return state.DoS(0, error("CheckBlock(DASH): found conflicting transaction with transaction lock"),
REJECT_INVALID, "conflicting-tx-ix");
return state.DoS(0, error("CheckBlock(DASH): transaction %s conflicts with transaction lock %s",
tx.GetHash().ToString(), hashLocked.ToString()),
REJECT_INVALID, "conflict-tx-lock");
}
}
}
} else {
LogPrintf("CheckBlock(DASH): skipping transaction locking checks\n");
LogPrintf("CheckBlock(DASH): spork is off, skipping transaction locking checks\n");
}
// END DASH
@ -4901,10 +4939,10 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
We want to only update the time on new hits, so that we can time out appropriately if needed.
*/
case MSG_TXLOCK_REQUEST:
return mapLockRequestAccepted.count(inv.hash) || mapLockRequestRejected.count(inv.hash);
return instantsend.AlreadyHave(inv.hash);
case MSG_TXLOCK_VOTE:
return mapTxLockVotes.count(inv.hash);
return instantsend.AlreadyHave(inv.hash);
case MSG_SPORK:
return mapSporks.count(inv.hash);
@ -5063,20 +5101,22 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam
}
if (!pushed && inv.type == MSG_TXLOCK_REQUEST) {
if(mapLockRequestAccepted.count(inv.hash)) {
CTxLockRequest txLockRequest;
if(instantsend.GetTxLockRequest(inv.hash, txLockRequest)) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(1000);
ss << mapLockRequestAccepted[inv.hash];
ss << txLockRequest;
pfrom->PushMessage(NetMsgType::TXLOCKREQUEST, ss);
pushed = true;
}
}
if (!pushed && inv.type == MSG_TXLOCK_VOTE) {
if(mapTxLockVotes.count(inv.hash)) {
CTxLockVote vote;
if(instantsend.GetTxLockVote(inv.hash, vote)) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(1000);
ss << mapTxLockVotes[inv.hash];
ss << vote;
pfrom->PushMessage(NetMsgType::TXLOCKVOTE, ss);
pushed = true;
}
@ -5687,13 +5727,16 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
vector<uint256> vWorkQueue;
vector<uint256> vEraseQueue;
CTransaction tx;
CTxLockRequest txLockRequest;
CDarksendBroadcastTx dstx;
int nInvType = MSG_TX;
// Read data and assign inv type
if(strCommand == NetMsgType::TX) {
vRecv >> tx;
} else if(strCommand == NetMsgType::TXLOCKREQUEST) {
vRecv >> tx;
vRecv >> txLockRequest;
tx = txLockRequest;
nInvType = MSG_TXLOCK_REQUEST;
} else if (strCommand == NetMsgType::DSTX) {
vRecv >> dstx;
@ -5705,7 +5748,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
pfrom->AddInventoryKnown(inv);
pfrom->setAskFor.erase(inv.hash);
if (strCommand == NetMsgType::DSTX) {
// Process custom logic, no matter if tx will be accepted to mempool later or not
if (strCommand == NetMsgType::TXLOCKREQUEST) {
if(!instantsend.ProcessTxLockRequest(txLockRequest)) {
LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", txLockRequest.GetHash().ToString());
return false;
}
} else if (strCommand == NetMsgType::DSTX) {
uint256 hashTx = tx.GetHash();
if(mapDarksendBroadcastTxes.count(hashTx)) {
@ -5745,11 +5794,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs))
{
// Process custom txes
// Process custom txes, this changes AlreadyHave to "true"
if (strCommand == NetMsgType::DSTX) {
LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
tx.GetHash().ToString(), pfrom->id);
mapDarksendBroadcastTxes.insert(make_pair(tx.GetHash(), dstx));
} else if (strCommand == NetMsgType::TXLOCKREQUEST) {
if(!ProcessTxLockRequest(pfrom, tx)) return false;
LogPrintf("TXLOCKREQUEST -- Transaction Lock Request accepted, txid=%s, peer=%d\n",
tx.GetHash().ToString(), pfrom->id);
instantsend.AcceptLockRequest(txLockRequest);
}
mempool.check(pcoinsTip);
@ -5828,18 +5881,17 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
assert(recentRejects);
recentRejects->insert(tx.GetHash());
if (strCommand == NetMsgType::TXLOCKREQUEST && !AlreadyHave(inv)) { // i.e. AcceptToMemoryPool failed
mapLockRequestRejected.insert(std::make_pair(tx.GetHash(), tx));
if (strCommand == NetMsgType::TXLOCKREQUEST && !AlreadyHave(inv)) {
// i.e. AcceptToMemoryPool failed, probably because it's conflicting
// with existing normal tx or tx lock for another tx. For the same tx lock
// AlreadyHave would have return "true" already.
// can we get the conflicting transaction as proof?
LogPrintf("TXLOCKREQUEST -- Transaction Lock Request: %s %s : rejected %s\n",
pfrom->addr.ToString(), pfrom->cleanSubVer,
tx.GetHash().ToString()
);
LockTransactionInputs(tx);
ResolveConflicts(tx);
// It's the first time we failed for this tx lock request,
// this should switch AlreadyHave to "true".
instantsend.RejectLockRequest(txLockRequest);
// this lets other nodes to create lock request candidate i.e.
// this allows multiple conflicting lock requests to compete for votes
RelayTransaction(tx);
}
if (pfrom->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
@ -6259,7 +6311,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
darkSendPool.ProcessMessage(pfrom, strCommand, vRecv);
mnodeman.ProcessMessage(pfrom, strCommand, vRecv);
mnpayments.ProcessMessage(pfrom, strCommand, vRecv);
ProcessMessageInstantSend(pfrom, strCommand, vRecv);
instantsend.ProcessMessage(pfrom, strCommand, vRecv);
sporkManager.ProcessSpork(pfrom, strCommand, vRecv);
masternodeSync.ProcessMessage(pfrom, strCommand, vRecv);
governance.ProcessMessage(pfrom, strCommand, vRecv);

View File

@ -291,6 +291,7 @@ void PruneAndFlush();
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fOverrideMempoolLimit=false, bool fRejectAbsurdFee=false, bool fDryRun=false);
int GetUTXOHeight(const COutPoint& outpoint);
int GetInputAge(const CTxIn &txin);
int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin);
int GetIXConfirmations(const uint256 &nTXHash);

View File

@ -2111,10 +2111,11 @@ void RelayTransaction(const CTransaction& tx)
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(10000);
uint256 hash = tx.GetHash();
CTxLockRequest txLockRequest;
if(mapDarksendBroadcastTxes.count(hash)) { // MSG_DSTX
ss << mapDarksendBroadcastTxes[hash];
} else if(mapLockRequestAccepted.count(hash)) { // MSG_TXLOCK_REQUEST
ss << mapLockRequestAccepted[hash];
} else if(instantsend.GetTxLockRequest(hash, txLockRequest)) { // MSG_TXLOCK_REQUEST
ss << txLockRequest;
} else { // MSG_TX
ss << tx;
}
@ -2125,7 +2126,7 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
{
uint256 hash = tx.GetHash();
int nInv = mapDarksendBroadcastTxes.count(hash) ? MSG_DSTX :
(mapLockRequestAccepted.count(hash) ? MSG_TXLOCK_REQUEST : MSG_TX);
(instantsend.HasTxLockRequest(hash) ? MSG_TXLOCK_REQUEST : MSG_TX);
CInv inv(nInv, hash);
{
LOCK(cs_mapRelay);

View File

@ -585,6 +585,10 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nBytesInputs += 148; // in all error cases, simply assume 148 here
}
else nBytesInputs += 148;
// Add inputs to calculate InstantSend Fee later
if(coinControl->fUseInstantSend)
txDummy.vin.push_back(CTxIn());
}
// calculation
@ -609,7 +613,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nPayFee = coinControl->nMinimumTotalFee;
// InstantSend Fee
if (coinControl->fUseInstantSend) nPayFee = std::max(nPayFee, INSTANTSEND_MIN_FEE);
if (coinControl->fUseInstantSend) nPayFee = std::max(nPayFee, CTxLockRequest(txDummy).GetMinFee());
// Allow free? (require at least hard-coded threshold and default to that if no estimate)
double dPriorityNeeded = std::max(mempoolEstimatePriority, AllowFreeThreshold());

View File

@ -41,7 +41,6 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
QString strTxStatus;
bool fOffline = (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60) && (wtx.GetRequestCount() == 0);
int nSignatures = GetTransactionLockSignatures(wtx.GetHash());
if (fOffline) {
strTxStatus = tr("%1/offline").arg(nDepth);
@ -51,14 +50,16 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
strTxStatus = tr("%1 confirmations").arg(nDepth);
}
if(nSignatures < 0) return strTxStatus; // regular tx
if(!instantsend.HasTxLockRequest(wtx.GetHash())) return strTxStatus; // regular tx
int nSignatures = instantsend.GetTransactionLockSignatures(wtx.GetHash());
int nSignaturesMax = CTxLockRequest(wtx).GetMaxSignatures();
// InstantSend
strTxStatus += " (";
if(nSignatures >= INSTANTSEND_SIGNATURES_REQUIRED) {
if(instantsend.IsLockedInstantSendTransaction(wtx.GetHash())) {
strTxStatus += tr("verified via InstantSend");
} else if(!IsTransactionLockTimedOut(wtx.GetHash())) {
strTxStatus += tr("InstantSend verification in progress - %1 of %2 signatures").arg(nSignatures).arg(INSTANTSEND_SIGNATURES_TOTAL);
} else if(!instantsend.IsTxLockRequestTimedOut(wtx.GetHash())) {
strTxStatus += tr("InstantSend verification in progress - %1 of %2 signatures").arg(nSignatures).arg(nSignaturesMax);
} else {
strTxStatus += tr("InstantSend verification failed");
}

View File

@ -307,11 +307,18 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
if (fSubtractFeeFromAmount && fCreated)
transaction.reassignAmounts(nChangePosRet);
if(recipients[0].fUseInstantSend && newTx->GetValueOut() > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN){
if(recipients[0].fUseInstantSend) {
if(newTx->GetValueOut() > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) {
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH.").arg(sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)),
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
if(newTx->vin.size() > CTxLockRequest::MAX_INPUTS) {
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support transactions with more than %1 inputs.").arg(CTxLockRequest::MAX_INPUTS),
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
}
if(!fCreated)
{

View File

@ -871,12 +871,8 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
} else if (fHaveChain) {
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
}
if (fInstantSend) {
if (!IsInstantSendTxValid(tx)) {
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Not a valid InstantSend transaction");
}
mapLockRequestAccepted.insert(make_pair(hashTx, tx));
CreateTxLockCandidate(tx);
if (fInstantSend && !instantsend.ProcessTxLockRequest(tx)) {
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Not a valid InstantSend transaction, see debug.log for more info");
}
RelayTransaction(tx);

View File

@ -10,7 +10,7 @@
* network protocol versioning
*/
static const int PROTOCOL_VERSION = 70204;
static const int PROTOCOL_VERSION = 70205;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;

View File

@ -1472,9 +1472,8 @@ bool CWalletTx::RelayWalletTransaction(std::string strCommand)
uint256 hash = GetHash();
LogPrintf("Relaying wtx %s\n", hash.ToString());
if(strCommand == NetMsgType::TXLOCKREQUEST){
mapLockRequestAccepted.insert(make_pair(hash, (CTransaction)*this));
CreateTxLockCandidate(((CTransaction)*this));
if(strCommand == NetMsgType::TXLOCKREQUEST) {
instantsend.ProcessTxLockRequest(((CTxLockRequest)*this));
}
RelayTransaction((CTransaction)*this);
return true;
@ -2098,8 +2097,8 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
continue;
int nDepth = pcoin->GetDepthInMainChain(false);
// do not use IX for inputs that have less then 6 blockchain confirmations
if (fUseInstantSend && nDepth < 6)
// do not use IX for inputs that have less then INSTANTSEND_CONFIRMATIONS_REQUIRED blockchain confirmations
if (fUseInstantSend && nDepth < INSTANTSEND_CONFIRMATIONS_REQUIRED)
continue;
// We should not consider coins which aren't at least in our mempool
@ -2358,6 +2357,10 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
nValueRet += out.tx->vout[out.i].nValue;
setCoinsRet.insert(make_pair(out.tx, out.i));
}
if(fUseInstantSend && setCoinsRet.size() > CTxLockRequest::MAX_INPUTS)
return false;
return (nValueRet >= nTargetValue);
}
@ -2885,7 +2888,7 @@ bool CWallet::ConvertList(std::vector<CTxIn> vecTxIn, std::vector<CAmount>& vecA
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType nCoinType, bool fUseInstantSend)
{
CAmount nFeePay = fUseInstantSend ? INSTANTSEND_MIN_FEE : 0;
CAmount nFeePay = fUseInstantSend ? CTxLockRequest().GetMinFee() : 0;
CAmount nValue = 0;
unsigned int nSubtractFeeFromAmount = 0;
@ -3000,19 +3003,26 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (!SelectCoins(nValueToSelect, setCoins, nValueIn, coinControl, nCoinType, fUseInstantSend))
{
if (nCoinType == ALL_COINS) {
strFailReason = _("Insufficient funds.");
} else if (nCoinType == ONLY_NOT1000IFMN) {
if (nCoinType == ONLY_NOT1000IFMN) {
strFailReason = _("Unable to locate enough funds for this transaction that are not equal 1000 DASH.");
} else if (nCoinType == ONLY_NONDENOMINATED_NOT1000IFMN) {
strFailReason = _("Unable to locate enough PrivateSend non-denominated funds for this transaction that are not equal 1000 DASH.");
} else {
} else if (nCoinType == ONLY_DENOMINATED) {
strFailReason = _("Unable to locate enough PrivateSend denominated funds for this transaction.");
strFailReason += " " + _("PrivateSend uses exact denominated amounts to send funds, you might simply need to anonymize some more coins.");
} else if (nValueIn < nValueToSelect) {
strFailReason = _("Insufficient funds.");
}
if (fUseInstantSend) {
size_t nMaxInputs = CTxLockRequest::MAX_INPUTS;
if(setCoins.size() > nMaxInputs) {
strFailReason += " " + strprintf(_("InstantSend doesn't support transactions with more than %d inputs."), nMaxInputs);
} else if (nValueIn > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) {
strFailReason += " " + strprintf(_("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH."), sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE));
} else {
// could be not true but most likely that's the reason
strFailReason += " " + strprintf(_("InstantSend requires inputs with at least %d confirmations, you might need to wait a few minutes and try again."), INSTANTSEND_CONFIRMATIONS_REQUIRED);
}
if(fUseInstantSend){
strFailReason += " " + _("InstantSend requires inputs with at least 6 confirmations, you might need to wait a few minutes and try again.");
}
return false;
@ -3205,6 +3215,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
if(fUseInstantSend) {
nFeeNeeded = std::max(nFeeNeeded, CTxLockRequest(txNew).GetMinFee());
}
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
@ -4045,7 +4058,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet, bool enableIX)
}
}
if(enableIX && nResult < 6 && IsLockedInstandSendTransaction(GetHash()))
if(enableIX && nResult < 6 && instantsend.IsLockedInstantSendTransaction(GetHash()))
return nInstantSendDepth + nResult;
return nResult;