From 36ce197dd7674f4ab0f8ecda7bcbf61fd56fa4d3 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Sun, 15 Mar 2020 18:50:54 +0300 Subject: [PATCH] Fixing bitcoin API change problems due to PR merges part #1 --- src/Makefile.am | 4 +- src/activeznode.cpp | 5 +- src/coins.h | 81 --- src/consensus/params.h | 2 +- src/instantx.cpp | 1156 +++++++++++++++++++---------------- src/instantx.h | 311 +++++++--- src/masternode-payments.cpp | 398 ++++++++++++ src/masternode-payments.h | 41 ++ src/masternode-sync.cpp | 21 +- src/masternode-utils.cpp | 108 ++++ src/masternode-utils.h | 28 + src/memusage.h | 18 +- src/net.h | 34 ++ src/net_processing.cpp | 16 +- src/rpc/blockchain.cpp | 1 + src/txdb.cpp | 8 +- src/txmempool.cpp | 31 +- src/txmempool.h | 8 +- src/util.cpp | 2 + src/util.h | 2 + src/validation.cpp | 112 ++-- src/validation.h | 7 - 22 files changed, 1596 insertions(+), 798 deletions(-) create mode 100644 src/masternode-payments.cpp create mode 100644 src/masternode-payments.h create mode 100644 src/masternode-utils.cpp create mode 100644 src/masternode-utils.h diff --git a/src/Makefile.am b/src/Makefile.am index 729f538973..ab2271e3ae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -360,7 +360,7 @@ endif # wallet: shared between zcoind and zcoin-qt, but only linked # when wallet enabled -libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(DEPENDENCIES_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ activeznode.cpp \ @@ -506,7 +506,7 @@ libbitcoin_common_a_SOURCES = \ # util: shared between all executables. # This library *must* be included to make sure that the glibc # backward-compatibility objects and their sanity checks are linked. -libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(DEPENDENCIES_INCLUDES) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ bls/bls_batchverifier.h \ diff --git a/src/activeznode.cpp b/src/activeznode.cpp index b1c5d1db0b..950912ab73 100644 --- a/src/activeznode.cpp +++ b/src/activeznode.cpp @@ -9,6 +9,7 @@ #include "znode-payments.h" #include "znodeman.h" #include "protocol.h" +#include "netbase.h" // TODO: upgrade to new dash, remove this hack #define vNodes (g_connman->vNodes) @@ -161,8 +162,8 @@ void CActiveZnode::ManageStateInitial() { if(Params().NetworkIDString() == CBaseChainParams::REGTEST) { std::string const & serv = GetArg("-externalip", ""); if(!serv.empty()) { - service = CService(serv.c_str()); - fFoundLocal = true; + if (Lookup(serv.c_str(), service, 0, false)) + fFoundLocal = true; } } diff --git a/src/coins.h b/src/coins.h index 6966e3d8d6..e665078d47 100644 --- a/src/coins.h +++ b/src/coins.h @@ -100,87 +100,6 @@ class SaltedOutpointHasher } }; -/** - * A UTXO entry. - * - * Serialized format: - * - VARINT((coinbase ? 1 : 0) | (height << 1)) - * - the non-spent CTxOut (via CTxOutCompressor) - */ -class Coin -{ -public: - //! unspent transaction output - CTxOut out; - - //! whether containing transaction was a coinbase - unsigned int fCoinBase : 1; - - //! at which height this containing transaction was included in the active block chain - uint32_t nHeight : 31; - - //! construct a Coin from a CTxOut and height/coinbase information. - Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {} - Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {} - - void Clear() { - out.SetNull(); - fCoinBase = false; - nHeight = 0; - } - - //! empty constructor - Coin() : fCoinBase(false), nHeight(0) { } - - bool IsCoinBase() const { - return fCoinBase; - } - - template - void Serialize(Stream &s) const { - assert(!IsSpent()); - uint32_t code = nHeight * 2 + fCoinBase; - ::Serialize(s, VARINT(code)); - ::Serialize(s, CTxOutCompressor(REF(out))); - } - - template - void Unserialize(Stream &s) { - uint32_t code = 0; - ::Unserialize(s, VARINT(code)); - nHeight = code >> 1; - fCoinBase = code & 1; - ::Unserialize(s, REF(CTxOutCompressor(out))); - } - - bool IsSpent() const { - return out.IsNull(); - } - - size_t DynamicMemoryUsage() const { - return memusage::DynamicUsage(out.scriptPubKey); - } -}; - -class SaltedOutpointHasher -{ -private: - /** Salt */ - const uint64_t k0, k1; - -public: - SaltedOutpointHasher(); - - /** - * This *must* return size_t. With Boost 1.46 on 32-bit systems the - * unordered_map will behave unpredictably if the custom hasher returns a - * uint64_t, resulting in failures when syncing the chain (#4634). - */ - size_t operator()(const COutPoint& id) const { - return SipHashUint256Extra(k0, k1, id.hash, id.n); - } -}; - struct CCoinsCacheEntry { Coin coin; // The actual cached data. diff --git a/src/consensus/params.h b/src/consensus/params.h index 72ef0eb5c2..3e1afaa1b7 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -171,7 +171,7 @@ struct Params { int nInstantSendConfirmationsRequired; // in blocks int nInstantSendKeepLock; // in blocks int nInstantSendSigsRequired; - + int nInstantSendSigsTotal; /** Zerocoin-related block numbers when features are changed */ int nCheckBugFixedAtBlock; diff --git a/src/instantx.cpp b/src/instantx.cpp index aa5948d265..849166d28d 100644 --- a/src/instantx.cpp +++ b/src/instantx.cpp @@ -1,147 +1,189 @@ -// Copyright (c) 2014-2017 The Dash Core developers +// Copyright (c) 2014-2019 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "activeznode.h" +#include "activemasternode.h" +#include "init.h" #include "instantx.h" #include "key.h" #include "validation.h" -#include "znode-sync.h" -#include "znodeman.h" -#include "darksend.h" +#include "masternode-payments.h" +#include "masternode-sync.h" +#include "masternode-utils.h" +#include "messagesigner.h" #include "net.h" +#include "netmessagemaker.h" #include "protocol.h" #include "spork.h" #include "sync.h" #include "txmempool.h" #include "util.h" #include "consensus/validation.h" +#include "validationinterface.h" +#include "warnings.h" +#ifdef ENABLE_WALLET +#include "wallet/wallet.h" +#endif // ENABLE_WALLET + +#include "llmq/quorums_instantsend.h" #include #include +#ifdef ENABLE_WALLET extern CWallet* pwalletMain; +#endif // ENABLE_WALLET extern CTxMemPool mempool; bool fEnableInstantSend = true; -int nInstantSendDepth = DEFAULT_INSTANTSEND_DEPTH; -int nCompleteTXLocks; + +std::atomic CInstantSend::isAutoLockBip9Active{false}; +const double CInstantSend::AUTO_IX_MEMPOOL_THRESHOLD = 0.1; CInstantSend instantsend; +const std::string CInstantSend::SERIALIZATION_VERSION_STRING = "CInstantSend-Version-1"; // Transaction Locks // -// step 1) Some node announces intention to lock transaction inputs via "txlreg" message -// step 2) Top COutPointLock::SIGNATURES_TOTAL znodes per each spent outpoint push "txvote" message -// step 3) Once there are COutPointLock::SIGNATURES_REQUIRED valid "txvote" messages per each spent outpoint -// for a corresponding "txlreg" message, all outpoints from that tx are treated as locked +// step 1) Some node announces intention to lock transaction inputs via "txlockrequest" message (ix) +// step 2) Top nInstantSendSigsTotal masternodes per each spent outpoint push "txlockvote" message (txlvote) +// step 3) Once there are nInstantSendSigsRequired valid "txlockvote" messages (txlvote) per each spent outpoint +// for a corresponding "txlockrequest" message (ix), all outpoints from that tx are treated as locked // // CInstantSend // -void CInstantSend::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +void CInstantSend::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { - if(fLiteMode) return; // disable all Zcoin specific functionality -// if(!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return; + if (fLiteMode) return; // disable all Dash specific functionality + if (!llmq::IsOldInstantSendEnabled()) return; - // Ignore any InstantSend messages until znode list is synced - if(!znodeSync.IsZnodeListSynced()) return; + // NOTE: NetMsgType::TXLOCKREQUEST is handled via ProcessMessage() in net_processing.cpp - // NOTE: NetMsgType::TXLOCKREQUEST is handled via ProcessMessage() in main.cpp + if (strCommand == NetMsgType::TXLOCKVOTE) { // InstantSend Transaction Lock Consensus Votes + if(pfrom->nVersion < MIN_INSTANTSEND_PROTO_VERSION) { + LogPrint("instantsend", "TXLOCKVOTE -- peer=%d using obsolete version %i\n", pfrom->id, pfrom->nVersion); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, + strprintf("Version must be %d or greater", MIN_INSTANTSEND_PROTO_VERSION))); + return; + } -// if (strCommand == NetMsgType::TXLOCKVOTE) // InstantSend Transaction Lock Consensus Votes -// { -// if(pfrom->nVersion < MIN_INSTANTSEND_PROTO_VERSION) return; -// -// CTxLockVote vote; -// vRecv >> vote; -// -// LOCK2(cs_main, cs_instantsend); -// -// uint256 nVoteHash = vote.GetHash(); -// -// if(mapTxLockVotes.count(nVoteHash)) return; -// mapTxLockVotes.insert(std::make_pair(nVoteHash, vote)); -// -// ProcessTxLockVote(pfrom, vote); -// -// return; -// } + CTxLockVote vote; + vRecv >> vote; + + + uint256 nVoteHash = vote.GetHash(); + + { + LOCK(cs_main); + connman.RemoveAskFor(nVoteHash); + } + + // Ignore any InstantSend messages until blockchain is synced + if (!masternodeSync.IsBlockchainSynced()) return; + + { + LOCK(cs_instantsend); + auto ret = mapTxLockVotes.emplace(nVoteHash, vote); + if (!ret.second) return; + } + + ProcessNewTxLockVote(pfrom, vote, connman); + + return; + } } -bool CInstantSend::ProcessTxLockRequest(const CTxLockRequest& txLockRequest) +bool CInstantSend::ProcessTxLockRequest(const CTxLockRequest& txLockRequest, CConnman& connman) { - LOCK2(cs_main, cs_instantsend); + LOCK(cs_main); +#ifdef ENABLE_WALLET + LOCK(pwalletMain ? &pwalletMain->cs_wallet : NULL); +#endif + LOCK2(mempool.cs, cs_instantsend); uint256 txHash = txLockRequest.GetHash(); - // Check to see if we conflict with existing completed lock, - // fail if so, there can't be 2 completed locks for the same outpoint - BOOST_FOREACH(const CTxIn& txin, txLockRequest.vin) { + // Check to see if we conflict with existing completed lock + for (const auto& txin : txLockRequest.tx->vin) { std::map::iterator it = mapLockedOutpoints.find(txin.prevout); - if(it != mapLockedOutpoints.end()) { - // Conflicting with complete lock, ignore this one - // (this could be the one we have but we don't want to try to lock it twice anyway) - LogPrintf("CInstantSend::ProcessTxLockRequest -- WARNING: Found conflicting completed Transaction Lock, skipping current one, txid=%s, completed lock txid=%s\n", + if (it != mapLockedOutpoints.end() && it->second != txLockRequest.GetHash()) { + // Conflicting with complete lock, proceed to see if we should cancel them both + LogPrintf("CInstantSend::ProcessTxLockRequest -- WARNING: Found conflicting completed Transaction Lock, txid=%s, completed lock txid=%s\n", txLockRequest.GetHash().ToString(), it->second.ToString()); - return false; } } // Check to see if there are votes for conflicting request, // if so - do not fail, just warn user - BOOST_FOREACH(const CTxIn& txin, txLockRequest.vin) { + for (const auto& txin : txLockRequest.tx->vin) { std::map >::iterator it = mapVotedOutpoints.find(txin.prevout); - if(it != mapVotedOutpoints.end()) { - BOOST_FOREACH(const uint256& hash, it->second) { - if(hash != txLockRequest.GetHash()) { + if (it != mapVotedOutpoints.end()) { + for (const auto& hash : it->second) { + if (hash != txLockRequest.GetHash()) { LogPrint("instantsend", "CInstantSend::ProcessTxLockRequest -- Double spend attempt! %s\n", txin.prevout.ToStringShort()); // do not fail here, let it go and see which one will get the votes to be locked + // NOTIFY ZMQ + CTransaction txCurrent = *txLockRequest.tx; // currently processed tx + auto itPrevious = mapTxLockCandidates.find(hash); + if (itPrevious != mapTxLockCandidates.end() && itPrevious->second.txLockRequest) { + CTransaction txPrevious = *itPrevious->second.txLockRequest.tx; // previously locked one + GetMainSignals().NotifyInstantSendDoubleSpendAttempt(txCurrent, txPrevious); + } } } } } - if(!CreateTxLockCandidate(txLockRequest)) { + if (!CreateTxLockCandidate(txLockRequest)) { // smth is not right LogPrintf("CInstantSend::ProcessTxLockRequest -- CreateTxLockCandidate failed, txid=%s\n", txHash.ToString()); return false; } LogPrintf("CInstantSend::ProcessTxLockRequest -- accepted, txid=%s\n", txHash.ToString()); - std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); - CTxLockCandidate& txLockCandidate = itLockCandidate->second; - Vote(txLockCandidate); + // Masternodes will sometimes propagate votes before the transaction is known to the client. + // If this just happened - process orphan votes, lock inputs, resolve conflicting locks, + // update transaction status forcing external script/zmq notifications. ProcessOrphanTxLockVotes(); - - // Znodes will sometimes propagate votes before the transaction is known to the client. - // If this just happened - lock inputs, resolve conflicting locks, update transaction status - // forcing external script notification. - TryToFinalizeLockCandidate(txLockCandidate); + std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); + TryToFinalizeLockCandidate(itLockCandidate->second); return true; } bool CInstantSend::CreateTxLockCandidate(const CTxLockRequest& txLockRequest) { - // Normally we should require all outpoints to be unspent, but in case we are reprocessing - // because of a lot of legit orphan votes we should also check already spent outpoints. - uint256 txHash = txLockRequest.GetHash(); - if(!txLockRequest.IsValid(!IsEnoughOrphanVotesForTx(txLockRequest))) return false; + if (!txLockRequest.IsValid()) return false; LOCK(cs_instantsend); + uint256 txHash = txLockRequest.GetHash(); + std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); - if(itLockCandidate == mapTxLockCandidates.end()) { + if (itLockCandidate == mapTxLockCandidates.end()) { LogPrintf("CInstantSend::CreateTxLockCandidate -- new, txid=%s\n", txHash.ToString()); CTxLockCandidate txLockCandidate(txLockRequest); // all inputs should already be checked by txLockRequest.IsValid() above, just use them now - BOOST_REVERSE_FOREACH(const CTxIn& txin, txLockRequest.vin) { + for (const auto& txin : txLockRequest.tx->vin) { txLockCandidate.AddOutPointLock(txin.prevout); } mapTxLockCandidates.insert(std::make_pair(txHash, txLockCandidate)); + } else if (!itLockCandidate->second.txLockRequest) { + // i.e. empty Transaction Lock Candidate was created earlier, let's update it with actual data + itLockCandidate->second.txLockRequest = txLockRequest; + if (itLockCandidate->second.IsTimedOut()) { + LogPrintf("CInstantSend::CreateTxLockCandidate -- timed out, txid=%s\n", txHash.ToString()); + return false; + } + LogPrintf("CInstantSend::CreateTxLockCandidate -- update empty, txid=%s\n", txHash.ToString()); + + // all inputs should already be checked by txLockRequest.IsValid() above, just use them now + for (const auto& txin : txLockRequest.tx->vin) { + itLockCandidate->second.AddOutPointLock(txin.prevout); + } } else { LogPrint("instantsend", "CInstantSend::CreateTxLockCandidate -- seen, txid=%s\n", txHash.ToString()); } @@ -149,74 +191,105 @@ bool CInstantSend::CreateTxLockCandidate(const CTxLockRequest& txLockRequest) return true; } -void CInstantSend::Vote(CTxLockCandidate& txLockCandidate) +void CInstantSend::CreateEmptyTxLockCandidate(const uint256& txHash) { - if(!fMasternodeMode) return; + if (mapTxLockCandidates.find(txHash) != mapTxLockCandidates.end()) + return; + LogPrintf("CInstantSend::CreateEmptyTxLockCandidate -- new, txid=%s\n", txHash.ToString()); + const CTxLockRequest txLockRequest = CTxLockRequest(); + mapTxLockCandidates.insert(std::make_pair(txHash, CTxLockCandidate(txLockRequest))); +} - LOCK2(cs_main, cs_instantsend); +void CInstantSend::Vote(const uint256& txHash, CConnman& connman) +{ + AssertLockHeld(cs_main); +#ifdef ENABLE_WALLET + LOCK(pwalletMain ? &pwalletMain->cs_wallet : NULL); +#endif + + CTxLockRequest dummyRequest; + CTxLockCandidate txLockCandidate(dummyRequest); + { + LOCK(cs_instantsend); + auto itLockCandidate = mapTxLockCandidates.find(txHash); + if (itLockCandidate == mapTxLockCandidates.end()) return; + txLockCandidate = itLockCandidate->second; + Vote(txLockCandidate, connman); + } + + // Let's see if our vote changed smth + LOCK2(mempool.cs, cs_instantsend); + TryToFinalizeLockCandidate(txLockCandidate); +} + +void CInstantSend::Vote(CTxLockCandidate& txLockCandidate, CConnman& connman) +{ + if (!fMasternodeMode) return; + if (!llmq::IsOldInstantSendEnabled()) return; + + AssertLockHeld(cs_main); + AssertLockHeld(cs_instantsend); uint256 txHash = txLockCandidate.GetHash(); + // We should never vote on a Transaction Lock Request that was not (yet) accepted by the mempool + if (mapLockRequestAccepted.find(txHash) == mapLockRequestAccepted.end()) return; // check if we need to vote on this candidate's outpoints, // it's possible that we need to vote for several of them - std::map::iterator itOutpointLock = txLockCandidate.mapOutPointLocks.begin(); - while(itOutpointLock != txLockCandidate.mapOutPointLocks.end()) { - - int nPrevoutHeight = GetUTXOHeight(itOutpointLock->first); - if(nPrevoutHeight == -1) { - LogPrint("instantsend", "CInstantSend::Vote -- Failed to find UTXO %s\n", itOutpointLock->first.ToStringShort()); + for (auto& outpointLockPair : txLockCandidate.mapOutPointLocks) { + int nPrevoutHeight = GetUTXOHeight(outpointLockPair.first); + if (nPrevoutHeight == -1) { + LogPrint("instantsend", "CInstantSend::Vote -- Failed to find UTXO %s\n", outpointLockPair.first.ToStringShort()); return; } - int nLockInputHeight = nPrevoutHeight + 4; + int nLockInputHeight = nPrevoutHeight + Params().GetConsensus().nInstantSendConfirmationsRequired - 2; - int n = mnodeman.GetZnodeRank(activeZnode.vin, nLockInputHeight, MIN_INSTANTSEND_PROTO_VERSION); - - if(n == -1) { - LogPrint("instantsend", "CInstantSend::Vote -- Unknown Znode %s\n", activeZnode.vin.prevout.ToStringShort()); - ++itOutpointLock; + int nRank; + uint256 quorumModifierHash; + if (!CMasternodeUtils::GetMasternodeRank(activeMasternodeInfo.outpoint, nRank, quorumModifierHash, nLockInputHeight)) { + LogPrint("instantsend", "CInstantSend::Vote -- Can't calculate rank for masternode %s\n", activeMasternodeInfo.outpoint.ToStringShort()); continue; } - int nSignaturesTotal = COutPointLock::SIGNATURES_TOTAL; - if(n > nSignaturesTotal) { - LogPrint("instantsend", "CInstantSend::Vote -- Znode not in the top %d (%d)\n", nSignaturesTotal, n); - ++itOutpointLock; + int nSignaturesTotal = Params().GetConsensus().nInstantSendSigsTotal; + if (nRank > nSignaturesTotal) { + LogPrint("instantsend", "CInstantSend::Vote -- Masternode not in the top %d (%d)\n", nSignaturesTotal, nRank); continue; } - LogPrint("instantsend", "CInstantSend::Vote -- In the top %d (%d)\n", nSignaturesTotal, n); + LogPrint("instantsend", "CInstantSend::Vote -- In the top %d (%d)\n", nSignaturesTotal, nRank); - std::map >::iterator itVoted = mapVotedOutpoints.find(itOutpointLock->first); + std::map >::iterator itVoted = mapVotedOutpoints.find(outpointLockPair.first); // Check to see if we already voted for this outpoint, // refuse to vote twice or to include the same outpoint in another tx bool fAlreadyVoted = false; - if(itVoted != mapVotedOutpoints.end()) { - BOOST_FOREACH(const uint256& hash, itVoted->second) { + if (itVoted != mapVotedOutpoints.end()) { + for (const auto& hash : itVoted->second) { std::map::iterator it2 = mapTxLockCandidates.find(hash); - if(it2->second.HasZnodeVoted(itOutpointLock->first, activeZnode.vin.prevout)) { + if (it2->second.HasMasternodeVoted(outpointLockPair.first, activeMasternodeInfo.outpoint)) { // we already voted for this outpoint to be included either in the same tx or in a competing one, // skip it anyway fAlreadyVoted = true; LogPrintf("CInstantSend::Vote -- WARNING: We already voted for this outpoint, skipping: txHash=%s, outpoint=%s\n", - txHash.ToString(), itOutpointLock->first.ToStringShort()); + txHash.ToString(), outpointLockPair.first.ToStringShort()); break; } } } - if(fAlreadyVoted) { - ++itOutpointLock; + if (fAlreadyVoted) { continue; // skip to the next outpoint } // we haven't voted for this outpoint yet, let's try to do this now - CTxLockVote vote(txHash, itOutpointLock->first, activeZnode.vin.prevout); + // Please note that activeMasternodeInfo.proTxHash is only valid after spork15 activation + CTxLockVote vote(txHash, outpointLockPair.first, activeMasternodeInfo.outpoint, quorumModifierHash, activeMasternodeInfo.proTxHash); - if(!vote.Sign()) { + if (!vote.Sign()) { LogPrintf("CInstantSend::Vote -- Failed to sign consensus vote\n"); return; } - if(!vote.CheckSignature()) { + if (!vote.CheckSignature()) { LogPrintf("CInstantSend::Vote -- Signature invalid\n"); return; } @@ -224,195 +297,216 @@ void CInstantSend::Vote(CTxLockCandidate& txLockCandidate) // vote constructed sucessfully, let's store and relay it uint256 nVoteHash = vote.GetHash(); mapTxLockVotes.insert(std::make_pair(nVoteHash, vote)); - if(itOutpointLock->second.AddVote(vote)) { + if (outpointLockPair.second.AddVote(vote)) { LogPrintf("CInstantSend::Vote -- Vote created successfully, relaying: txHash=%s, outpoint=%s, vote=%s\n", - txHash.ToString(), itOutpointLock->first.ToStringShort(), nVoteHash.ToString()); + txHash.ToString(), outpointLockPair.first.ToStringShort(), nVoteHash.ToString()); - if(itVoted == mapVotedOutpoints.end()) { + if (itVoted == mapVotedOutpoints.end()) { std::set setHashes; setHashes.insert(txHash); - mapVotedOutpoints.insert(std::make_pair(itOutpointLock->first, setHashes)); + mapVotedOutpoints.insert(std::make_pair(outpointLockPair.first, setHashes)); } else { - mapVotedOutpoints[itOutpointLock->first].insert(txHash); - if(mapVotedOutpoints[itOutpointLock->first].size() > 1) { + mapVotedOutpoints[outpointLockPair.first].insert(txHash); + if (mapVotedOutpoints[outpointLockPair.first].size() > 1) { // it's ok to continue, just warn user LogPrintf("CInstantSend::Vote -- WARNING: Vote conflicts with some existing votes: txHash=%s, outpoint=%s, vote=%s\n", - txHash.ToString(), itOutpointLock->first.ToStringShort(), nVoteHash.ToString()); + txHash.ToString(), outpointLockPair.first.ToStringShort(), nVoteHash.ToString()); } } - vote.Relay(); + vote.Relay(connman); } - - ++itOutpointLock; } } -//received a consensus vote -bool CInstantSend::ProcessTxLockVote(CNode* pfrom, CTxLockVote& vote) +bool CInstantSend::ProcessNewTxLockVote(CNode* pfrom, const CTxLockVote& vote, CConnman& connman) { - LOCK2(cs_main, cs_instantsend); - uint256 txHash = vote.GetTxHash(); + uint256 nVoteHash = vote.GetHash(); - if(!vote.IsValid(pfrom)) { + if (!vote.IsValid(pfrom, connman)) { // could be because of missing MN - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- Vote is invalid, txid=%s\n", txHash.ToString()); + LogPrint("instantsend", "CInstantSend::%s -- Vote is invalid, txid=%s\n", __func__, txHash.ToString()); return false; } - // Znodes will sometimes propagate votes before the transaction is known to the client, + // relay valid vote asap + vote.Relay(connman); + + LOCK(cs_main); +#ifdef ENABLE_WALLET + LOCK(pwalletMain ? &pwalletMain->cs_wallet : NULL); +#endif + LOCK2(mempool.cs, cs_instantsend); + + // Masternodes will sometimes propagate votes before the transaction is known to the client, // will actually process only after the lock request itself has arrived std::map::iterator it = mapTxLockCandidates.find(txHash); - if(it == mapTxLockCandidates.end()) { - if(!mapTxLockVotesOrphan.count(vote.GetHash())) { - mapTxLockVotesOrphan[vote.GetHash()] = vote; - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- Orphan vote: txid=%s znode=%s new\n", - txHash.ToString(), vote.GetZnodeOutpoint().ToStringShort()); - bool fReprocess = true; - std::map::iterator itLockRequest = mapLockRequestAccepted.find(txHash); - if(itLockRequest == mapLockRequestAccepted.end()) { - itLockRequest = mapLockRequestRejected.find(txHash); - if(itLockRequest == mapLockRequestRejected.end()) { - // still too early, wait for tx lock request - fReprocess = false; - } - } - if(fReprocess && IsEnoughOrphanVotesForTx(itLockRequest->second)) { - // We have enough votes for corresponding lock to complete, - // tx lock request should already be received at this stage. - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- Found enough orphan votes, reprocessing Transaction Lock Request: txid=%s\n", txHash.ToString()); - ProcessTxLockRequest(itLockRequest->second); - return true; - } - } else { - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- Orphan vote: txid=%s znode=%s seen\n", - txHash.ToString(), vote.GetZnodeOutpoint().ToStringShort()); + if (it == mapTxLockCandidates.end() || !it->second.txLockRequest) { + // no or empty tx lock candidate + if (it == mapTxLockCandidates.end()) { + // start timeout countdown after the very first vote + CreateEmptyTxLockCandidate(txHash); } + bool fInserted = mapTxLockVotesOrphan.emplace(nVoteHash, vote).second; + LogPrint("instantsend", "CInstantSend::%s -- Orphan vote: txid=%s masternode=%s %s\n", + __func__, txHash.ToString(), vote.GetMasternodeOutpoint().ToStringShort(), fInserted ? "new" : "seen"); // This tracks those messages and allows only the same rate as of the rest of the network // TODO: make sure this works good enough for multi-quorum - int nZnodeOrphanExpireTime = GetTime() + 60*10; // keep time data for 10 minutes - if(!mapZnodeOrphanVotes.count(vote.GetZnodeOutpoint())) { - mapZnodeOrphanVotes[vote.GetZnodeOutpoint()] = nZnodeOrphanExpireTime; + int nMasternodeOrphanExpireTime = GetTime() + 60*10; // keep time data for 10 minutes + auto itMnOV = mapMasternodeOrphanVotes.find(vote.GetMasternodeOutpoint()); + if (itMnOV == mapMasternodeOrphanVotes.end()) { + mapMasternodeOrphanVotes.emplace(vote.GetMasternodeOutpoint(), nMasternodeOrphanExpireTime); } else { - int64_t nPrevOrphanVote = mapZnodeOrphanVotes[vote.GetZnodeOutpoint()]; - if(nPrevOrphanVote > GetTime() && nPrevOrphanVote > GetAverageZnodeOrphanVoteTime()) { - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- znode is spamming orphan Transaction Lock Votes: txid=%s znode=%s\n", - txHash.ToString(), vote.GetZnodeOutpoint().ToStringShort()); + if (itMnOV->second > GetTime() && itMnOV->second > GetAverageMasternodeOrphanVoteTime()) { + LogPrint("instantsend", "CInstantSend::%s -- masternode is spamming orphan Transaction Lock Votes: txid=%s masternode=%s\n", + __func__, txHash.ToString(), vote.GetMasternodeOutpoint().ToStringShort()); // Misbehaving(pfrom->id, 1); return false; } // not spamming, refresh - mapZnodeOrphanVotes[vote.GetZnodeOutpoint()] = nZnodeOrphanExpireTime; + itMnOV->second = nMasternodeOrphanExpireTime; } return true; } - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- Transaction Lock Vote, txid=%s\n", txHash.ToString()); + // We have a valid (non-empty) tx lock candidate + CTxLockCandidate& txLockCandidate = it->second; - std::map >::iterator it1 = mapVotedOutpoints.find(vote.GetOutpoint()); - if(it1 != mapVotedOutpoints.end()) { - BOOST_FOREACH(const uint256& hash, it1->second) { - if(hash != txHash) { - // same outpoint was already voted to be locked by another tx lock request, - // find out if the same mn voted on this outpoint before - std::map::iterator it2 = mapTxLockCandidates.find(hash); - if(it2->second.HasZnodeVoted(vote.GetOutpoint(), vote.GetZnodeOutpoint())) { - // yes, it did, refuse to accept a vote to include the same outpoint in another tx - // from the same znode. - // TODO: apply pose ban score to this znode? - // NOTE: if we decide to apply pose ban score here, this vote must be relayed further - // to let all other nodes know about this node's misbehaviour and let them apply - // pose ban score too. - LogPrintf("CInstantSend::ProcessTxLockVote -- znode sent conflicting votes! %s\n", vote.GetZnodeOutpoint().ToStringShort()); - return false; - } - } - } - // we have votes by other znodes only (so far), let's continue and see who will win - it1->second.insert(txHash); - } else { - std::set setHashes; - setHashes.insert(txHash); - mapVotedOutpoints.insert(std::make_pair(vote.GetOutpoint(), setHashes)); + if (txLockCandidate.IsTimedOut()) { + LogPrint("instantsend", "CInstantSend::%s -- too late, Transaction Lock timed out, txid=%s\n", __func__, txHash.ToString()); + return false; } - CTxLockCandidate& txLockCandidate = it->second; + LogPrint("instantsend", "CInstantSend::%s -- Transaction Lock Vote, txid=%s\n", __func__, txHash.ToString()); - if(!txLockCandidate.AddVote(vote)) { + UpdateVotedOutpoints(vote, txLockCandidate); + + if (!txLockCandidate.AddVote(vote)) { // this should never happen return false; } int nSignatures = txLockCandidate.CountVotes(); int nSignaturesMax = txLockCandidate.txLockRequest.GetMaxSignatures(); - LogPrint("instantsend", "CInstantSend::ProcessTxLockVote -- Transaction Lock signatures count: %d/%d, vote hash=%s\n", - nSignatures, nSignaturesMax, vote.GetHash().ToString()); + LogPrint("instantsend", "CInstantSend::%s -- Transaction Lock signatures count: %d/%d, vote hash=%s\n", __func__, + nSignatures, nSignaturesMax, nVoteHash.ToString()); TryToFinalizeLockCandidate(txLockCandidate); - vote.Relay(); - return true; } -void CInstantSend::ProcessOrphanTxLockVotes() +bool CInstantSend::ProcessOrphanTxLockVote(const CTxLockVote& vote) { - LOCK2(cs_main, cs_instantsend); - std::map::iterator it = mapTxLockVotesOrphan.begin(); - while(it != mapTxLockVotesOrphan.end()) { - if(ProcessTxLockVote(NULL, it->second)) { - mapTxLockVotesOrphan.erase(it++); - } else { - ++it; - } + // cs_main, cs_wallet and cs_instantsend should be already locked + AssertLockHeld(cs_main); +#ifdef ENABLE_WALLET + if (pwalletMain) + AssertLockHeld(pwalletMain->cs_wallet); +#endif + AssertLockHeld(cs_instantsend); + + uint256 txHash = vote.GetTxHash(); + + // We shouldn't process orphan votes without a valid tx lock candidate + std::map::iterator it = mapTxLockCandidates.find(txHash); + if (it == mapTxLockCandidates.end() || !it->second.txLockRequest) + return false; // this shouldn never happen + + CTxLockCandidate& txLockCandidate = it->second; + + if (txLockCandidate.IsTimedOut()) { + LogPrint("instantsend", "CInstantSend::%s -- too late, Transaction Lock timed out, txid=%s\n", __func__, txHash.ToString()); + return false; + } + + LogPrint("instantsend", "CInstantSend::%s -- Transaction Lock Vote, txid=%s\n", __func__, txHash.ToString()); + + UpdateVotedOutpoints(vote, txLockCandidate); + + if (!txLockCandidate.AddVote(vote)) { + // this should never happen + return false; } + + int nSignatures = txLockCandidate.CountVotes(); + int nSignaturesMax = txLockCandidate.txLockRequest.GetMaxSignatures(); + LogPrint("instantsend", "CInstantSend::%s -- Transaction Lock signatures count: %d/%d, vote hash=%s\n", + __func__, nSignatures, nSignaturesMax, vote.GetHash().ToString()); + + return true; } -bool CInstantSend::IsEnoughOrphanVotesForTx(const CTxLockRequest& txLockRequest) +void CInstantSend::UpdateVotedOutpoints(const CTxLockVote& vote, CTxLockCandidate& txLockCandidate) { - // There could be a situation when we already have quite a lot of votes - // but tx lock request still wasn't received. Let's scan through - // orphan votes to check if this is the case. - BOOST_FOREACH(const CTxIn& txin, txLockRequest.vin) { - if(!IsEnoughOrphanVotesForTxAndOutPoint(txLockRequest.GetHash(), txin.prevout)) { - return false; + AssertLockHeld(cs_instantsend); + + uint256 txHash = vote.GetTxHash(); + + std::map >::iterator it1 = mapVotedOutpoints.find(vote.GetOutpoint()); + if (it1 != mapVotedOutpoints.end()) { + for (const auto& hash : it1->second) { + if (hash != txHash) { + // same outpoint was already voted to be locked by another tx lock request, + // let's see if it was the same masternode who voted on this outpoint + // for another tx lock request + std::map::iterator it2 = mapTxLockCandidates.find(hash); + if (it2 !=mapTxLockCandidates.end() && it2->second.HasMasternodeVoted(vote.GetOutpoint(), vote.GetMasternodeOutpoint())) { + // yes, it was the same masternode + LogPrintf("CInstantSend::%s -- masternode sent conflicting votes! %s\n", __func__, vote.GetMasternodeOutpoint().ToStringShort()); + // mark both Lock Candidates as attacked, none of them should complete, + // or at least the new (current) one shouldn't even + // if the second one was already completed earlier + txLockCandidate.MarkOutpointAsAttacked(vote.GetOutpoint()); + it2->second.MarkOutpointAsAttacked(vote.GetOutpoint()); + // apply maximum PoSe ban score to this masternode i.e. PoSe-ban it instantly + // TODO Call new PoSe system when it's ready + //mnodeman.PoSeBan(vote.GetMasternodeOutpoint()); + // NOTE: This vote must be relayed further to let all other nodes know about such + // misbehaviour of this masternode. This way they should also be able to construct + // conflicting lock and PoSe-ban this masternode. + } + } } + // store all votes, regardless of them being sent by malicious masternode or not + it1->second.insert(txHash); + } else { + mapVotedOutpoints.emplace(vote.GetOutpoint(), std::set({txHash})); } - return true; } -bool CInstantSend::IsEnoughOrphanVotesForTxAndOutPoint(const uint256& txHash, const COutPoint& outpoint) +void CInstantSend::ProcessOrphanTxLockVotes() { - // Scan orphan votes to check if this outpoint has enough orphan votes to be locked in some tx. - LOCK2(cs_main, cs_instantsend); - int nCountVotes = 0; + AssertLockHeld(cs_main); + AssertLockHeld(cs_instantsend); + std::map::iterator it = mapTxLockVotesOrphan.begin(); - while(it != mapTxLockVotesOrphan.end()) { - if(it->second.GetTxHash() == txHash && it->second.GetOutpoint() == outpoint) { - nCountVotes++; - if(nCountVotes >= COutPointLock::SIGNATURES_REQUIRED) { - return true; - } + while (it != mapTxLockVotesOrphan.end()) { + if (ProcessOrphanTxLockVote(it->second)) { + mapTxLockVotesOrphan.erase(it++); + } else { + ++it; } - ++it; } - return false; } void CInstantSend::TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate) { - LOCK2(cs_main, cs_instantsend); + if (!llmq::IsOldInstantSendEnabled()) return; - uint256 txHash = txLockCandidate.txLockRequest.GetHash(); - if(txLockCandidate.IsAllOutPointsReady() && !IsLockedInstantSendTransaction(txHash)) { + AssertLockHeld(cs_main); + AssertLockHeld(cs_instantsend); + + uint256 txHash = txLockCandidate.txLockRequest.tx->GetHash(); + if (txLockCandidate.IsAllOutPointsReady() && !IsLockedInstantSendTransaction(txHash)) { // we have enough votes now LogPrint("instantsend", "CInstantSend::TryToFinalizeLockCandidate -- Transaction Lock is ready to complete, txid=%s\n", txHash.ToString()); - if(ResolveConflicts(txLockCandidate, Params().GetConsensus().nInstantSendKeepLock)) { + if (ResolveConflicts(txLockCandidate)) { LockTransactionInputs(txLockCandidate); UpdateLockedTransaction(txLockCandidate); } @@ -421,44 +515,47 @@ void CInstantSend::TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCand void CInstantSend::UpdateLockedTransaction(const CTxLockCandidate& txLockCandidate) { - LOCK(cs_instantsend); + // cs_main, cs_wallet and cs_instantsend should be already locked + AssertLockHeld(cs_main); +#ifdef ENABLE_WALLET + if (pwalletMain) { + AssertLockHeld(pwalletMain->cs_wallet); + } +#endif + AssertLockHeld(cs_instantsend); uint256 txHash = txLockCandidate.GetHash(); - if(!IsLockedInstantSendTransaction(txHash)) return; // not a locked tx, do not update/notify + if (!IsLockedInstantSendTransaction(txHash)) return; // not a locked tx, do not update/notify #ifdef ENABLE_WALLET - if(pwalletMain) { - pwalletMain->UpdatedTransaction(txHash); - // bumping this to update UI - nCompleteTXLocks++; + if (pwalletMain && pwalletMain->UpdatedTransaction(txHash)) { // notify an external script once threshold is reached std::string strCmd = GetArg("-instantsendnotify", ""); - if(!strCmd.empty()) { + if (!strCmd.empty()) { boost::replace_all(strCmd, "%s", txHash.GetHex()); boost::thread t(runCommand, strCmd); // thread runs free } } #endif -// GetMainSignals().NotifyTransactionLock(txLockCandidate.txLockRequest); + GetMainSignals().NotifyTransactionLock(*txLockCandidate.txLockRequest.tx); LogPrint("instantsend", "CInstantSend::UpdateLockedTransaction -- done, txid=%s\n", txHash.ToString()); } void CInstantSend::LockTransactionInputs(const CTxLockCandidate& txLockCandidate) { + if (!llmq::IsOldInstantSendEnabled()) return; + LOCK(cs_instantsend); uint256 txHash = txLockCandidate.GetHash(); - if(!txLockCandidate.IsAllOutPointsReady()) return; + if (!txLockCandidate.IsAllOutPointsReady()) return; - std::map::const_iterator it = txLockCandidate.mapOutPointLocks.begin(); - - while(it != txLockCandidate.mapOutPointLocks.end()) { - mapLockedOutpoints.insert(std::make_pair(it->first, txHash)); - ++it; + for (const auto& pair : txLockCandidate.mapOutPointLocks) { + mapLockedOutpoints.insert(std::make_pair(pair.first, txHash)); } LogPrint("instantsend", "CInstantSend::LockTransactionInputs -- done, txid=%s\n", txHash.ToString()); } @@ -467,92 +564,80 @@ bool CInstantSend::GetLockedOutPointTxHash(const COutPoint& outpoint, uint256& h { LOCK(cs_instantsend); std::map::iterator it = mapLockedOutpoints.find(outpoint); - if(it == mapLockedOutpoints.end()) return false; + if (it == mapLockedOutpoints.end()) return false; hashRet = it->second; return true; } -bool CInstantSend::ResolveConflicts(const CTxLockCandidate& txLockCandidate, int nMaxBlocks) +bool CInstantSend::ResolveConflicts(const CTxLockCandidate& txLockCandidate) { - if(nMaxBlocks < 1) return false; - - LOCK2(cs_main, cs_instantsend); + AssertLockHeld(cs_main); + AssertLockHeld(cs_instantsend); uint256 txHash = txLockCandidate.GetHash(); // make sure the lock is ready - if(!txLockCandidate.IsAllOutPointsReady()) return true; // not an error - - LOCK(mempool.cs); // protect mempool.mapNextTx, mempool.mapTx + if (!txLockCandidate.IsAllOutPointsReady()) return false; - bool fMempoolConflict = false; + AssertLockHeld(mempool.cs); // protect mempool.mapNextTx - BOOST_FOREACH(const CTxIn& txin, txLockCandidate.txLockRequest.vin) { + for (const auto& txin : txLockCandidate.txLockRequest.tx->vin) { uint256 hashConflicting; - if(GetLockedOutPointTxHash(txin.prevout, hashConflicting) && txHash != hashConflicting) { - // conflicting with complete lock, ignore current one - LogPrintf("CInstantSend::ResolveConflicts -- WARNING: Found conflicting completed Transaction Lock, skipping current one, txid=%s, conflicting txid=%s\n", + if (GetLockedOutPointTxHash(txin.prevout, hashConflicting) && txHash != hashConflicting) { + // completed lock which conflicts with another completed one? + // this means that majority of MNs in the quorum for this specific tx input are malicious! + std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); + std::map::iterator itLockCandidateConflicting = mapTxLockCandidates.find(hashConflicting); + if (itLockCandidate == mapTxLockCandidates.end() || itLockCandidateConflicting == mapTxLockCandidates.end()) { + // safety check, should never really happen + LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Found conflicting completed Transaction Lock, but one of txLockCandidate-s is missing, txid=%s, conflicting txid=%s\n", + txHash.ToString(), hashConflicting.ToString()); + return false; + } + LogPrintf("CInstantSend::ResolveConflicts -- WARNING: Found conflicting completed Transaction Lock, dropping both, txid=%s, conflicting txid=%s\n", txHash.ToString(), hashConflicting.ToString()); - return false; // can't/shouldn't do anything + CTxLockRequest txLockRequest = itLockCandidate->second.txLockRequest; + CTxLockRequest txLockRequestConflicting = itLockCandidateConflicting->second.txLockRequest; + itLockCandidate->second.SetConfirmedHeight(0); // expired + itLockCandidateConflicting->second.SetConfirmedHeight(0); // expired + CheckAndRemove(); // clean up + // AlreadyHave should still return "true" for both of them + mapLockRequestRejected.insert(std::make_pair(txHash, txLockRequest)); + mapLockRequestRejected.insert(std::make_pair(hashConflicting, txLockRequestConflicting)); + + // TODO: clean up mapLockRequestRejected later somehow + // (not a big issue since we already PoSe ban malicious masternodes + // and they won't be able to spam) + // TODO: ban all malicious masternodes permanently, do not accept anything from them, ever + + // TODO: notify zmq+script about this double-spend attempt + // and let merchant cancel/hold the order if it's not too late... + + // can't do anything else, fallback to regular txes + return false; } else if (mempool.mapNextTx.count(txin.prevout)) { // check if it's in mempool -// hashConflicting = mempool.mapNextTx[txin.prevout].ptx->GetHash(); - if(txHash == hashConflicting) continue; // matches current, not a conflict, skip to next txin - // conflicting with tx in mempool - fMempoolConflict = true; - if(HasTxLockRequest(hashConflicting)) { - // There can be only one completed lock, the other lock request should never complete - LogPrintf("CInstantSend::ResolveConflicts -- WARNING: Found conflicting Transaction Lock Request, replacing by completed Transaction Lock, txid=%s, conflicting txid=%s\n", - txHash.ToString(), hashConflicting.ToString()); - } else { - // If this lock is completed, we don't really care about normal conflicting txes. - LogPrintf("CInstantSend::ResolveConflicts -- WARNING: Found conflicting transaction, replacing by completed Transaction Lock, txid=%s, conflicting txid=%s\n", - txHash.ToString(), hashConflicting.ToString()); - } - } - } // FOREACH - if(fMempoolConflict) { - // remove every tx conflicting with current Transaction Lock Request - mempool.removeConflicts(txLockCandidate.txLockRequest); - // and try to accept it in mempool again - CValidationState state; - bool fMissingInputs = false; - if(!AcceptToMemoryPool(mempool, state, MakeTransactionRef(txLockCandidate.txLockRequest), true, &fMissingInputs)) { - LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Failed to accept completed Transaction Lock to mempool, txid=%s\n", txHash.ToString()); + hashConflicting = mempool.mapNextTx.find(txin.prevout)->second->GetHash(); + if (txHash == hashConflicting) continue; // matches current, not a conflict, skip to next txin + // conflicts with tx in mempool + LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Failed to complete Transaction Lock, conflicts with mempool, txid=%s\n", txHash.ToString()); return false; } - LogPrintf("CInstantSend::ResolveConflicts -- Accepted completed Transaction Lock, txid=%s\n", txHash.ToString()); - return true; - } + } // FOREACH // No conflicts were found so far, check to see if it was already included in block CTransactionRef txTmp; uint256 hashBlock; - if(GetTransaction(txHash, txTmp, Params().GetConsensus(), hashBlock, true) && hashBlock != uint256()) { + if (GetTransaction(txHash, txTmp, Params().GetConsensus(), hashBlock, true) && hashBlock != uint256()) { LogPrint("instantsend", "CInstantSend::ResolveConflicts -- Done, %s is included in block %s\n", txHash.ToString(), hashBlock.ToString()); return true; } // Not in block yet, make sure all its inputs are still unspent - BOOST_FOREACH(const CTxIn& txin, txLockCandidate.txLockRequest.vin) { - CCoins coins; - if(!pcoinsTip->GetCoins(txin.prevout.hash, coins) || - (unsigned int)txin.prevout.n>=coins.vout.size() || - coins.vout[txin.prevout.n].IsNull()) { + for (const auto& txin : txLockCandidate.txLockRequest.tx->vin) { + Coin coin; + if (!GetUTXOCoin(txin.prevout, coin)) { // Not in UTXO anymore? A conflicting tx was mined while we were waiting for votes. - // Reprocess tip to make sure tx for this lock is included. - LogPrintf("CTxLockRequest::ResolveConflicts -- Failed to find UTXO %s - disconnecting tip...\n", txin.prevout.ToStringShort()); - if(!DisconnectBlocks(1)) { - return false; - } - // Recursively check at "new" old height. Conflicting tx should be rejected by AcceptToMemoryPool. - ResolveConflicts(txLockCandidate, nMaxBlocks - 1); - LogPrintf("CTxLockRequest::ResolveConflicts -- Failed to find UTXO %s - activating best chain...\n", txin.prevout.ToStringShort()); - // Activate best chain, block which includes conflicting tx should be rejected by ConnectBlock. - CValidationState state; - if(!ActivateBestChain(state, Params()) || !state.IsValid()) { - LogPrintf("CTxLockRequest::ResolveConflicts -- ActivateBestChain failed, txid=%s\n", txin.prevout.ToStringShort()); - return false; - } - LogPrintf("CTxLockRequest::ResolveConflicts -- Failed to find UTXO %s - fixed!\n", txin.prevout.ToStringShort()); + LogPrintf("CInstantSend::ResolveConflicts -- ERROR: Failed to find UTXO %s, can't complete Transaction Lock\n", txin.prevout.ToStringShort()); + return false; } } LogPrint("instantsend", "CInstantSend::ResolveConflicts -- Done, txid=%s\n", txHash.ToString()); @@ -560,42 +645,38 @@ bool CInstantSend::ResolveConflicts(const CTxLockCandidate& txLockCandidate, int return true; } -int64_t CInstantSend::GetAverageZnodeOrphanVoteTime() +int64_t CInstantSend::GetAverageMasternodeOrphanVoteTime() { LOCK(cs_instantsend); - // NOTE: should never actually call this function when mapZnodeOrphanVotes is empty - if(mapZnodeOrphanVotes.empty()) return 0; + // NOTE: should never actually call this function when mapMasternodeOrphanVotes is empty + if (mapMasternodeOrphanVotes.empty()) return 0; - std::map::iterator it = mapZnodeOrphanVotes.begin(); int64_t total = 0; - - while(it != mapZnodeOrphanVotes.end()) { - total+= it->second; - ++it; + for (const auto& pair : mapMasternodeOrphanVotes) { + total += pair.second; } - return total / mapZnodeOrphanVotes.size(); + return total / mapMasternodeOrphanVotes.size(); } void CInstantSend::CheckAndRemove() { - if(!pCurrentBlockIndex) return; + if (!masternodeSync.IsBlockchainSynced()) return; LOCK(cs_instantsend); std::map::iterator itLockCandidate = mapTxLockCandidates.begin(); // remove expired candidates - while(itLockCandidate != mapTxLockCandidates.end()) { + while (itLockCandidate != mapTxLockCandidates.end()) { CTxLockCandidate &txLockCandidate = itLockCandidate->second; uint256 txHash = txLockCandidate.GetHash(); - if(txLockCandidate.IsExpired(pCurrentBlockIndex->nHeight)) { + if (txLockCandidate.IsExpired(nCachedBlockHeight)) { LogPrintf("CInstantSend::CheckAndRemove -- Removing expired Transaction Lock Candidate: txid=%s\n", txHash.ToString()); - std::map::iterator itOutpointLock = txLockCandidate.mapOutPointLocks.begin(); - while(itOutpointLock != txLockCandidate.mapOutPointLocks.end()) { - mapLockedOutpoints.erase(itOutpointLock->first); - mapVotedOutpoints.erase(itOutpointLock->first); - ++itOutpointLock; + + for (const auto& pair : txLockCandidate.mapOutPointLocks) { + mapLockedOutpoints.erase(pair.first); + mapVotedOutpoints.erase(pair.first); } mapLockRequestAccepted.erase(txHash); mapLockRequestRejected.erase(txHash); @@ -607,22 +688,22 @@ void CInstantSend::CheckAndRemove() // remove expired votes std::map::iterator itVote = mapTxLockVotes.begin(); - while(itVote != mapTxLockVotes.end()) { - if(itVote->second.IsExpired(pCurrentBlockIndex->nHeight)) { - LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing expired vote: txid=%s znode=%s\n", - itVote->second.GetTxHash().ToString(), itVote->second.GetZnodeOutpoint().ToStringShort()); + while (itVote != mapTxLockVotes.end()) { + if (itVote->second.IsExpired(nCachedBlockHeight)) { + LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing expired vote: txid=%s masternode=%s\n", + itVote->second.GetTxHash().ToString(), itVote->second.GetMasternodeOutpoint().ToStringShort()); mapTxLockVotes.erase(itVote++); } else { ++itVote; } } - // remove expired orphan votes + // remove timed out orphan votes std::map::iterator itOrphanVote = mapTxLockVotesOrphan.begin(); - while(itOrphanVote != mapTxLockVotesOrphan.end()) { - if(GetTime() - itOrphanVote->second.GetTimeCreated() > ORPHAN_VOTE_SECONDS) { - LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing expired orphan vote: txid=%s znode=%s\n", - itOrphanVote->second.GetTxHash().ToString(), itOrphanVote->second.GetZnodeOutpoint().ToStringShort()); + while (itOrphanVote != mapTxLockVotesOrphan.end()) { + if (itOrphanVote->second.IsTimedOut()) { + LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing timed out orphan vote: txid=%s masternode=%s\n", + itOrphanVote->second.GetTxHash().ToString(), itOrphanVote->second.GetMasternodeOutpoint().ToStringShort()); mapTxLockVotes.erase(itOrphanVote->first); mapTxLockVotesOrphan.erase(itOrphanVote++); } else { @@ -630,21 +711,38 @@ void CInstantSend::CheckAndRemove() } } - // remove expired znode orphan votes (DOS protection) - std::map::iterator itZnodeOrphan = mapZnodeOrphanVotes.begin(); - while(itZnodeOrphan != mapZnodeOrphanVotes.end()) { - if(itZnodeOrphan->second < GetTime()) { - LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing expired orphan znode vote: znode=%s\n", - itZnodeOrphan->first.ToStringShort()); - mapZnodeOrphanVotes.erase(itZnodeOrphan++); + // remove invalid votes and votes for failed lock attempts + itVote = mapTxLockVotes.begin(); + while (itVote != mapTxLockVotes.end()) { + if (itVote->second.IsFailed()) { + LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing vote for failed lock attempt: txid=%s masternode=%s\n", + itVote->second.GetTxHash().ToString(), itVote->second.GetMasternodeOutpoint().ToStringShort()); + mapTxLockVotes.erase(itVote++); + } else { + ++itVote; + } + } + + // remove timed out masternode orphan votes (DOS protection) + std::map::iterator itMasternodeOrphan = mapMasternodeOrphanVotes.begin(); + while (itMasternodeOrphan != mapMasternodeOrphanVotes.end()) { + if (itMasternodeOrphan->second < GetTime()) { + LogPrint("instantsend", "CInstantSend::CheckAndRemove -- Removing timed out orphan masternode vote: masternode=%s\n", + itMasternodeOrphan->first.ToStringShort()); + mapMasternodeOrphanVotes.erase(itMasternodeOrphan++); } else { - ++itZnodeOrphan; + ++itMasternodeOrphan; } } + LogPrintf("CInstantSend::CheckAndRemove -- %s\n", ToString()); } bool CInstantSend::AlreadyHave(const uint256& hash) { + if (!llmq::IsOldInstantSendEnabled()) { + return true; + } + LOCK(cs_instantsend); return mapLockRequestAccepted.count(hash) || mapLockRequestRejected.count(hash) || @@ -654,13 +752,13 @@ bool CInstantSend::AlreadyHave(const uint256& hash) void CInstantSend::AcceptLockRequest(const CTxLockRequest& txLockRequest) { LOCK(cs_instantsend); - mapLockRequestAccepted.insert(make_pair(txLockRequest.GetHash(), txLockRequest)); + mapLockRequestAccepted.insert(std::make_pair(txLockRequest.GetHash(), txLockRequest)); } void CInstantSend::RejectLockRequest(const CTxLockRequest& txLockRequest) { LOCK(cs_instantsend); - mapLockRequestRejected.insert(make_pair(txLockRequest.GetHash(), txLockRequest)); + mapLockRequestRejected.insert(std::make_pair(txLockRequest.GetHash(), txLockRequest)); } bool CInstantSend::HasTxLockRequest(const uint256& txHash) @@ -671,10 +769,14 @@ bool CInstantSend::HasTxLockRequest(const uint256& txHash) bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLockRequestRet) { + if (!llmq::IsOldInstantSendEnabled()) { + return false; + } + LOCK(cs_instantsend); std::map::iterator it = mapTxLockCandidates.find(txHash); - if(it == mapTxLockCandidates.end()) return false; + if (it == mapTxLockCandidates.end() || !it->second.txLockRequest) return false; txLockRequestRet = it->second.txLockRequest; return true; @@ -682,47 +784,52 @@ bool CInstantSend::GetTxLockRequest(const uint256& txHash, CTxLockRequest& txLoc bool CInstantSend::GetTxLockVote(const uint256& hash, CTxLockVote& txLockVoteRet) { + if (!llmq::IsOldInstantSendEnabled()) { + return false; + } + LOCK(cs_instantsend); std::map::iterator it = mapTxLockVotes.find(hash); - if(it == mapTxLockVotes.end()) return false; + if (it == mapTxLockVotes.end()) return false; txLockVoteRet = it->second; return true; } -bool CInstantSend::IsInstantSendReadyToLock(const uint256& txHash) +void CInstantSend::Clear() { -// if(!fEnableInstantSend || fLargeWorkForkFound || fLargeWorkInvalidChainFound || -// !sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return false; - LOCK(cs_instantsend); - // There must be a successfully verified lock request - // and all outputs must be locked (i.e. have enough signatures) - std::map::iterator it = mapTxLockCandidates.find(txHash); - return it != mapTxLockCandidates.end() && it->second.IsAllOutPointsReady(); + + mapLockRequestAccepted.clear(); + mapLockRequestRejected.clear(); + mapTxLockVotes.clear(); + mapTxLockVotesOrphan.clear(); + mapTxLockCandidates.clear(); + mapVotedOutpoints.clear(); + mapLockedOutpoints.clear(); + mapMasternodeOrphanVotes.clear(); + nCachedBlockHeight = 0; } bool CInstantSend::IsLockedInstantSendTransaction(const uint256& txHash) { -// if(!fEnableInstantSend || fLargeWorkForkFound || fLargeWorkInvalidChainFound || -// !sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return false; + if (!fEnableInstantSend || GetfLargeWorkForkFound() || GetfLargeWorkInvalidChainFound() || + !sporkManager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) return false; LOCK(cs_instantsend); // there must be a lock candidate std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); - if(itLockCandidate == mapTxLockCandidates.end()) return false; + if (itLockCandidate == mapTxLockCandidates.end()) return false; // which should have outpoints - if(itLockCandidate->second.mapOutPointLocks.empty()) return false; + if (itLockCandidate->second.mapOutPointLocks.empty()) return false; // and all of these outputs must be included in mapLockedOutpoints with correct hash - std::map::iterator itOutpointLock = itLockCandidate->second.mapOutPointLocks.begin(); - while(itOutpointLock != itLockCandidate->second.mapOutPointLocks.end()) { + for (const auto& pair : itLockCandidate->second.mapOutPointLocks) { uint256 hashLocked; - if(!GetLockedOutPointTxHash(itOutpointLock->first, hashLocked) || hashLocked != txHash) return false; - ++itOutpointLock; + if (!GetLockedOutPointTxHash(pair.first, hashLocked) || hashLocked != txHash) return false; } return true; @@ -730,51 +837,51 @@ bool CInstantSend::IsLockedInstantSendTransaction(const uint256& txHash) int CInstantSend::GetTransactionLockSignatures(const uint256& txHash) { - if(!fEnableInstantSend) return -1; -// if(fLargeWorkForkFound || fLargeWorkInvalidChainFound) return -2; -// if(!sporkManager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED)) return -3; + if (!fEnableInstantSend) return -1; + if (GetfLargeWorkForkFound() || GetfLargeWorkInvalidChainFound()) return -2; + if (!llmq::IsOldInstantSendEnabled()) return -3; LOCK(cs_instantsend); std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); - if(itLockCandidate != mapTxLockCandidates.end()) { + if (itLockCandidate != mapTxLockCandidates.end()) { return itLockCandidate->second.CountVotes(); } return -1; } -bool CInstantSend::IsTxLockRequestTimedOut(const uint256& txHash) +bool CInstantSend::IsTxLockCandidateTimedOut(const uint256& txHash) { - if(!fEnableInstantSend) return false; + if (!fEnableInstantSend) return false; LOCK(cs_instantsend); std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); if (itLockCandidate != mapTxLockCandidates.end()) { return !itLockCandidate->second.IsAllOutPointsReady() && - itLockCandidate->second.txLockRequest.IsTimedOut(); + itLockCandidate->second.IsTimedOut(); } return false; } -void CInstantSend::Relay(const uint256& txHash) +void CInstantSend::Relay(const uint256& txHash, CConnman& connman) { LOCK(cs_instantsend); std::map::const_iterator itLockCandidate = mapTxLockCandidates.find(txHash); if (itLockCandidate != mapTxLockCandidates.end()) { - itLockCandidate->second.Relay(); + itLockCandidate->second.Relay(connman); } } void CInstantSend::UpdatedBlockTip(const CBlockIndex *pindex) { - pCurrentBlockIndex = pindex; + nCachedBlockHeight = pindex->nHeight; } -void CInstantSend::SyncTransaction(const CTransaction& tx, const CBlock* pblock) +void CInstantSend::SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock) { // Update lock candidates and votes if corresponding tx confirmed // or went from confirmed to 0-confirmed or conflicted. @@ -785,139 +892,118 @@ void CInstantSend::SyncTransaction(const CTransaction& tx, const CBlock* pblock) uint256 txHash = tx.GetHash(); - // When tx is 0-confirmed or conflicted, pblock is NULL and nHeightNew should be set to -1 - CBlockIndex* pblockindex = pblock ? mapBlockIndex[pblock->GetHash()] : NULL; - int nHeightNew = pblockindex ? pblockindex->nHeight : -1; + // When tx is 0-confirmed or conflicted, posInBlock is SYNC_TRANSACTION_NOT_IN_BLOCK and nHeightNew should be set to -1 + int nHeightNew = posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK ? -1 : pindex->nHeight; LogPrint("instantsend", "CInstantSend::SyncTransaction -- txid=%s nHeightNew=%d\n", txHash.ToString(), nHeightNew); // Check lock candidates std::map::iterator itLockCandidate = mapTxLockCandidates.find(txHash); - if(itLockCandidate != mapTxLockCandidates.end()) { + if (itLockCandidate != mapTxLockCandidates.end()) { LogPrint("instantsend", "CInstantSend::SyncTransaction -- txid=%s nHeightNew=%d lock candidate updated\n", txHash.ToString(), nHeightNew); itLockCandidate->second.SetConfirmedHeight(nHeightNew); // Loop through outpoint locks - std::map::iterator itOutpointLock = itLockCandidate->second.mapOutPointLocks.begin(); - while(itOutpointLock != itLockCandidate->second.mapOutPointLocks.end()) { + for (const auto& pair : itLockCandidate->second.mapOutPointLocks) { // Check corresponding lock votes - std::vector vVotes = itOutpointLock->second.GetVotes(); - std::vector::iterator itVote = vVotes.begin(); - std::map::iterator it; - while(itVote != vVotes.end()) { - uint256 nVoteHash = itVote->GetHash(); + for (const auto& vote : pair.second.GetVotes()) { + uint256 nVoteHash = vote.GetHash(); LogPrint("instantsend", "CInstantSend::SyncTransaction -- txid=%s nHeightNew=%d vote %s updated\n", txHash.ToString(), nHeightNew, nVoteHash.ToString()); - it = mapTxLockVotes.find(nVoteHash); - if(it != mapTxLockVotes.end()) { + const auto& it = mapTxLockVotes.find(nVoteHash); + if (it != mapTxLockVotes.end()) { it->second.SetConfirmedHeight(nHeightNew); } - ++itVote; } - ++itOutpointLock; } } // check orphan votes - std::map::iterator itOrphanVote = mapTxLockVotesOrphan.begin(); - while(itOrphanVote != mapTxLockVotesOrphan.end()) { - if(itOrphanVote->second.GetTxHash() == txHash) { + for (const auto& pair : mapTxLockVotesOrphan) { + if (pair.second.GetTxHash() == txHash) { LogPrint("instantsend", "CInstantSend::SyncTransaction -- txid=%s nHeightNew=%d vote %s updated\n", - txHash.ToString(), nHeightNew, itOrphanVote->first.ToString()); - mapTxLockVotes[itOrphanVote->first].SetConfirmedHeight(nHeightNew); + txHash.ToString(), nHeightNew, pair.first.ToString()); + mapTxLockVotes[pair.first].SetConfirmedHeight(nHeightNew); } - ++itOrphanVote; } } +std::string CInstantSend::ToString() const +{ + LOCK(cs_instantsend); + return strprintf("Lock Candidates: %llu, Votes %llu", mapTxLockCandidates.size(), mapTxLockVotes.size()); +} + +void CInstantSend::DoMaintenance() +{ + if (ShutdownRequested()) return; + + CheckAndRemove(); +} + +bool CInstantSend::CanAutoLock() +{ + if (!isAutoLockBip9Active || !llmq::IsOldInstantSendEnabled()) { + return false; + } + if (!sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS)) { + return false; + } + return (mempool.UsedMemoryShare() < AUTO_IX_MEMPOOL_THRESHOLD); +} + // // CTxLockRequest // -bool CTxLockRequest::IsValid(bool fRequireUnspent) const +bool CTxLockRequest::IsValid() const { - if(vout.size() < 1) return false; + if (tx->vout.size() < 1) return false; - if(vin.size() > WARN_MANY_INPUTS) { + if (tx->vin.size() > WARN_MANY_INPUTS) { LogPrint("instantsend", "CTxLockRequest::IsValid -- WARNING: Too many inputs: tx=%s", ToString()); } - LOCK(cs_main); - if(!CheckFinalTx(*this)) { + AssertLockHeld(cs_main); + if (!CheckFinalTx(*tx)) { LogPrint("instantsend", "CTxLockRequest::IsValid -- Transaction is not final: tx=%s", ToString()); return false; } CAmount nValueIn = 0; - CAmount nValueOut = 0; - BOOST_FOREACH(const CTxOut& txout, vout) { - // InstantSend supports normal scripts and unspendable (i.e. data) scripts. - // TODO: Look into other script types that are normal and can be included - if(!txout.scriptPubKey.IsNormalPaymentScript() && !txout.scriptPubKey.IsUnspendable()) { - LogPrint("instantsend", "CTxLockRequest::IsValid -- Invalid Script %s", ToString()); - return false; - } - nValueOut += txout.nValue; - } + int nInstantSendConfirmationsRequired = Params().GetConsensus().nInstantSendConfirmationsRequired; - BOOST_FOREACH(const CTxIn& txin, vin) { + for (const auto& txin : tx->vin) { - CCoins coins; - int nPrevoutHeight = 0; - CAmount nValue = 0; + Coin coin; - if(!pcoinsTip->GetCoins(txin.prevout.hash, coins) || - (unsigned int)txin.prevout.n>=coins.vout.size() || - coins.vout[txin.prevout.n].IsNull()) { + if (!GetUTXOCoin(txin.prevout, coin)) { LogPrint("instantsend", "CTxLockRequest::IsValid -- Failed to find UTXO %s\n", txin.prevout.ToStringShort()); - // Normally above sould be enough, but in case we are reprocessing this because of - // a lot of legit orphan votes we should also check already spent outpoints. - if(fRequireUnspent) return false; - CTransactionRef txOutpointCreated; - uint256 nHashOutpointConfirmed; - if(!GetTransaction(txin.prevout.hash, txOutpointCreated, Params().GetConsensus(), nHashOutpointConfirmed, true) || nHashOutpointConfirmed == uint256()) { - LogPrint("instantsend", "CTxLockRequest::IsValid -- Failed to find outpoint %s\n", txin.prevout.ToStringShort()); - return false; - } - if(txin.prevout.n >= txOutpointCreated->vout.size()) { - LogPrint("instantsend", "CTxLockRequest::IsValid -- Outpoint %s is out of bounds, size() = %lld\n", - txin.prevout.ToStringShort(), txOutpointCreated->vout.size()); - return false; - } - BlockMap::iterator mi = mapBlockIndex.find(nHashOutpointConfirmed); - if(mi == mapBlockIndex.end() || !mi->second) { - // shouldn't happen - LogPrint("instantsend", "CTxLockRequest::IsValid -- Failed to find block %s for outpoint %s\n", - nHashOutpointConfirmed.ToString(), txin.prevout.ToStringShort()); - return false; - } - nPrevoutHeight = mi->second->nHeight; - nValue = txOutpointCreated->vout[txin.prevout.n].nValue; - } else { - nPrevoutHeight = coins.nHeight; - nValue = coins.vout[txin.prevout.n].nValue; + return false; } - int nTxAge = chainActive.Height() - nPrevoutHeight + 1; + int nTxAge = chainActive.Height() - coin.nHeight + 1; // 1 less than the "send IX" gui requires, in case of a block propagating the network at the time - int nConfirmationsRequired = INSTANTSEND_CONFIRMATIONS_REQUIRED - 1; + int nConfirmationsRequired = nInstantSendConfirmationsRequired - 1; - if(nTxAge < nConfirmationsRequired) { + if (nTxAge < nConfirmationsRequired) { LogPrint("instantsend", "CTxLockRequest::IsValid -- outpoint %s too new: nTxAge=%d, nConfirmationsRequired=%d, txid=%s\n", txin.prevout.ToStringShort(), nTxAge, nConfirmationsRequired, GetHash().ToString()); return false; } - nValueIn += nValue; + nValueIn += coin.out.nValue; } -// if(nValueOut > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) { -// LogPrint("instantsend", "CTxLockRequest::IsValid -- Transaction value too high: nValueOut=%d, tx=%s", nValueOut, ToString()); -// return false; -// } + if (nValueIn > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) { + LogPrint("instantsend", "CTxLockRequest::IsValid -- Transaction value too high: nValueIn=%d, tx=%s", nValueIn, ToString()); + return false; + } - if(nValueIn - nValueOut < GetMinFee()) { + CAmount nValueOut = tx->GetValueOut(); + + if (nValueIn - nValueOut < GetMinFee(false)) { LogPrint("instantsend", "CTxLockRequest::IsValid -- did not include enough fees in transaction: fees=%d, tx=%s", nValueOut - nValueIn, ToString()); return false; } @@ -925,74 +1011,87 @@ bool CTxLockRequest::IsValid(bool fRequireUnspent) const return true; } -CAmount CTxLockRequest::GetMinFee() const +CAmount CTxLockRequest::GetMinFee(bool fForceMinFee) const { + if (!fForceMinFee && CInstantSend::CanAutoLock() && IsSimple()) { + return CAmount(); + } CAmount nMinFee = MIN_FEE; - return std::max(nMinFee, CAmount(vin.size() * nMinFee)); + return std::max(nMinFee, CAmount(tx->vin.size() * nMinFee)); } int CTxLockRequest::GetMaxSignatures() const { - return vin.size() * COutPointLock::SIGNATURES_TOTAL; + return tx->vin.size() * Params().GetConsensus().nInstantSendSigsTotal; } -bool CTxLockRequest::IsTimedOut() const +bool CTxLockRequest::IsSimple() const { - return GetTime() - nTimeCreated > TIMEOUT_SECONDS; + return (tx->vin.size() <= MAX_INPUTS_FOR_AUTO_IX); } // // CTxLockVote // -bool CTxLockVote::IsValid(CNode* pnode) const +bool CTxLockVote::IsValid(CNode* pnode, CConnman& connman) const { - if(!mnodeman.Has(CTxIn(outpointZnode))) { - LogPrint("instantsend", "CTxLockVote::IsValid -- Unknown znode %s\n", outpointZnode.ToStringShort()); - mnodeman.AskForMN(pnode, CTxIn(outpointZnode)); + auto mnList = deterministicMNManager->GetListAtChainTip(); + + if (!mnList.HasValidMNByCollateral(outpointMasternode)) { + LogPrint("instantsend", "CTxLockVote::IsValid -- Unknown masternode %s\n", outpointMasternode.ToStringShort()); return false; } - int nPrevoutHeight = GetUTXOHeight(outpoint); - if(nPrevoutHeight == -1) { - LogPrint("instantsend", "CTxLockVote::IsValid -- Failed to find UTXO %s\n", outpoint.ToStringShort()); - // Validating utxo set is not enough, votes can arrive after outpoint was already spent, - // if lock request was mined. We should process them too to count them later if they are legit. - CTransactionRef txOutpointCreated; - uint256 nHashOutpointConfirmed; - if(!GetTransaction(outpoint.hash, txOutpointCreated, Params().GetConsensus(), nHashOutpointConfirmed, true) || nHashOutpointConfirmed == uint256()) { - LogPrint("instantsend", "CTxLockVote::IsValid -- Failed to find outpoint %s\n", outpoint.ToStringShort()); - return false; - } - LOCK(cs_main); - BlockMap::iterator mi = mapBlockIndex.find(nHashOutpointConfirmed); - if(mi == mapBlockIndex.end() || !mi->second) { - // not on this chain? - LogPrint("instantsend", "CTxLockVote::IsValid -- Failed to find block %s for outpoint %s\n", nHashOutpointConfirmed.ToString(), outpoint.ToStringShort()); + // Verify that masternodeProTxHash belongs to the same MN referred by the collateral + // This is a leftover from the legacy non-deterministic MN list where we used the collateral to identify MNs + // TODO eventually remove the collateral from CTxLockVote + if (!masternodeProTxHash.IsNull()) { + auto dmn = mnList.GetValidMN(masternodeProTxHash); + if (!dmn || dmn->collateralOutpoint != outpointMasternode) { + LogPrint("instantsend", "CTxLockVote::IsValid -- invalid masternodeProTxHash %s\n", masternodeProTxHash.ToString()); return false; } - nPrevoutHeight = mi->second->nHeight; + } else { + LogPrint("instantsend", "CTxLockVote::IsValid -- missing masternodeProTxHash\n"); + return false; } - int nLockInputHeight = nPrevoutHeight + 4; + Coin coin; + if (!GetUTXOCoin(outpoint, coin)) { + LogPrint("instantsend", "CTxLockVote::IsValid -- Failed to find UTXO %s\n", outpoint.ToStringShort()); + return false; + } - int n = mnodeman.GetZnodeRank(CTxIn(outpointZnode), nLockInputHeight, MIN_INSTANTSEND_PROTO_VERSION); + int nLockInputHeight = coin.nHeight + Params().GetConsensus().nInstantSendConfirmationsRequired - 2; - if(n == -1) { + int nRank; + uint256 expectedQuorumModifierHash; + if (!CMasternodeUtils::GetMasternodeRank(outpointMasternode, nRank, expectedQuorumModifierHash, nLockInputHeight)) { //can be caused by past versions trying to vote with an invalid protocol - LogPrint("instantsend", "CTxLockVote::IsValid -- Outdated znode %s\n", outpointZnode.ToStringShort()); + LogPrint("instantsend", "CTxLockVote::IsValid -- Can't calculate rank for masternode %s\n", outpointMasternode.ToStringShort()); + return false; + } + if (!quorumModifierHash.IsNull()) { + if (quorumModifierHash != expectedQuorumModifierHash) { + LogPrint("instantsend", "CTxLockVote::IsValid -- invalid quorumModifierHash %s, expected %s\n", quorumModifierHash.ToString(), expectedQuorumModifierHash.ToString()); + return false; + } + } else { + LogPrint("instantsend", "CTxLockVote::IsValid -- missing quorumModifierHash\n"); return false; } - LogPrint("instantsend", "CTxLockVote::IsValid -- Znode %s, rank=%d\n", outpointZnode.ToStringShort(), n); - int nSignaturesTotal = COutPointLock::SIGNATURES_TOTAL; - if(n > nSignaturesTotal) { - LogPrint("instantsend", "CTxLockVote::IsValid -- Znode %s is not in the top %d (%d), vote hash=%s\n", - outpointZnode.ToStringShort(), nSignaturesTotal, n, GetHash().ToString()); + LogPrint("instantsend", "CTxLockVote::IsValid -- Masternode %s, rank=%d\n", outpointMasternode.ToStringShort(), nRank); + + int nSignaturesTotal = Params().GetConsensus().nInstantSendSigsTotal; + if (nRank > nSignaturesTotal) { + LogPrint("instantsend", "CTxLockVote::IsValid -- Masternode %s is not in the top %d (%d), vote hash=%s\n", + outpointMasternode.ToStringShort(), nSignaturesTotal, nRank, GetHash().ToString()); return false; } - if(!CheckSignature()) { + if (!CheckSignature()) { LogPrintf("CTxLockVote::IsValid -- Signature invalid\n"); return false; } @@ -1002,55 +1101,57 @@ bool CTxLockVote::IsValid(CNode* pnode) const uint256 CTxLockVote::GetHash() const { - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - ss << txHash; - ss << outpoint; - ss << outpointZnode; - return ss.GetHash(); + return SerializeHash(*this); +} + +uint256 CTxLockVote::GetSignatureHash() const +{ + return GetHash(); } bool CTxLockVote::CheckSignature() const { std::string strError; - std::string strMessage = txHash.ToString() + outpoint.ToStringShort(); - znode_info_t infoMn = mnodeman.GetZnodeInfo(CTxIn(outpointZnode)); - - if(!infoMn.fInfoValid) { - LogPrintf("CTxLockVote::CheckSignature -- Unknown Znode: znode=%s\n", outpointZnode.ToString()); + auto dmn = deterministicMNManager->GetListAtChainTip().GetValidMN(masternodeProTxHash); + if (!dmn) { + LogPrintf("CTxLockVote::CheckSignature -- Unknown Masternode: masternode=%s\n", masternodeProTxHash.ToString()); return false; } - if(!darkSendSigner.VerifyMessage(infoMn.pubKeyZnode, vchZnodeSignature, strMessage, strError)) { - LogPrintf("CTxLockVote::CheckSignature -- VerifyMessage() failed, error: %s\n", strError); + uint256 hash = GetSignatureHash(); + + CBLSSignature sig; + sig.SetBuf(vchMasternodeSignature); + if (!sig.IsValid() || !sig.VerifyInsecure(dmn->pdmnState->pubKeyOperator.Get(), hash)) { + LogPrintf("CTxLockVote::CheckSignature -- VerifyInsecure() failed\n"); return false; } - return true; } bool CTxLockVote::Sign() { - std::string strError; - std::string strMessage = txHash.ToString() + outpoint.ToStringShort(); - if(!darkSendSigner.SignMessage(strMessage, vchZnodeSignature, activeZnode.keyZnode)) { - LogPrintf("CTxLockVote::Sign -- SignMessage() failed\n"); - return false; - } + uint256 hash = GetSignatureHash(); - if(!darkSendSigner.VerifyMessage(activeZnode.pubKeyZnode, vchZnodeSignature, strMessage, strError)) { - LogPrintf("CTxLockVote::Sign -- VerifyMessage() failed, error: %s\n", strError); + CBLSSignature sig = activeMasternodeInfo.blsKeyOperator->Sign(hash); + if (!sig.IsValid()) { return false; } - + sig.GetBuf(vchMasternodeSignature); return true; } -void CTxLockVote::Relay() const +void CTxLockVote::Relay(CConnman& connman) const { -// CInv inv(MSG_TXLOCK_VOTE, GetHash()); -// RelayInv(inv); + CInv inv(MSG_TXLOCK_VOTE, GetHash()); + CTxLockRequest request; + if (instantsend.GetTxLockRequest(txHash, request) && request) { + connman.RelayInvFiltered(inv, *request.tx); + } else { + connman.RelayInvFiltered(inv, txHash); + } } bool CTxLockVote::IsExpired(int nHeight) const @@ -1059,40 +1160,48 @@ bool CTxLockVote::IsExpired(int nHeight) const return (nConfirmedHeight != -1) && (nHeight - nConfirmedHeight > Params().GetConsensus().nInstantSendKeepLock); } +bool CTxLockVote::IsTimedOut() const +{ + return GetTime() - nTimeCreated > INSTANTSEND_LOCK_TIMEOUT_SECONDS; +} + +bool CTxLockVote::IsFailed() const +{ + return (GetTime() - nTimeCreated > INSTANTSEND_FAILED_TIMEOUT_SECONDS) && !instantsend.IsLockedInstantSendTransaction(GetTxHash()); +} + // // COutPointLock // bool COutPointLock::AddVote(const CTxLockVote& vote) { - if(mapZnodeVotes.count(vote.GetZnodeOutpoint())) - return false; - mapZnodeVotes.insert(std::make_pair(vote.GetZnodeOutpoint(), vote)); - return true; + return mapMasternodeVotes.emplace(vote.GetMasternodeOutpoint(), vote).second; } std::vector COutPointLock::GetVotes() const { std::vector vRet; - std::map::const_iterator itVote = mapZnodeVotes.begin(); - while(itVote != mapZnodeVotes.end()) { - vRet.push_back(itVote->second); - ++itVote; + for (const auto& pair : mapMasternodeVotes) { + vRet.push_back(pair.second); } return vRet; } -bool COutPointLock::HasZnodeVoted(const COutPoint& outpointZnodeIn) const +bool COutPointLock::HasMasternodeVoted(const COutPoint& outpointMasternodeIn) const { - return mapZnodeVotes.count(outpointZnodeIn); + return mapMasternodeVotes.count(outpointMasternodeIn); } -void COutPointLock::Relay() const +bool COutPointLock::IsReady() const { - std::map::const_iterator itVote = mapZnodeVotes.begin(); - while(itVote != mapZnodeVotes.end()) { - itVote->second.Relay(); - ++itVote; + return !fAttacked && CountVotes() >= Params().GetConsensus().nInstantSendSigsRequired; +} + +void COutPointLock::Relay(CConnman& connman) const +{ + for (const auto& pair : mapMasternodeVotes) { + pair.second.Relay(connman); } } @@ -1102,43 +1211,45 @@ void COutPointLock::Relay() const void CTxLockCandidate::AddOutPointLock(const COutPoint& outpoint) { - mapOutPointLocks.insert(make_pair(outpoint, COutPointLock(outpoint))); + mapOutPointLocks.insert(std::make_pair(outpoint, COutPointLock(outpoint))); } +void CTxLockCandidate::MarkOutpointAsAttacked(const COutPoint& outpoint) +{ + std::map::iterator it = mapOutPointLocks.find(outpoint); + if (it != mapOutPointLocks.end()) + it->second.MarkAsAttacked(); +} bool CTxLockCandidate::AddVote(const CTxLockVote& vote) { std::map::iterator it = mapOutPointLocks.find(vote.GetOutpoint()); - if(it == mapOutPointLocks.end()) return false; + if (it == mapOutPointLocks.end()) return false; return it->second.AddVote(vote); } bool CTxLockCandidate::IsAllOutPointsReady() const { - if(mapOutPointLocks.empty()) return false; + if (mapOutPointLocks.empty()) return false; - std::map::const_iterator it = mapOutPointLocks.begin(); - while(it != mapOutPointLocks.end()) { - if(!it->second.IsReady()) return false; - ++it; + for (const auto& pair : mapOutPointLocks) { + if (!pair.second.IsReady()) return false; } return true; } -bool CTxLockCandidate::HasZnodeVoted(const COutPoint& outpointIn, const COutPoint& outpointZnodeIn) +bool CTxLockCandidate::HasMasternodeVoted(const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn) { std::map::iterator it = mapOutPointLocks.find(outpointIn); - return it !=mapOutPointLocks.end() && it->second.HasZnodeVoted(outpointZnodeIn); + return it !=mapOutPointLocks.end() && it->second.HasMasternodeVoted(outpointMasternodeIn); } int CTxLockCandidate::CountVotes() const { // Note: do NOT use vote count to figure out if tx is locked, use IsAllOutPointsReady() instead int nCountVotes = 0; - std::map::const_iterator it = mapOutPointLocks.begin(); - while(it != mapOutPointLocks.end()) { - nCountVotes += it->second.CountVotes(); - ++it; + for (const auto& pair : mapOutPointLocks) { + nCountVotes += pair.second.CountVotes(); } return nCountVotes; } @@ -1149,12 +1260,15 @@ bool CTxLockCandidate::IsExpired(int nHeight) const return (nConfirmedHeight != -1) && (nHeight - nConfirmedHeight > Params().GetConsensus().nInstantSendKeepLock); } -void CTxLockCandidate::Relay() const +bool CTxLockCandidate::IsTimedOut() const +{ + return GetTime() - nTimeCreated > INSTANTSEND_LOCK_TIMEOUT_SECONDS; +} + +void CTxLockCandidate::Relay(CConnman& connman) const { - g_connman->RelayTransaction(txLockRequest); - std::map::const_iterator itOutpointLock = mapOutPointLocks.begin(); - while(itOutpointLock != mapOutPointLocks.end()) { - itOutpointLock->second.Relay(); - ++itOutpointLock; + connman.RelayTransaction(*txLockRequest.tx); + for (const auto& pair : mapOutPointLocks) { + pair.second.Relay(connman); } } diff --git a/src/instantx.h b/src/instantx.h index 60589294c6..9fef88e052 100644 --- a/src/instantx.h +++ b/src/instantx.h @@ -1,12 +1,15 @@ -// Copyright (c) 2014-2017 The Dash Core developers +// Copyright (c) 2014-2019 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef INSTANTX_H #define INSTANTX_H +#include "chain.h" #include "net.h" #include "primitives/transaction.h" +#include "evo/deterministicmns.h" + class CTxLockVote; class COutPointLock; class CTxLockRequest; @@ -16,69 +19,110 @@ class CInstantSend; extern CInstantSend instantsend; /* - At 15 signatures, 1/2 of the znode network can be owned by - one party without comprimising the security of InstantSend + At 15 signatures, 1/2 of the masternode network can be owned by + one party without compromising the security of InstantSend (1000/2150.0)**10 = 0.00047382219560689856 (1000/2900.0)**10 = 2.3769498616783657e-05 ### getting 5 of 10 signatures w/ 1000 nodes of 2900 (1000/2900.0)**5 = 0.004875397277841433 */ -static const int INSTANTSEND_CONFIRMATIONS_REQUIRED = 6; -static const int DEFAULT_INSTANTSEND_DEPTH = 5; -static const int MIN_INSTANTSEND_PROTO_VERSION = 70206; +static const int MIN_INSTANTSEND_PROTO_VERSION = 70213; + +/// For how long we are going to accept votes/locks +/// after we saw the first one for a specific transaction +static const int INSTANTSEND_LOCK_TIMEOUT_SECONDS = 15; +/// For how long we are going to keep invalid votes and votes for failed lock attempts, +/// must be greater than INSTANTSEND_LOCK_TIMEOUT_SECONDS +static const int INSTANTSEND_FAILED_TIMEOUT_SECONDS = 60; extern bool fEnableInstantSend; -extern int nInstantSendDepth; -extern int nCompleteTXLocks; +/** + * Manages InstantSend. Processes lock requests, candidates, and votes. + */ class CInstantSend { +public: + /// Automatic locks of "simple" transactions are only allowed + /// when mempool usage is lower than this threshold + static const double AUTO_IX_MEMPOOL_THRESHOLD; private: - static const int ORPHAN_VOTE_SECONDS = 60; + static const std::string SERIALIZATION_VERSION_STRING; - // Keep track of current block index - const CBlockIndex *pCurrentBlockIndex; + // Keep track of current block height + int nCachedBlockHeight; // maps for AlreadyHave - std::map mapLockRequestAccepted; // tx hash - tx - std::map mapLockRequestRejected; // tx hash - tx - std::map mapTxLockVotes; // vote hash - vote - std::map mapTxLockVotesOrphan; // vote hash - vote + std::map mapLockRequestAccepted; ///< Tx hash - Tx + std::map mapLockRequestRejected; ///< Tx hash - Tx + std::map mapTxLockVotes; ///< Vote hash - Vote + std::map mapTxLockVotesOrphan; ///< Vote hash - Vote - std::map mapTxLockCandidates; // tx hash - lock candidate + std::map mapTxLockCandidates; ///< Tx hash - Lock candidate - std::map > mapVotedOutpoints; // utxo - tx hash set - std::map mapLockedOutpoints; // utxo - tx hash + std::map > mapVotedOutpoints; ///< UTXO - Tx hash set + std::map mapLockedOutpoints; ///< UTXO - Tx hash - //track znodes who voted with no txreq (for DOS protection) - std::map mapZnodeOrphanVotes; // mn outpoint - time + /// Track masternodes who voted with no txlockrequest (for DOS protection) + std::map mapMasternodeOrphanVotes; ///< MN outpoint - Time bool CreateTxLockCandidate(const CTxLockRequest& txLockRequest); - void Vote(CTxLockCandidate& txLockCandidate); + void CreateEmptyTxLockCandidate(const uint256& txHash); + void Vote(CTxLockCandidate& txLockCandidate, CConnman& connman); + + /// Process consensus vote message + bool ProcessNewTxLockVote(CNode* pfrom, const CTxLockVote& vote, CConnman& connman); - //process consensus vote message - bool ProcessTxLockVote(CNode* pfrom, CTxLockVote& vote); + void UpdateVotedOutpoints(const CTxLockVote& vote, CTxLockCandidate& txLockCandidate); + bool ProcessOrphanTxLockVote(const CTxLockVote& vote); void ProcessOrphanTxLockVotes(); - bool IsEnoughOrphanVotesForTx(const CTxLockRequest& txLockRequest); - bool IsEnoughOrphanVotesForTxAndOutPoint(const uint256& txHash, const COutPoint& outpoint); - int64_t GetAverageZnodeOrphanVoteTime(); + int64_t GetAverageMasternodeOrphanVoteTime(); void TryToFinalizeLockCandidate(const CTxLockCandidate& txLockCandidate); void LockTransactionInputs(const CTxLockCandidate& txLockCandidate); - //update UI and notify external script if any + /// Update UI and notify external script if any void UpdateLockedTransaction(const CTxLockCandidate& txLockCandidate); - bool ResolveConflicts(const CTxLockCandidate& txLockCandidate, int nMaxBlocks); - - bool IsInstantSendReadyToLock(const uint256 &txHash); + bool ResolveConflicts(const CTxLockCandidate& txLockCandidate); public: - CCriticalSection cs_instantsend; + mutable CCriticalSection cs_instantsend; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + std::string strVersion; + if(ser_action.ForRead()) { + READWRITE(strVersion); + } + else { + strVersion = SERIALIZATION_VERSION_STRING; + READWRITE(strVersion); + } + + READWRITE(mapLockRequestAccepted); + READWRITE(mapLockRequestRejected); + READWRITE(mapTxLockVotes); + READWRITE(mapTxLockVotesOrphan); + READWRITE(mapTxLockCandidates); + READWRITE(mapVotedOutpoints); + READWRITE(mapLockedOutpoints); + READWRITE(mapMasternodeOrphanVotes); + READWRITE(nCachedBlockHeight); + + if(ser_action.ForRead() && (strVersion != SERIALIZATION_VERSION_STRING)) { + Clear(); + } + } + + void Clear(); - void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); - bool ProcessTxLockRequest(const CTxLockRequest& txLockRequest); + bool ProcessTxLockRequest(const CTxLockRequest& txLockRequest, CConnman& connman); + void Vote(const uint256& txHash, CConnman& connman); bool AlreadyHave(const uint256& hash); @@ -91,80 +135,132 @@ class CInstantSend bool GetLockedOutPointTxHash(const COutPoint& outpoint, uint256& hashRet); - // verify if transaction is currently locked + /// Verify if transaction is currently locked bool IsLockedInstantSendTransaction(const uint256& txHash); - // get the actual uber og accepted lock signatures + /// Get the actual number of accepted lock signatures int GetTransactionLockSignatures(const uint256& txHash); - // remove expired entries from maps + /// Remove expired entries from maps void CheckAndRemove(); - // verify if transaction lock timed out - bool IsTxLockRequestTimedOut(const uint256& txHash); + /// Verify if transaction lock timed out + bool IsTxLockCandidateTimedOut(const uint256& txHash); - void Relay(const uint256& txHash); + void Relay(const uint256& txHash, CConnman& connman); void UpdatedBlockTip(const CBlockIndex *pindex); - void SyncTransaction(const CTransaction& tx, const CBlock* pblock); + void SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock); + + std::string ToString() const; + + void DoMaintenance(); + + /// checks if we can automatically lock "simple" transactions + static bool CanAutoLock(); + + /// flag of the AutoLock Bip9 activation + static std::atomic isAutoLockBip9Active; }; -// TODO: make CTransactionRef member of the class -class CTxLockRequest : public CMutableTransaction +/** + * An InstantSend transaction lock request. + */ +class CTxLockRequest { private: - static const int TIMEOUT_SECONDS = 5 * 60; - static const CAmount MIN_FEE = 0.001 * COIN; - - int64_t nTimeCreated; + static const CAmount MIN_FEE = 0.0001 * COIN; + /// If transaction has less or equal inputs than MAX_INPUTS_FOR_AUTO_IX, + /// it will be automatically locked + static const int MAX_INPUTS_FOR_AUTO_IX = 4; public: + /// Warn for a large number of inputs to an IS tx - fees could be substantial + /// and the number txlvote responses requested large (10 * # of inputs) static const int WARN_MANY_INPUTS = 100; - CTxLockRequest() : - CMutableTransaction(), - nTimeCreated(GetTime()) - {} - CTxLockRequest(const CTransaction& tx) : - CMutableTransaction(tx), - nTimeCreated(GetTime()) - {} + CTransactionRef tx; - CTxLockRequest(const deserialize_type &deserialize, CDataStream &s) : - CMutableTransaction(deserialize, s), - nTimeCreated(GetTime()) - {} + CTxLockRequest() : tx(MakeTransactionRef()) {} + CTxLockRequest(const CTransaction& _tx) : tx(MakeTransactionRef(_tx)) {}; + CTxLockRequest(const CTransactionRef& _tx) : tx(_tx) {}; - bool IsValid(bool fRequireUnspent = true) const; - CAmount GetMinFee() const; + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(tx); + } + + bool IsValid() const; + CAmount GetMinFee(bool fForceMinFee) const; int GetMaxSignatures() const; - bool IsTimedOut() const; + + // checks if related transaction is "simple" to lock it automatically + bool IsSimple() const; + + const uint256 &GetHash() const { + return tx->GetHash(); + } + + std::string ToString() const { + return tx->ToString(); + } + + friend bool operator==(const CTxLockRequest& a, const CTxLockRequest& b) + { + return *a.tx == *b.tx; + } + + friend bool operator!=(const CTxLockRequest& a, const CTxLockRequest& b) + { + return *a.tx != *b.tx; + } + + explicit operator bool() const + { + return *this != CTxLockRequest(); + } }; +/** + * An InstantSend transaction lock vote. Sent by a masternode in response to a + * transaction lock request (ix message) to indicate the transaction input can + * be locked. Contains the proposed transaction's hash and the outpoint being + * locked along with the masternodes outpoint and signature. + * @see CTxLockRequest + */ class CTxLockVote { private: uint256 txHash; COutPoint outpoint; - COutPoint outpointZnode; - std::vector vchZnodeSignature; + // TODO remove this member (not needed anymore after DIP3 has been deployed) + COutPoint outpointMasternode; + uint256 quorumModifierHash; + uint256 masternodeProTxHash; + std::vector vchMasternodeSignature; // local memory only - int nConfirmedHeight; // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1 + int nConfirmedHeight; ///< When corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1 int64_t nTimeCreated; public: CTxLockVote() : txHash(), outpoint(), - outpointZnode(), - vchZnodeSignature(), + outpointMasternode(), + quorumModifierHash(), + masternodeProTxHash(), + vchMasternodeSignature(), nConfirmedHeight(-1), nTimeCreated(GetTime()) {} - CTxLockVote(const uint256& txHashIn, const COutPoint& outpointIn, const COutPoint& outpointZnodeIn) : + CTxLockVote(const uint256& txHashIn, const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn, const uint256& quorumModifierHashIn, const uint256& masternodeProTxHashIn) : txHash(txHashIn), outpoint(outpointIn), - outpointZnode(outpointZnodeIn), - vchZnodeSignature(), + outpointMasternode(outpointMasternodeIn), + quorumModifierHash(quorumModifierHashIn), + masternodeProTxHash(masternodeProTxHashIn), + vchMasternodeSignature(), nConfirmedHeight(-1), nTimeCreated(GetTime()) {} @@ -175,61 +271,90 @@ class CTxLockVote inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(txHash); READWRITE(outpoint); - READWRITE(outpointZnode); - READWRITE(vchZnodeSignature); + READWRITE(outpointMasternode); + READWRITE(quorumModifierHash); + READWRITE(masternodeProTxHash); + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(vchMasternodeSignature); + } } uint256 GetHash() const; + uint256 GetSignatureHash() const; uint256 GetTxHash() const { return txHash; } COutPoint GetOutpoint() const { return outpoint; } - COutPoint GetZnodeOutpoint() const { return outpointZnode; } - int64_t GetTimeCreated() const { return nTimeCreated; } + COutPoint GetMasternodeOutpoint() const { return outpointMasternode; } - bool IsValid(CNode* pnode) const; + bool IsValid(CNode* pnode, CConnman& connman) const; void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; } bool IsExpired(int nHeight) const; + bool IsTimedOut() const; + bool IsFailed() const; bool Sign(); bool CheckSignature() const; - void Relay() const; + void Relay(CConnman& connman) const; }; +/** + * An InstantSend OutpointLock. + */ class COutPointLock { private: - COutPoint outpoint; // utxo - std::map mapZnodeVotes; // znode outpoint - vote + COutPoint outpoint; ///< UTXO + std::map mapMasternodeVotes; ///< Masternode outpoint - vote + bool fAttacked = false; public: - static const int SIGNATURES_REQUIRED = 6; - static const int SIGNATURES_TOTAL = 10; + COutPointLock() {} COutPointLock(const COutPoint& outpointIn) : outpoint(outpointIn), - mapZnodeVotes() + mapMasternodeVotes() {} COutPoint GetOutpoint() const { return outpoint; } + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(outpoint); + READWRITE(mapMasternodeVotes); + READWRITE(fAttacked); + } + bool AddVote(const CTxLockVote& vote); std::vector GetVotes() const; - bool HasZnodeVoted(const COutPoint& outpointZnodeIn) const; - int CountVotes() const { return mapZnodeVotes.size(); } - bool IsReady() const { return CountVotes() >= SIGNATURES_REQUIRED; } + bool HasMasternodeVoted(const COutPoint& outpointMasternodeIn) const; + int CountVotes() const { return fAttacked ? 0 : mapMasternodeVotes.size(); } + bool IsReady() const; + void MarkAsAttacked() { fAttacked = true; } - void Relay() const; + void Relay(CConnman& connman) const; }; +/** + * An InstantSend transaction lock candidate. + */ class CTxLockCandidate { private: - int nConfirmedHeight; // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is -1 + int nConfirmedHeight; /// mapOutPointLocks; + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(txLockRequest); + READWRITE(mapOutPointLocks); + READWRITE(nTimeCreated); + READWRITE(nConfirmedHeight); + } + uint256 GetHash() const { return txLockRequest.GetHash(); } void AddOutPointLock(const COutPoint& outpoint); + void MarkOutpointAsAttacked(const COutPoint& outpoint); bool AddVote(const CTxLockVote& vote); bool IsAllOutPointsReady() const; - bool HasZnodeVoted(const COutPoint& outpointIn, const COutPoint& outpointZnodeIn); + bool HasMasternodeVoted(const COutPoint& outpointIn, const COutPoint& outpointMasternodeIn); int CountVotes() const; void SetConfirmedHeight(int nConfirmedHeightIn) { nConfirmedHeight = nConfirmedHeightIn; } bool IsExpired(int nHeight) const; + bool IsTimedOut() const; - void Relay() const; + void Relay(CConnman& connman) const; }; #endif diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp new file mode 100644 index 0000000000..6e4ede3e41 --- /dev/null +++ b/src/masternode-payments.cpp @@ -0,0 +1,398 @@ +// Copyright (c) 2014-2019 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "activemasternode.h" +#include "consensus/validation.h" +#include "governance-classes.h" +#include "init.h" +#include "masternode-payments.h" +#include "masternode-sync.h" +#include "messagesigner.h" +#include "netfulfilledman.h" +#include "netmessagemaker.h" +#include "spork.h" +#include "util.h" +#include "validation.h" + +#include "evo/deterministicmns.h" + +#include + +CMasternodePayments mnpayments; + +bool IsOldBudgetBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet) { + const Consensus::Params& consensusParams = Params().GetConsensus(); + bool isBlockRewardValueMet = (block.vtx[0]->GetValueOut() <= blockReward); + + if (nBlockHeight < consensusParams.nBudgetPaymentsStartBlock) { + strErrorRet = strprintf("Incorrect block %d, old budgets are not activated yet", nBlockHeight); + return false; + } + + if (nBlockHeight >= consensusParams.nSuperblockStartBlock) { + strErrorRet = strprintf("Incorrect block %d, old budgets are no longer active", nBlockHeight); + return false; + } + + // we are still using budgets, but we have no data about them anymore, + // all we know is predefined budget cycle and window + + int nOffset = nBlockHeight % consensusParams.nBudgetPaymentsCycleBlocks; + if(nBlockHeight >= consensusParams.nBudgetPaymentsStartBlock && + nOffset < consensusParams.nBudgetPaymentsWindowBlocks) { + // NOTE: old budget system is disabled since 12.1 + if(masternodeSync.IsSynced()) { + // no old budget blocks should be accepted here on mainnet, + // testnet/devnet/regtest should produce regular blocks only + LogPrint("gobject", "%s -- WARNING: Client synced but old budget system is disabled, checking block value against block reward\n", __func__); + if(!isBlockRewardValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, old budgets are disabled", + nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); + } + return isBlockRewardValueMet; + } + // when not synced, rely on online nodes (all networks) + LogPrint("gobject", "%s -- WARNING: Skipping old budget block value checks, accepting block\n", __func__); + return true; + } + // LogPrint("gobject", "%s -- Block is not in budget cycle window, checking block value against block reward\n", __func__); + if(!isBlockRewardValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, block is not in old budget cycle window", + nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); + } + return isBlockRewardValueMet; +} + +/** +* IsBlockValueValid +* +* Determine if coinbase outgoing created money is the correct value +* +* Why is this needed? +* - In Dash some blocks are superblocks, which output much higher amounts of coins +* - Otherblocks are 10% lower in outgoing value, so in total, no extra coins are created +* - When non-superblocks are detected, the normal schedule should be maintained +*/ + +bool IsBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet) +{ + const Consensus::Params& consensusParams = Params().GetConsensus(); + bool isBlockRewardValueMet = (block.vtx[0]->GetValueOut() <= blockReward); + + strErrorRet = ""; + + if (nBlockHeight < consensusParams.nBudgetPaymentsStartBlock) { + // old budget system is not activated yet, just make sure we do not exceed the regular block reward + if(!isBlockRewardValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, old budgets are not activated yet", + nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); + } + return isBlockRewardValueMet; + } else if (nBlockHeight < consensusParams.nSuperblockStartBlock) { + // superblocks are not enabled yet, check if we can pass old budget rules + return IsOldBudgetBlockValueValid(block, nBlockHeight, blockReward, strErrorRet); + } + + if(fDebug) LogPrintf("block.vtx[0]->GetValueOut() %lld <= blockReward %lld\n", block.vtx[0]->GetValueOut(), blockReward); + + CAmount nSuperblockMaxValue = blockReward + CSuperblock::GetPaymentsLimit(nBlockHeight); + bool isSuperblockMaxValueMet = (block.vtx[0]->GetValueOut() <= nSuperblockMaxValue); + + LogPrint("gobject", "block.vtx[0]->GetValueOut() %lld <= nSuperblockMaxValue %lld\n", block.vtx[0]->GetValueOut(), nSuperblockMaxValue); + + if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) { + // can't possibly be a superblock, so lets just check for block reward limits + if (!isBlockRewardValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, only regular blocks are allowed at this height", + nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); + } + return isBlockRewardValueMet; + } + + // bail out in case superblock limits were exceeded + if (!isSuperblockMaxValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded superblock max value", + nBlockHeight, block.vtx[0]->GetValueOut(), nSuperblockMaxValue); + return false; + } + + if(!masternodeSync.IsSynced() || fLiteMode) { + if(fDebug) LogPrintf("%s -- WARNING: Not enough data, checked superblock max bounds only\n", __func__); + // not enough data for full checks but at least we know that the superblock limits were honored. + // We rely on the network to have followed the correct chain in this case + return true; + } + + // we are synced and possibly on a superblock now + + if (!sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) { + // should NOT allow superblocks at all, when superblocks are disabled + // revert to block reward limits in this case + LogPrint("gobject", "%s -- Superblocks are disabled, no superblocks allowed\n", __func__); + if(!isBlockRewardValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, superblocks are disabled", + nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); + } + return isBlockRewardValueMet; + } + + if (!CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { + // we are on a valid superblock height but a superblock was not triggered + // revert to block reward limits in this case + if(!isBlockRewardValueMet) { + strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, no triggered superblock detected", + nBlockHeight, block.vtx[0]->GetValueOut(), blockReward); + } + return isBlockRewardValueMet; + } + + // this actually also checks for correct payees and not only amount + if (!CSuperblockManager::IsValid(*block.vtx[0], nBlockHeight, blockReward)) { + // triggered but invalid? that's weird + LogPrintf("%s -- ERROR: Invalid superblock detected at height %d: %s", __func__, nBlockHeight, block.vtx[0]->ToString()); + // should NOT allow invalid superblocks, when superblocks are enabled + strErrorRet = strprintf("invalid superblock detected at height %d", nBlockHeight); + return false; + } + + // we got a valid superblock + return true; +} + +bool IsBlockPayeeValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) +{ + if(fLiteMode) { + //there is no budget data to use to check anything, let's just accept the longest chain + if(fDebug) LogPrintf("%s -- WARNING: Not enough data, skipping block payee checks\n", __func__); + return true; + } + + // we are still using budgets, but we have no data about them anymore, + // we can only check masternode payments + + const Consensus::Params& consensusParams = Params().GetConsensus(); + + if(nBlockHeight < consensusParams.nSuperblockStartBlock) { + // NOTE: old budget system is disabled since 12.1 and we should never enter this branch + // anymore when sync is finished (on mainnet). We have no old budget data but these blocks + // have tons of confirmations and can be safely accepted without payee verification + LogPrint("gobject", "%s -- WARNING: Client synced but old budget system is disabled, accepting any payee\n", __func__); + return true; + } + + // superblocks started + // SEE IF THIS IS A VALID SUPERBLOCK + + if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) { + if(CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { + if(CSuperblockManager::IsValid(txNew, nBlockHeight, blockReward)) { + LogPrint("gobject", "%s -- Valid superblock at height %d: %s", __func__, nBlockHeight, txNew.ToString()); + // continue validation, should also pay MN + } else { + LogPrintf("%s -- ERROR: Invalid superblock detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); + // should NOT allow such superblocks, when superblocks are enabled + return false; + } + } else { + LogPrint("gobject", "%s -- No triggered superblock detected at height %d\n", __func__, nBlockHeight); + } + } else { + // should NOT allow superblocks at all, when superblocks are disabled + LogPrint("gobject", "%s -- Superblocks are disabled, no superblocks allowed\n", __func__); + } + + // Check for correct masternode payment + if(mnpayments.IsTransactionValid(txNew, nBlockHeight, blockReward)) { + LogPrint("mnpayments", "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); + return true; + } + + LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); + return false; +} + +void FillBlockPayments(CMutableTransaction& txNew, int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet) +{ + // only create superblocks if spork is enabled AND if superblock is actually triggered + // (height should be validated inside) + if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED) && + CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { + LogPrint("gobject", "%s -- triggered superblock creation at height %d\n", __func__, nBlockHeight); + CSuperblockManager::GetSuperblockPayments(nBlockHeight, voutSuperblockPaymentsRet); + } + + if (!mnpayments.GetMasternodeTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) { + LogPrint("mnpayments", "%s -- no masternode to pay (MN list probably empty)\n", __func__); + } + + txNew.vout.insert(txNew.vout.end(), voutMasternodePaymentsRet.begin(), voutMasternodePaymentsRet.end()); + txNew.vout.insert(txNew.vout.end(), voutSuperblockPaymentsRet.begin(), voutSuperblockPaymentsRet.end()); + + std::string voutMasternodeStr; + for (const auto& txout : voutMasternodePaymentsRet) { + // subtract MN payment from miner reward + txNew.vout[0].nValue -= txout.nValue; + if (!voutMasternodeStr.empty()) + voutMasternodeStr += ","; + voutMasternodeStr += txout.ToString(); + } + + LogPrint("mnpayments", "%s -- nBlockHeight %d blockReward %lld voutMasternodePaymentsRet \"%s\" txNew %s", __func__, + nBlockHeight, blockReward, voutMasternodeStr, txNew.ToString()); +} + +std::string GetRequiredPaymentsString(int nBlockHeight, const CDeterministicMNCPtr &payee) +{ + std::string strPayee = "Unknown"; + if (payee) { + CTxDestination dest; + if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) + assert(false); + strPayee = CBitcoinAddress(dest).ToString(); + } + if (CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { + strPayee += ", " + CSuperblockManager::GetRequiredPaymentsString(nBlockHeight); + } + return strPayee; +} + +std::map GetRequiredPaymentsStrings(int nStartHeight, int nEndHeight) +{ + std::map mapPayments; + + if (nStartHeight < 1) { + nStartHeight = 1; + } + + LOCK(cs_main); + int nChainTipHeight = chainActive.Height(); + + bool doProjection = false; + for(int h = nStartHeight; h < nEndHeight; h++) { + if (h <= nChainTipHeight) { + auto payee = deterministicMNManager->GetListForBlock(chainActive[h - 1]).GetMNPayee(); + mapPayments.emplace(h, GetRequiredPaymentsString(h, payee)); + } else { + doProjection = true; + break; + } + } + if (doProjection) { + auto projection = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(nEndHeight - nChainTipHeight); + for (size_t i = 0; i < projection.size(); i++) { + auto payee = projection[i]; + int h = nChainTipHeight + 1 + i; + mapPayments.emplace(h, GetRequiredPaymentsString(h, payee)); + } + } + + return mapPayments; +} + +/** +* GetMasternodeTxOuts +* +* Get masternode payment tx outputs +*/ + +bool CMasternodePayments::GetMasternodeTxOuts(int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet) const +{ + // make sure it's not filled yet + voutMasternodePaymentsRet.clear(); + + if(!GetBlockTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) { + LogPrintf("CMasternodePayments::%s -- no payee (deterministic masternode list empty)\n", __func__); + return false; + } + + for (const auto& txout : voutMasternodePaymentsRet) { + CTxDestination address1; + ExtractDestination(txout.scriptPubKey, address1); + CBitcoinAddress address2(address1); + + LogPrintf("CMasternodePayments::%s -- Masternode payment %lld to %s\n", __func__, txout.nValue, address2.ToString()); + } + + return true; +} + +bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet) const +{ + voutMasternodePaymentsRet.clear(); + + CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockReward); + + const CBlockIndex* pindex; + { + LOCK(cs_main); + pindex = chainActive[nBlockHeight - 1]; + } + uint256 proTxHash; + auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee(); + if (!dmnPayee) { + return false; + } + + CAmount operatorReward = 0; + if (dmnPayee->nOperatorReward != 0 && dmnPayee->pdmnState->scriptOperatorPayout != CScript()) { + // This calculation might eventually turn out to result in 0 even if an operator reward percentage is given. + // This will however only happen in a few years when the block rewards drops very low. + operatorReward = (masternodeReward * dmnPayee->nOperatorReward) / 10000; + masternodeReward -= operatorReward; + } + + if (masternodeReward > 0) { + voutMasternodePaymentsRet.emplace_back(masternodeReward, dmnPayee->pdmnState->scriptPayout); + } + if (operatorReward > 0) { + voutMasternodePaymentsRet.emplace_back(operatorReward, dmnPayee->pdmnState->scriptOperatorPayout); + } + + return true; +} + +// Is this masternode scheduled to get paid soon? +// -- Only look ahead up to 8 blocks to allow for propagation of the latest 2 blocks of votes +bool CMasternodePayments::IsScheduled(const CDeterministicMNCPtr& dmnIn, int nNotBlockHeight) const +{ + auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8); + for (const auto &dmn : projectedPayees) { + if (dmn->proTxHash == dmnIn->proTxHash) { + return true; + } + } + return false; +} + +bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) const +{ + if (!deterministicMNManager->IsDIP3Enforced(nBlockHeight)) { + // can't verify historical blocks here + return true; + } + + std::vector voutMasternodePayments; + if (!GetBlockTxOuts(nBlockHeight, blockReward, voutMasternodePayments)) { + LogPrintf("CMasternodePayments::%s -- ERROR failed to get payees for block at height %s\n", __func__, nBlockHeight); + return true; + } + + for (const auto& txout : voutMasternodePayments) { + bool found = false; + for (const auto& txout2 : txNew.vout) { + if (txout == txout2) { + found = true; + break; + } + } + if (!found) { + CTxDestination dest; + if (!ExtractDestination(txout.scriptPubKey, dest)) + assert(false); + LogPrintf("CMasternodePayments::%s -- ERROR failed to find expected payee %s in block at height %s\n", __func__, CBitcoinAddress(dest).ToString(), nBlockHeight); + return false; + } + } + return true; +} diff --git a/src/masternode-payments.h b/src/masternode-payments.h new file mode 100644 index 0000000000..db6a7fd010 --- /dev/null +++ b/src/masternode-payments.h @@ -0,0 +1,41 @@ +// Copyright (c) 2014-2019 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MASTERNODE_PAYMENTS_H +#define MASTERNODE_PAYMENTS_H + +#include "util.h" +#include "core_io.h" +#include "key.h" +#include "net_processing.h" +#include "utilstrencodings.h" + +#include "evo/deterministicmns.h" + +class CMasternodePayments; + +/// TODO: all 4 functions do not belong here really, they should be refactored/moved somewhere (main.cpp ?) +bool IsBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet); +bool IsBlockPayeeValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward); +void FillBlockPayments(CMutableTransaction& txNew, int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet); +std::map GetRequiredPaymentsStrings(int nStartHeight, int nEndHeight); + +extern CMasternodePayments mnpayments; + +// +// Masternode Payments Class +// Keeps track of who should get paid for which blocks +// + +class CMasternodePayments +{ +public: + bool GetBlockTxOuts(int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet) const; + bool IsTransactionValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) const; + bool IsScheduled(const CDeterministicMNCPtr& dmn, int nNotBlockHeight) const; + + bool GetMasternodeTxOuts(int nBlockHeight, CAmount blockReward, std::vector& voutMasternodePaymentsRet) const; +}; + +#endif diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index 5f5744a3aa..79d4f34f24 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "activemasternode.h" -#include "governance.h" +//#include "governance.h" #include "init.h" #include "validation.h" #include "masternode-payments.h" @@ -64,10 +64,10 @@ void CMasternodeSync::SwitchToNextAsset(CConnman& connman) break; case(MASTERNODE_SYNC_WAITING): LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted); - nCurrentAsset = MASTERNODE_SYNC_GOVERNANCE; + nCurrentAsset = MASTERNODE_SYNC_FINISHED; LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName()); break; - case(MASTERNODE_SYNC_GOVERNANCE): +/* case(MASTERNODE_SYNC_GOVERNANCE): LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted); nCurrentAsset = MASTERNODE_SYNC_FINISHED; uiInterface.NotifyAdditionalDataSyncProgressChanged(1); @@ -77,7 +77,7 @@ void CMasternodeSync::SwitchToNextAsset(CConnman& connman) }); LogPrintf("CMasternodeSync::SwitchToNextAsset -- Sync has finished\n"); - break; + break;*/ } nTriedPeerCount = 0; nTimeAssetSyncStarted = GetTime(); @@ -146,7 +146,7 @@ void CMasternodeSync::ProcessTick(CConnman& connman) // gradually request the rest of the votes after sync finished if(IsSynced()) { std::vector vNodesCopy = connman.CopyNodeVector(CConnman::FullyConnectedOnly); - governance.RequestGovernanceObjectVotes(vNodesCopy, connman); + //governance.RequestGovernanceObjectVotes(vNodesCopy, connman); connman.ReleaseNodeVector(vNodesCopy); return; } @@ -166,7 +166,7 @@ void CMasternodeSync::ProcessTick(CConnman& connman) // they are temporary and should be considered unreliable for a sync process. // Inbound connection this early is most likely a "masternode" connection // initiated from another node, so skip it too. - if(pnode->fMasternode || (fMasternodeMode && pnode->fInbound)) continue; + if(pnode->fZnode || (fMasternodeMode && pnode->fInbound)) continue; // QUICK MODE (REGTEST ONLY!) if(Params().NetworkIDString() == CBaseChainParams::REGTEST) @@ -174,10 +174,10 @@ void CMasternodeSync::ProcessTick(CConnman& connman) if (nCurrentAsset == MASTERNODE_SYNC_WAITING) { connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS)); //get current network sporks SwitchToNextAsset(connman); - } else if (nCurrentAsset == MASTERNODE_SYNC_GOVERNANCE) { + } /*else if (nCurrentAsset == MASTERNODE_SYNC_GOVERNANCE) { SendGovernanceSyncRequest(pnode, connman); SwitchToNextAsset(connman); - } + }*/ connman.ReleaseNodeVector(vNodesCopy); return; } @@ -220,6 +220,7 @@ void CMasternodeSync::ProcessTick(CConnman& connman) // GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS + /* if(nCurrentAsset == MASTERNODE_SYNC_GOVERNANCE) { LogPrint("gobject", "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nCurrentAsset, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped); @@ -278,6 +279,7 @@ void CMasternodeSync::ProcessTick(CConnman& connman) connman.ReleaseNodeVector(vNodesCopy); return; //this will cause each peer to get one request each six seconds for the various assets we need } + */ } } // looped through all nodes, release them @@ -286,7 +288,7 @@ void CMasternodeSync::ProcessTick(CConnman& connman) void CMasternodeSync::SendGovernanceSyncRequest(CNode* pnode, CConnman& connman) { - CNetMsgMaker msgMaker(pnode->GetSendVersion()); +/* CNetMsgMaker msgMaker(pnode->GetSendVersion()); if(pnode->nVersion >= GOVERNANCE_FILTER_PROTO_VERSION) { CBloomFilter filter; @@ -297,6 +299,7 @@ void CMasternodeSync::SendGovernanceSyncRequest(CNode* pnode, CConnman& connman) else { connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256())); } +*/ } void CMasternodeSync::AcceptedBlockHeader(const CBlockIndex *pindexNew) diff --git a/src/masternode-utils.cpp b/src/masternode-utils.cpp new file mode 100644 index 0000000000..9d46050867 --- /dev/null +++ b/src/masternode-utils.cpp @@ -0,0 +1,108 @@ +// Copyright (c) 2014-2019 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "masternode-utils.h" + +#include "init.h" +#include "masternode-sync.h" +#ifdef ENABLE_WALLET +#include "privatesend-client.h" +#endif +#include "validation.h" + +struct CompareScoreMN +{ + bool operator()(const std::pair& t1, + const std::pair& t2) const + { + return (t1.first != t2.first) ? (t1.first < t2.first) : (t1.second->collateralOutpoint < t2.second->collateralOutpoint); + } +}; + +bool CMasternodeUtils::GetMasternodeScores(const uint256& nBlockHash, score_pair_vec_t& vecMasternodeScoresRet) +{ + vecMasternodeScoresRet.clear(); + + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto scores = mnList.CalculateScores(nBlockHash); + for (const auto& p : scores) { + vecMasternodeScoresRet.emplace_back(p.first, p.second); + } + + std::sort(vecMasternodeScoresRet.rbegin(), vecMasternodeScoresRet.rend(), CompareScoreMN()); + return !vecMasternodeScoresRet.empty(); +} + +bool CMasternodeUtils::GetMasternodeRank(const COutPoint& outpoint, int& nRankRet, uint256& blockHashRet, int nBlockHeight) +{ + nRankRet = -1; + + if (!masternodeSync.IsBlockchainSynced()) + return false; + + // make sure we know about this block + blockHashRet = uint256(); + if (!GetBlockHash(blockHashRet, nBlockHeight)) { + LogPrintf("CMasternodeUtils::%s -- ERROR: GetBlockHash() failed at nBlockHeight %d\n", __func__, nBlockHeight); + return false; + } + + score_pair_vec_t vecMasternodeScores; + if (!GetMasternodeScores(blockHashRet, vecMasternodeScores)) + return false; + + int nRank = 0; + for (const auto& scorePair : vecMasternodeScores) { + nRank++; + if(scorePair.second->collateralOutpoint == outpoint) { + nRankRet = nRank; + return true; + } + } + + return false; +} + + +void CMasternodeUtils::ProcessMasternodeConnections(CConnman& connman) +{ + std::vector vecDmns; // will be empty when no wallet +#ifdef ENABLE_WALLET + privateSendClient.GetMixingMasternodesInfo(vecDmns); +#endif // ENABLE_WALLET + + connman.ForEachNode(CConnman::AllNodes, [&](CNode* pnode) { + if (pnode->fMasternode && !connman.IsMasternodeQuorumNode(pnode)) { +#ifdef ENABLE_WALLET + bool fFound = false; + for (const auto& dmn : vecDmns) { + if (pnode->addr == dmn->pdmnState->addr) { + fFound = true; + break; + } + } + if (fFound) return; // do NOT disconnect mixing masternodes +#endif // ENABLE_WALLET + LogPrintf("Closing Masternode connection: peer=%d, addr=%s\n", pnode->id, pnode->addr.ToString()); + pnode->fDisconnect = true; + } + }); +} + +void CMasternodeUtils::DoMaintenance(CConnman& connman) +{ + if(fLiteMode) return; // disable all Dash specific functionality + + if(!masternodeSync.IsBlockchainSynced() || ShutdownRequested()) + return; + + static unsigned int nTick = 0; + + nTick++; + + if(nTick % 60 == 0) { + ProcessMasternodeConnections(connman); + } +} + diff --git a/src/masternode-utils.h b/src/masternode-utils.h new file mode 100644 index 0000000000..28d3395bd5 --- /dev/null +++ b/src/masternode-utils.h @@ -0,0 +1,28 @@ +// Copyright (c) 2014-2019 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MASTERNODE_UTILS_H +#define MASTERNODE_UTILS_H + +#include "evo/deterministicmns.h" + +class CConnman; + +class CMasternodeUtils +{ +public: + typedef std::pair score_pair_t; + typedef std::vector score_pair_vec_t; + typedef std::pair rank_pair_t; + typedef std::vector rank_pair_vec_t; + +public: + static bool GetMasternodeScores(const uint256& nBlockHash, score_pair_vec_t& vecMasternodeScoresRet); + static bool GetMasternodeRank(const COutPoint &outpoint, int& nRankRet, uint256& blockHashRet, int nBlockHeight = -1); + + static void ProcessMasternodeConnections(CConnman& connman); + static void DoMaintenance(CConnman &connman); +}; + +#endif//MASTERNODE_UTILS_H diff --git a/src/memusage.h b/src/memusage.h index 81e8702954..e66b149005 100644 --- a/src/memusage.h +++ b/src/memusage.h @@ -149,7 +149,7 @@ static inline size_t DynamicUsage(const std::shared_ptr& p) // Boost data structures template -struct boost_unordered_node : private X +struct unordered_node : private X { private: void* ptr; @@ -158,13 +158,25 @@ struct boost_unordered_node : private X template static inline size_t DynamicUsage(const boost::unordered_set& s) { - return MallocUsage(sizeof(boost_unordered_node)) * s.size() + MallocUsage(sizeof(void*) * s.bucket_count()); + return MallocUsage(sizeof(unordered_node)) * s.size() + MallocUsage(sizeof(void*) * s.bucket_count()); } template static inline size_t DynamicUsage(const boost::unordered_map& m) { - return MallocUsage(sizeof(boost_unordered_node >)) * m.size() + MallocUsage(sizeof(void*) * m.bucket_count()); + return MallocUsage(sizeof(unordered_node >)) * m.size() + MallocUsage(sizeof(void*) * m.bucket_count()); +} + +template +static inline size_t DynamicUsage(const std::unordered_set& s) +{ + return MallocUsage(sizeof(unordered_node)) * s.size() + MallocUsage(sizeof(void*) * s.bucket_count()); +} + +template +static inline size_t DynamicUsage(const std::unordered_map& m) +{ + return MallocUsage(sizeof(unordered_node >)) * m.size() + MallocUsage(sizeof(void*) * m.bucket_count()); } } diff --git a/src/net.h b/src/net.h index 0d3245c6f0..4129480656 100644 --- a/src/net.h +++ b/src/net.h @@ -195,6 +195,40 @@ class CConnman void PushMessage(CNode* pnode, CSerializedNetMsg&& msg, bool allowOptimisticSend = DEFAULT_ALLOW_OPTIMISTIC_SEND); + template + bool ForEachNodeContinueIf(const Condition& cond, Callable&& func) + { + LOCK(cs_vNodes); + for (auto&& node : vNodes) + if (cond(node)) + if(!func(node)) + return false; + return true; + }; + + template + bool ForEachNodeContinueIf(Callable&& func) + { + return ForEachNodeContinueIf(FullyConnectedOnly, func); + } + + template + bool ForEachNodeContinueIf(const Condition& cond, Callable&& func) const + { + LOCK(cs_vNodes); + for (const auto& node : vNodes) + if (cond(node)) + if(!func(node)) + return false; + return true; + }; + + template + bool ForEachNodeContinueIf(Callable&& func) const + { + return ForEachNodeContinueIf(FullyConnectedOnly, func); + } + template void ForEachNode(Callable&& func) { diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 01c92b444f..8d5060cd79 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2006,7 +2006,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr std::deque vWorkQueue; std::vector vEraseQueue; - std::shared_ptr ptxLockRequest; + CTxLockRequest txLockRequest; int nInvType = MSG_TX; CTransactionRef ptx; @@ -2014,9 +2014,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::TX) { vRecv >> ptx; } else if (strCommand == NetMsgType::TXLOCKREQUEST) { - vRecv >> ptxLockRequest; - ptx = MakeTransactionRef(*ptxLockRequest); + vRecv >> txLockRequest; + ptx = txLockRequest.tx; nInvType = MSG_TXLOCK_REQUEST; + /*if (llmq::IsNewInstantSendEnabled()) { + // the new system does not require explicit lock requests + // changing the inv type to MSG_TX also results in re-broadcasting the TX as normal TX + nInvType = MSG_TX; + }*/ } const CTransaction& tx = *ptx; @@ -2026,10 +2031,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Process custom logic, no matter if tx will be accepted to mempool later or not if (strCommand == NetMsgType::TXLOCKREQUEST) { - if (!instantsend.ProcessTxLockRequest(*ptxLockRequest)) { + /*if (!instantsend.ProcessTxLockRequest(*ptxLockRequest, g_connman)) { LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", ptxLockRequest->GetHash().ToString()); return false; - } + }*/ } LOCK(cs_main); @@ -2989,7 +2994,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr //probably one the extensions mnodeman.ProcessMessage(pfrom, command, vRecv); mnpayments.ProcessMessage(pfrom, command, vRecv); - instantsend.ProcessMessage(pfrom, command, vRecv); sporkManager.ProcessSpork(pfrom, command, vRecv); znodeSync.ProcessMessage(pfrom, command, vRecv); } else { diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 683637d227..c79d7259f6 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -8,6 +8,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "coins.h" +#include "core_io.h" #include "consensus/validation.h" #include "validation.h" #include "policy/policy.h" diff --git a/src/txdb.cpp b/src/txdb.cpp index 1f91de89f7..51dafe5e68 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -97,7 +97,7 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { batch.Write(DB_BEST_BLOCK, hashBlock); bool ret = db.WriteBatch(batch); - LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); + LogPrint("coindb", "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); return ret; } @@ -497,8 +497,8 @@ std::pair classifyAddress(txnouttype type, vectorvout[input.prevout.n]; + const Coin coin = view.AccessCoin(input.prevout); + const CTxOut &prevout = coin.out; txnouttype type; vector > addresses; @@ -751,6 +751,8 @@ class CCoins } }; +} + /** Upgrade the database from older formats. * * Currently implemented: from the per-tx utxo model (0.8..0.14.x) to per-txout. diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 04206486f7..0787bbab7d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -19,10 +19,10 @@ #include "version.h" CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, - int64_t _nTime, double _entryPriority, unsigned int _entryHeight, + int64_t _nTime, unsigned int _entryHeight, CAmount _inChainInputValue, bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp): - tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight), + tx(_tx), nFee(_nFee), nTime(_nTime), entryHeight(_entryHeight), inChainInputValue(_inChainInputValue), spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp) { @@ -51,16 +51,6 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other) *this = other; } -double -CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const -{ - double deltaPriority = ((double)(currentHeight-entryHeight)*inChainInputValue)/nModSize; - double dResult = entryPriority + deltaPriority; - if (dResult < 0) // This should only happen if it was called with a height below entry height - dResult = 0; - return dResult; -} - void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta) { nModFeesWithDescendants += newFeeDelta - feeDelta; @@ -496,7 +486,7 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC continue; } - const CTxOut &prevout = view.GetOutputFor(input); + const CTxOut &prevout = view.AccessCoin(input.prevout).out; if (prevout.scriptPubKey.IsPayToScriptHash()) { vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); CMempoolAddressDeltaKey key(AddressType::payToScriptHash, uint160(hashBytes), txhash, j, 1); @@ -575,7 +565,7 @@ void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCac continue; } - const CTxOut &prevout = view.GetOutputFor(input); + const CTxOut &prevout = view.AccessCoin(input.prevout).out; uint160 addressHash; AddressType addressType; @@ -841,8 +831,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const parentSigOpCost += it2->GetSigOpCost(); } } else { - const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash); - assert(coins && coins->IsAvailable(txin.prevout.n)); + assert(pcoins->HaveCoin(txin.prevout)); } // Check whether its inputs are marked in mapNextTx. auto it3 = mapNextTx.find(txin.prevout); @@ -1163,6 +1152,16 @@ size_t CTxMemPool::DynamicMemoryUsage() const { return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; } +double CTxMemPool::UsedMemoryShare() const +{ + // use 1000000 instead of real bytes number in megabyte because of + // this param is calculated in such way in other places (see AppInit + // function in src/init.cpp or mempoolInfoToJSON function in + // src/rpc/blockchain.cpp) + size_t maxmempool = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; + return double(DynamicMemoryUsage()) / maxmempool; +} + void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) { AssertLockHeld(cs); UpdateForRemoveFromMempool(stage, updateDescendants); diff --git a/src/txmempool.h b/src/txmempool.h index 1a4fa4217d..f3587dcfee 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -44,8 +44,6 @@ inline bool AllowFree(double dPriority) return dPriority > AllowFreeThreshold(); } -/** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */ -static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF; /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */ static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF; @@ -92,7 +90,6 @@ class CTxMemPoolEntry size_t nModSize; //!< ... and modified size for priority size_t nUsageSize; //!< ... and total memory usage int64_t nTime; //!< Local time when entering the mempool - double entryPriority; //!< Priority when entering the mempool unsigned int entryHeight; //!< Chain height when entering the mempool CAmount inChainInputValue; //!< Sum of all txin values that are already in blockchain bool spendsCoinbase; //!< keep track of transactions that spend a coinbase @@ -117,7 +114,7 @@ class CTxMemPoolEntry public: CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, - int64_t _nTime, double _entryPriority, unsigned int _entryHeight, + int64_t _nTime, unsigned int _entryHeight, CAmount _inChainInputValue, bool spendsCoinbase, int64_t nSigOpsCost, LockPoints lp); @@ -585,6 +582,7 @@ class CTxMemPool bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb); void queryHashes(std::vector& vtxid); bool isSpent(const COutPoint& outpoint); + void getTransactions(std::set& setTxid); unsigned int GetTransactionsUpdated() const; void AddTransactionsUpdated(unsigned int n); /** @@ -708,6 +706,8 @@ class CTxMemPool bool ReadFeeEstimates(CAutoFile& filein); size_t DynamicMemoryUsage() const; + // returns share of the used memory to maximum allowed memory + double UsedMemoryShare() const; boost::signals2::signal NotifyEntryAdded; boost::signals2::signal NotifyEntryRemoved; diff --git a/src/util.cpp b/src/util.cpp index 1cb715302e..9427312dd9 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -9,7 +9,9 @@ #include "util.h" +#include "support/allocators/secure.h" #include "chainparamsbase.h" +#include "ctpl.h" #include "random.h" #include "serialize.h" #include "sync.h" diff --git a/src/util.h b/src/util.h index 256097a96b..d7c493c50b 100644 --- a/src/util.h +++ b/src/util.h @@ -24,7 +24,9 @@ #include #include #include +#include #include + #include #include #include diff --git a/src/validation.cpp b/src/validation.cpp index e88a376fcc..9ff13e3c3f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -80,7 +80,7 @@ #endif bool AbortNode(const std::string& strMessage, const std::string& userMessage=""); -bool AbortNode(CValidationState& state, const std::string& strMessage, const std::string& userMessage=""); +bool AbortNode(CValidationState &state, const std::string& strMessage, const std::string& userMessage=""); /** * Global state @@ -506,7 +506,9 @@ unsigned int GetP2SHSigOpCount(const CTransaction &tx, const CCoinsViewCache &in unsigned int nSigOps = 0; for (unsigned int i = 0; i < tx.vin.size(); i++) { - const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]); + const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout); + assert(!coin.IsSpent()); + const CTxOut &prevout = coin.out; if (prevout.scriptPubKey.IsPayToScriptHash()) nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig); } @@ -526,7 +528,7 @@ int64_t GetTransactionSigOpCost(const CTransaction &tx, const CCoinsViewCache &i for (unsigned int i = 0; i < tx.vin.size(); i++) { - const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]); + const CTxOut &prevout = inputs.AccessCoin(tx.vin[i].prevout).out; nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, &tx.vin[i].scriptWitness, flags); } return nSigOps; @@ -631,9 +633,9 @@ void LimitMempoolSize(CTxMemPool &pool, size_t limit, unsigned long age) { if (expired != 0) LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired); - std::vector vNoSpendsRemaining; + std::vector vNoSpendsRemaining; pool.TrimToSize(limit, &vNoSpendsRemaining); - BOOST_FOREACH(const uint256 &removed, vNoSpendsRemaining) + BOOST_FOREACH(const COutPoint& removed, vNoSpendsRemaining) pcoinsTip->Uncache(removed); } @@ -864,10 +866,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C if (!tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint()) { // do all inputs exist? BOOST_FOREACH(const CTxIn txin, tx.vin) { - if (!pcoinsTip->HaveCoinsInCache(txin.prevout)) { + if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { coins_to_uncache.push_back(txin.prevout); } - if (!view.HaveCoins(txin.prevout)) { + if (!view.HaveCoin(txin.prevout)) { if (pfMissingInputs) { *pfMissingInputs = true; } @@ -917,23 +919,20 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C pool.ApplyDeltas(hash, nPriorityDummy, nModifiedFees); CAmount inChainInputValue = 0; - double dPriority = 0; bool fSpendsCoinbase = false; if (!tx.IsSigmaSpend()) { - dPriority = view.GetPriority(tx, chainActive.Height(), inChainInputValue); - // Keep track of transactions that spend a coinbase, which we re-scan // during reorgs to ensure COINBASE_MATURITY is still met. BOOST_FOREACH(const CTxIn &txin, tx.vin) { - const CCoins *coins = view.AccessCoins(txin.prevout.hash); - if (coins->IsCoinBase()) { + const Coin &coin = view.AccessCoin(txin.prevout); + if (coin.IsCoinBase()) { fSpendsCoinbase = true; break; } } } - CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, dPriority, chainActive.Height(), + CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, chainActive.Height(), inChainInputValue, fSpendsCoinbase, nSigOpsCost, lp); unsigned int nSize = entry.GetTxSize(); @@ -1233,13 +1232,12 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } else { LockPoints lp; - double dPriority = 0; double fSpendsCoinbase = false; CAmount inChainInputValue = 0; CAmount nFees = 0; int64_t nSigOpsCost = GetLegacySigOpCount(tx); CTxMemPool::setEntries setAncestors; - CTxMemPoolEntry entry(ptx, nFees, GetTime(), dPriority, chainActive.Height(), + CTxMemPoolEntry entry(ptx, nFees, GetTime(), chainActive.Height(), inChainInputValue, fSpendsCoinbase, nSigOpsCost, lp); pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload()); if (tx.IsZerocoinSpend()) { @@ -1726,20 +1724,20 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins for (unsigned int i = 0; i < tx.vin.size(); i++) { const COutPoint &prevout = tx.vin[i].prevout; - const CCoins *coins = inputs.AccessCoins(prevout.hash); - assert(coins); + const Coin &coin = inputs.AccessCoin(prevout); + assert(!coin.IsSpent()); // If prev is coinbase, check that it's matured - if (coins->IsCoinBase()) { - if (nSpendHeight - coins->nHeight < COINBASE_MATURITY) + if (coin.IsCoinBase()) { + if (nSpendHeight - coin.nHeight < COINBASE_MATURITY) return state.Invalid(false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", - strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight)); + strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } // Check for negative or overflow input values - nValueIn += coins->vout[prevout.n].nValue; - if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn)) + nValueIn += coin.out.nValue; + if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); } @@ -1924,8 +1922,6 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std return state.Error(strMessage); } -} // anon namespace - enum DisconnectResult { DISCONNECT_OK, // All good. @@ -1965,25 +1961,38 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) /** Undo the effects of this block (with given index) on the UTXO set represented by coins. * When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */ -static DisconnectResult DisconnectBlock(const CBlock& block,CValidationState& state, const CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean) +static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockIndex* pindex, CCoinsViewCache& view, bool *pfClean = nullptr) { assert(pindex->GetBlockHash() == view.GetBestBlock()); + bool fDIP0003Active = pindex->nHeight >= Params().GetConsensus().DIP0003Height; + bool fHasBestBlock = evoDb->VerifyBestBlock(pindex->GetBlockHash()); + + if (fDIP0003Active && !fHasBestBlock) { + // Nodes that upgraded after DIP3 activation will have to reindex to ensure evodb consistency + AbortNode("Found EvoDB inconsistency, you must reindex to continue"); + return DISCONNECT_FAILED; + } + if (pfClean) *pfClean = false; - bool fClean = true; CBlockUndo blockUndo; CDiskBlockPos pos = pindex->GetUndoPos(); + if (pos.IsNull()) { + error("DisconnectBlock(): no undo data available"); + return DISCONNECT_FAILED; + } + if (!UndoReadFromDisk(blockUndo, pos, pindex->pprev->GetBlockHash())) { + error("DisconnectBlock(): failure reading undo data"); + return DISCONNECT_FAILED; + } - if (pos.IsNull()) - return error("DisconnectBlock(): no undo data available"); - if (!UndoReadFromDisk(blockUndo, pos, pindex->pprev->GetBlockHash())) - return error("DisconnectBlock(): failure reading undo data"); - - if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) - return error("DisconnectBlock(): block and undo data inconsistent"); + if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) { + error("DisconnectBlock(): block and undo data inconsistent"); + return DISCONNECT_FAILED; + } CDbIndexHelper dbIndexHelper(fAddressIndex, fSpentIndex); @@ -2041,25 +2050,26 @@ static DisconnectResult DisconnectBlock(const CBlock& block,CValidationState& st if (fAddressIndex) { if (!pblocktree->EraseAddressIndex(dbIndexHelper.getAddressIndex())) { AbortNode(state, "Failed to delete address index"); - return error("Failed to delete address index"); + error("Failed to delete address index"); + return DISCONNECT_FAILED; } if (!pblocktree->UpdateAddressUnspentIndex(dbIndexHelper.getAddressUnspentIndex())) { AbortNode(state, "Failed to write address unspent index"); - return error("Failed to write address unspent index"); + error("Failed to write address unspent index"); + return DISCONNECT_FAILED; } if (!pblocktree->AddTotalSupply(-(block.vtx[0]->GetValueOut() - nFees))) { AbortNode(state, "Failed to write total supply"); - return error("Failed to write total supply"); + error("Failed to write total supply"); + return DISCONNECT_FAILED; } } } - if (pfClean) { + if (pfClean) *pfClean = fClean; - return true; - } - return fClean; + return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; } void static FlushBlockFile(bool fFinalize = false) @@ -2990,18 +3000,6 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, return true; } - -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; CCoinsViewCache view(&viewDummy); @@ -3010,11 +3008,11 @@ int GetInputAge(const CTxIn &txin) { CCoinsViewMemPool viewMempool(pcoinsTip, mempool); view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view - const CCoins *coins = view.AccessCoins(txin.prevout.hash); + const Coin &coin = view.AccessCoin(txin.prevout); - if (coins) { - if (coins->nHeight < 0) return 0; - return chainActive.Height() - coins->nHeight + 1; + if (!coin.IsSpent()) { + if (coin.nHeight < 0) return 0; + return chainActive.Height() - coin.nHeight + 1; } else { return -1; } @@ -3718,6 +3716,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); // 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. @@ -3745,6 +3744,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P } else { LogPrintf("CheckBlock(XZC): spork is off, skipping transaction locking checks\n"); } + */ // Check transactions if (nHeight == INT_MAX) diff --git a/src/validation.h b/src/validation.h index 94a07ce344..0f64bca544 100644 --- a/src/validation.h +++ b/src/validation.h @@ -554,17 +554,10 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Co bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, const CChainParams& chainparams, bool fJustCheck = false); -/** Undo the effects of this block (with given index) on the UTXO set represented by coins. - * In case pfClean is provided, operation will try to be tolerant about errors, and *pfClean - * will be true if no problems were found. Otherwise, the return value will be false in case - * of problems. Note that in any case, coins may be modified. */ -bool DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL); - /** Reprocess a number of blocks to try and get on the correct chain again **/ bool DisconnectBlocks(int blocks); void ReprocessBlocks(int nBlocks); -int GetUTXOHeight(const COutPoint& outpoint); int GetInputAge(const CTxIn &txin); int GetInputAgeIX(const uint256 &nTXHash, const CTxIn &txin); int GetIXConfirmations(const uint256 &nTXHash);