diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9ee27e55e3..fac98fea98 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,7 @@ image: "ubuntu:jammy" variables: DOCKER_DRIVER: overlay2 FAST_MODE: "false" # when "true", only run linter on arm and unit/functional tests on linux64, skip everything else + CI_FAILFAST_TEST_LEAVE_DANGLING: "1" # Gitlab CI does not care about dangling process and setting this variable avoids killing the CI script itself on error workflow: rules: diff --git a/doc/README.md b/doc/README.md index 425b25991d..16dab17218 100644 --- a/doc/README.md +++ b/doc/README.md @@ -80,6 +80,7 @@ The Dash Core repo's [root README](/README.md) contains relevant information on - [Reduce Memory](reduce-memory.md) - [Reduce Traffic](reduce-traffic.md) - [Tor Support](tor.md) +- [Transaction Relay Policy](policy/README.md) - [ZMQ](zmq.md) License diff --git a/doc/policy/README.md b/doc/policy/README.md new file mode 100644 index 0000000000..9c83f4b56e --- /dev/null +++ b/doc/policy/README.md @@ -0,0 +1,10 @@ +# Transaction Relay Policy + +Policy is a set of validation rules, in addition to consensus, enforced for unconfirmed +transactions. + +This documentation is not an exhaustive list of all policy rules. + +- [Packages](packages.md) + + diff --git a/doc/policy/packages.md b/doc/policy/packages.md new file mode 100644 index 0000000000..1e2ddbd82f --- /dev/null +++ b/doc/policy/packages.md @@ -0,0 +1,62 @@ +# Package Mempool Accept + +## Definitions + +A **package** is an ordered list of transactions, representable by a connected Directed Acyclic +Graph (a directed edge exists between a transaction that spends the output of another transaction). + +For every transaction `t` in a **topologically sorted** package, if any of its parents are present +in the package, they appear somewhere in the list before `t`. + +A **child-with-unconfirmed-parents** package is a topologically sorted package that consists of +exactly one child and all of its unconfirmed parents (no other transactions may be present). +The last transaction in the package is the child, and its package can be canonically defined based +on the current state: each of its inputs must be available in the UTXO set as of the current chain +tip or some preceding transaction in the package. + +## Package Mempool Acceptance Rules + +The following rules are enforced for all packages: + +* Packages cannot exceed `MAX_PACKAGE_COUNT=25` count and `MAX_PACKAGE_SIZE=101KvB` total size + (#20833) + + - *Rationale*: This is already enforced as mempool ancestor/descendant limits. If + transactions in a package are all related, exceeding this limit would mean that the package + can either be split up or it wouldn't pass individual mempool policy. + + - Note that, if these mempool limits change, package limits should be reconsidered. Users may + also configure their mempool limits differently. + +* Packages must be topologically sorted. (#20833) + +* Packages cannot have conflicting transactions, i.e. no two transactions in a package can spend + the same inputs. Packages cannot have duplicate transactions. (#20833) + +* No transaction in a package can conflict with a mempool transaction. + +* When packages are evaluated against ancestor/descendant limits, the union of all transactions' + descendants and ancestors is considered. (#21800) + + - *Rationale*: This is essentially a "worst case" heuristic intended for packages that are + heavily connected, i.e. some transaction in the package is the ancestor or descendant of all + the other transactions. + +The following rules are only enforced for packages to be submitted to the mempool (not enforced for +test accepts): + +* Packages must be child-with-unconfirmed-parents packages. This also means packages must contain at + least 2 transactions. (#22674) + +* Transactions in the package that have the same txid as another transaction already in the mempool + will be removed from the package prior to submission ("deduplication"). + + - *Rationale*: Node operators are free to set their mempool policies however they please, nodes + may receive transactions in different orders, and malicious counterparties may try to take + advantage of policy differences to pin or delay propagation of transactions. As such, it's + possible for some package transaction(s) to already be in the mempool, and there is no need to + repeat validation for those transactions or double-count them in fees. + + - *Rationale*: We want to prevent potential censorship vectors. We should not reject entire + packages because we already have one of the transactions. Also, if an attacker first broadcasts + a competing package, the honest package should still be considered for acceptance. diff --git a/doc/release-notes-6499.md b/doc/release-notes-6499.md new file mode 100644 index 0000000000..ee00c83b6e --- /dev/null +++ b/doc/release-notes-6499.md @@ -0,0 +1,11 @@ +Updated RPCs +------------ + +- The top-level fee fields `fee`, `modifiedfee`, `ancestorfees` and `descendantfees` + returned by RPCs `getmempoolentry`,`getrawmempool(verbose=true)`, + `getmempoolancestors(verbose=true)` and `getmempooldescendants(verbose=true)` + are deprecated and will be removed in the next major version (use + `-deprecated=fees` if needed in this version). The same fee fields can be accessed + through the `fees` object in the result. WARNING: deprecated + fields `ancestorfees` and `descendantfees` are denominated in sats, whereas all + fields in the `fees` object are denominated in DASH. diff --git a/src/Makefile.am b/src/Makefile.am index aacdf9a4af..98f3ab4ea8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -300,6 +300,7 @@ BITCOIN_CORE_H = \ rpc/blockchain.h \ rpc/client.h \ rpc/index_util.h \ + rpc/mempool.h \ rpc/mining.h \ rpc/protocol.h \ rpc/rawtransaction_util.h \ @@ -528,8 +529,10 @@ libbitcoin_server_a_SOURCES = \ rpc/blockchain.cpp \ rpc/coinjoin.cpp \ rpc/evo.cpp \ + rpc/fees.cpp \ rpc/index_util.cpp \ rpc/masternode.cpp \ + rpc/mempool.cpp \ rpc/governance.cpp \ rpc/mining.cpp \ rpc/misc.cpp \ @@ -538,6 +541,8 @@ libbitcoin_server_a_SOURCES = \ rpc/rawtransaction.cpp \ rpc/server.cpp \ rpc/server_util.cpp \ + rpc/signmessage.cpp \ + rpc/txoutproof.cpp \ script/sigcache.cpp \ shutdown.cpp \ spork.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 7583a616b9..c859010acf 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -174,6 +174,7 @@ BITCOIN_TESTS =\ test/torcontrol_tests.cpp \ test/transaction_tests.cpp \ test/txindex_tests.cpp \ + test/txpackage_tests.cpp \ test/txreconciliation_tests.cpp \ test/txvalidation_tests.cpp \ test/txvalidationcache_tests.cpp \ diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index a874fab0f7..55aa71bfdf 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include -#include +#include #include #include diff --git a/src/llmq/dkgsession.h b/src/llmq/dkgsession.h index 82a1263b92..b1897482fc 100644 --- a/src/llmq/dkgsession.h +++ b/src/llmq/dkgsession.h @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include +#include class CActiveMasternodeManager; class CInv; @@ -293,7 +295,7 @@ class CDKGSession private: std::vector> members; std::map membersMap; - std::set relayMembers; + std::unordered_set relayMembers; BLSVerificationVectorPtr vvecContribution; std::vector m_sk_contributions; @@ -382,7 +384,7 @@ class CDKGSession public: [[nodiscard]] CDKGMember* GetMember(const uint256& proTxHash) const; - [[nodiscard]] const std::set& RelayMembers() const { return relayMembers; } + [[nodiscard]] const std::unordered_set& RelayMembers() const { return relayMembers; } [[nodiscard]] const CBlockIndex* BlockIndex() const { return m_quorum_base_block_index; } [[nodiscard]] const uint256& ProTx() const { return myProTxHash; } [[nodiscard]] const Consensus::LLMQParams GetParams() const { return params; } diff --git a/src/llmq/dkgsessionhandler.cpp b/src/llmq/dkgsessionhandler.cpp index a773af25f7..107bbf98af 100644 --- a/src/llmq/dkgsessionhandler.cpp +++ b/src/llmq/dkgsessionhandler.cpp @@ -367,24 +367,22 @@ std::set BatchVerifyMessageSigs(CDKGSession& session, const std::vector< pubKeys.reserve(messages.size()); messageHashes.reserve(messages.size()); bool first = true; - for (const auto& p : messages ) { - const auto& msg = *p.second; - - auto member = session.GetMember(msg.proTxHash); + for (const auto& [nodeId, msg] : messages) { + auto member = session.GetMember(msg->proTxHash); if (!member) { // should not happen as it was verified before - ret.emplace(p.first); + ret.emplace(nodeId); continue; } if (first) { - aggSig = msg.sig; + aggSig = msg->sig; } else { - aggSig.AggregateInsecure(msg.sig); + aggSig.AggregateInsecure(msg->sig); } first = false; - auto msgHash = msg.GetSignHash(); + auto msgHash = msg->GetSignHash(); if (!messageHashesSet.emplace(msgHash).second) { // can only happen in 2 cases: // 1. Someone sent us the same message twice but with differing signature, meaning that at least one of them @@ -418,16 +416,15 @@ std::set BatchVerifyMessageSigs(CDKGSession& session, const std::vector< // different nodes, let's figure out who are the bad ones } - for (const auto& p : messages) { - if (ret.count(p.first)) { + for (const auto& [nodeId, msg] : messages) { + if (ret.count(nodeId)) { continue; } - const auto& msg = *p.second; - auto member = session.GetMember(msg.proTxHash); - bool valid = msg.sig.VerifyInsecure(member->dmn->pdmnState->pubKeyOperator.Get(), msg.GetSignHash()); + auto member = session.GetMember(msg->proTxHash); + bool valid = msg->sig.VerifyInsecure(member->dmn->pdmnState->pubKeyOperator.Get(), msg->GetSignHash()); if (!valid) { - ret.emplace(p.first); + ret.emplace(nodeId); } } return ret; @@ -506,7 +503,6 @@ bool ProcessPendingMessageBatch(const CConnman& connman, CDKGSession& session, C auto badNodes = BatchVerifyMessageSigs(session, preverifiedMessages); if (!badNodes.empty()) { - LOCK(cs_main); for (auto nodeId : badNodes) { LogPrint(BCLog::LLMQ_DKG, "%s -- failed to verify signature, peer=%d\n", __func__, nodeId); pendingMessages.Misbehaving(nodeId, 100, peerman); diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index 67b2890b57..a27fa04494 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -370,7 +370,7 @@ void CQuorumManager::CheckQuorumConnections(CConnman& connman, const Consensus:: LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); } } else if (watchOtherISQuorums && !quorum->IsMember(myProTxHash)) { - std::set connections; + std::unordered_set connections; const auto& cindexes = utils::CalcDeterministicWatchConnections(llmqParams.type, quorum->m_quorum_base_block_index, quorum->members.size(), 1); for (auto idx : cindexes) { connections.emplace(quorum->members[idx]->proTxHash); diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index 6bcc0e8996..f77d9cedec 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -717,7 +717,7 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s bool canTryRecovery = false; // prepare node set for direct-push in case this is our sig share - std::set quorumNodes; + std::unordered_set quorumNodes; if (!IsAllMembersConnectedEnabled(llmqType, m_sporkman) && sigShare.getQuorumMember() == quorum->GetMemberIndex(m_mn_activeman->GetProTxHash())) { quorumNodes = connman.GetMasternodeQuorumNodes(sigShare.getLlmqType(), sigShare.getQuorumHash()); } @@ -739,16 +739,14 @@ void CSigSharesManager::ProcessSigShare(PeerManager& peerman, const CSigShare& s // Update the time we've seen the last sigShare timeSeenForSessions[sigShare.GetSignHash()] = GetTime().count(); - if (!quorumNodes.empty()) { - // don't announce and wait for other nodes to request this share and directly send it to them - // there is no way the other nodes know about this share as this is the one created on this node - for (auto otherNodeId : quorumNodes) { - auto& nodeState = nodeStates[otherNodeId]; - auto& session = nodeState.GetOrCreateSessionFromShare(sigShare); - session.quorum = quorum; - session.requested.Set(sigShare.getQuorumMember(), true); - session.knows.Set(sigShare.getQuorumMember(), true); - } + // don't announce and wait for other nodes to request this share and directly send it to them + // there is no way the other nodes know about this share as this is the one created on this node + for (auto otherNodeId : quorumNodes) { + auto& nodeState = nodeStates[otherNodeId]; + auto& session = nodeState.GetOrCreateSessionFromShare(sigShare); + session.quorum = quorum; + session.requested.Set(sigShare.getQuorumMember(), true); + session.knows.Set(sigShare.getQuorumMember(), true); } size_t sigShareCount = sigShares.CountForSignHash(sigShare.GetSignHash()); diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index 4f7d635958..a9bb1e3c81 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -653,12 +653,13 @@ uint256 DeterministicOutboundConnection(const uint256& proTxHash1, const uint256 return proTxHash2; } -std::set GetQuorumConnections(const Consensus::LLMQParams& llmqParams, CDeterministicMNManager& dmnman, const CSporkManager& sporkman, - gsl::not_null pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound) +std::unordered_set GetQuorumConnections( + const Consensus::LLMQParams& llmqParams, CDeterministicMNManager& dmnman, const CSporkManager& sporkman, + gsl::not_null pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound) { if (IsAllMembersConnectedEnabled(llmqParams.type, sporkman)) { auto mns = GetAllQuorumMembers(llmqParams.type, dmnman, pQuorumBaseBlockIndex); - std::set result; + std::unordered_set result; for (const auto& dmn : mns) { if (dmn->proTxHash == forMember) { @@ -677,23 +678,25 @@ std::set GetQuorumConnections(const Consensus::LLMQParams& llmqParams, return GetQuorumRelayMembers(llmqParams, dmnman, pQuorumBaseBlockIndex, forMember, onlyOutbound); } -std::set GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, CDeterministicMNManager& dmnman, gsl::not_null pQuorumBaseBlockIndex, - const uint256& forMember, bool onlyOutbound) +std::unordered_set GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, + CDeterministicMNManager& dmnman, + gsl::not_null pQuorumBaseBlockIndex, + const uint256& forMember, bool onlyOutbound) { auto mns = GetAllQuorumMembers(llmqParams.type, dmnman, pQuorumBaseBlockIndex); - std::set result; + std::unordered_set result; auto calcOutbound = [&](size_t i, const uint256& proTxHash) { + // Relay to nodes at indexes (i+2^k)%n, where + // k: 0..max(1, floor(log2(n-1))-1) + // n: size of the quorum/ring + std::unordered_set r{}; if (mns.size() == 1) { // No outbound connections are needed when there is one MN only. // Also note that trying to calculate results via the algorithm below // would result in an endless loop. - return std::set(); + return r; } - // Relay to nodes at indexes (i+2^k)%n, where - // k: 0..max(1, floor(log2(n-1))-1) - // n: size of the quorum/ring - std::set r; int gap = 1; int gap_max = (int)mns.size() - 1; int k = 0; @@ -775,8 +778,8 @@ bool EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, CConnman& LogPrint(BCLog::NET_NETCONN, "%s -- isMember=%d for quorum %s:\n", __func__, isMember, pQuorumBaseBlockIndex->GetBlockHash().ToString()); - std::set connections; - std::set relayMembers; + std::unordered_set connections; + std::unordered_set relayMembers; if (isMember) { connections = GetQuorumConnections(llmqParams, dmnman, sporkman, pQuorumBaseBlockIndex, myProTxHash, true); relayMembers = GetQuorumRelayMembers(llmqParams, dmnman, pQuorumBaseBlockIndex, myProTxHash, true); diff --git a/src/llmq/utils.h b/src/llmq/utils.h index 2b5dc3a88e..2795db58dc 100644 --- a/src/llmq/utils.h +++ b/src/llmq/utils.h @@ -5,13 +5,15 @@ #ifndef BITCOIN_LLMQ_UTILS_H #define BITCOIN_LLMQ_UTILS_H +#include #include +#include #include -#include #include #include #include +#include #include class CConnman; @@ -34,8 +36,13 @@ namespace utils std::vector GetAllQuorumMembers(Consensus::LLMQType llmqType, CDeterministicMNManager& dmnman, gsl::not_null pQuorumBaseBlockIndex, bool reset_cache = false); uint256 DeterministicOutboundConnection(const uint256& proTxHash1, const uint256& proTxHash2); -std::set GetQuorumConnections(const Consensus::LLMQParams& llmqParams, CDeterministicMNManager& dmnman, const CSporkManager& sporkman, gsl::not_null pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound); -std::set GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, CDeterministicMNManager& dmnman, gsl::not_null pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound); +std::unordered_set GetQuorumConnections( + const Consensus::LLMQParams& llmqParams, CDeterministicMNManager& dmnman, const CSporkManager& sporkman, + gsl::not_null pQuorumBaseBlockIndex, const uint256& forMember, bool onlyOutbound); +std::unordered_set GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, + CDeterministicMNManager& dmnman, + gsl::not_null pQuorumBaseBlockIndex, + const uint256& forMember, bool onlyOutbound); std::set CalcDeterministicWatchConnections(Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, size_t memberCount, size_t connectionCount); bool EnsureQuorumConnections(const Consensus::LLMQParams& llmqParams, CConnman& connman, CDeterministicMNManager& dmnman, const CSporkManager& sporkman, diff --git a/src/net.cpp b/src/net.cpp index 9bfb813178..5b07e5f9bf 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3718,12 +3718,11 @@ void CConnman::ThreadOpenMasternodeConnections(CDeterministicMNManager& dmnman, if (!fNetworkActive || !m_masternode_thread_active || !mn_sync.IsBlockchainSynced()) continue; - std::set connectedNodes; - std::map connectedProRegTxHashes; + std::unordered_set connectedNodes; + std::unordered_map connectedProRegTxHashes; ForEachNode([&](const CNode* pnode) { - auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash(); connectedNodes.emplace(pnode->addr); - if (!verifiedProRegTxHash.IsNull()) { + if (auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash(); !verifiedProRegTxHash.IsNull()) { connectedProRegTxHashes.emplace(verifiedProRegTxHash, pnode->IsInboundConn()); } }); @@ -4617,7 +4616,7 @@ bool CConnman::AddPendingMasternode(const uint256& proTxHash) return true; } -void CConnman::SetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::set& proTxHashes) +void CConnman::SetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::unordered_set& proTxHashes) { LOCK(cs_vPendingMasternodes); auto it = masternodeQuorumNodes.emplace(std::make_pair(llmqType, quorumHash), proTxHashes); @@ -4626,7 +4625,7 @@ void CConnman::SetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint } } -void CConnman::SetMasternodeQuorumRelayMembers(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::set& proTxHashes) +void CConnman::SetMasternodeQuorumRelayMembers(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::unordered_set& proTxHashes) { { LOCK(cs_vPendingMasternodes); @@ -4657,10 +4656,10 @@ bool CConnman::HasMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint return masternodeQuorumNodes.count(std::make_pair(llmqType, quorumHash)); } -std::set CConnman::GetMasternodeQuorums(Consensus::LLMQType llmqType) const +std::unordered_set CConnman::GetMasternodeQuorums(Consensus::LLMQType llmqType) const { LOCK(cs_vPendingMasternodes); - std::set result; + std::unordered_set result; for (const auto& p : masternodeQuorumNodes) { if (p.first.first != llmqType) { continue; @@ -4670,7 +4669,7 @@ std::set CConnman::GetMasternodeQuorums(Consensus::LLMQType llmqType) c return result; } -std::set CConnman::GetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash) const +std::unordered_set CConnman::GetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash) const { LOCK2(m_nodes_mutex, cs_vPendingMasternodes); auto it = masternodeQuorumNodes.find(std::make_pair(llmqType, quorumHash)); @@ -4679,7 +4678,7 @@ std::set CConnman::GetMasternodeQuorumNodes(Consensus::LLMQType llmqType } const auto& proRegTxHashes = it->second; - std::set nodes; + std::unordered_set nodes; for (const auto pnode : m_nodes) { if (pnode->fDisconnect) { continue; diff --git a/src/net.h b/src/net.h index 524f0a244b..4901507a6c 100644 --- a/src/net.h +++ b/src/net.h @@ -1501,12 +1501,12 @@ friend class CNode; EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex, !mutexMsgProc); bool AddPendingMasternode(const uint256& proTxHash); - void SetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::set& proTxHashes); - void SetMasternodeQuorumRelayMembers(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::set& proTxHashes); + void SetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::unordered_set& proTxHashes); + void SetMasternodeQuorumRelayMembers(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::unordered_set& proTxHashes); bool HasMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash) const; - std::set GetMasternodeQuorums(Consensus::LLMQType llmqType) const; + std::unordered_set GetMasternodeQuorums(Consensus::LLMQType llmqType) const; // also returns QWATCH nodes - std::set GetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash) const; + std::unordered_set GetMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash) const; void RemoveMasternodeQuorumNodes(Consensus::LLMQType llmqType, const uint256& quorumHash); bool IsMasternodeQuorumNode(const CNode* pnode, const CDeterministicMNList& tip_mn_list) const; bool IsMasternodeQuorumRelayMember(const uint256& protxHash); @@ -1815,8 +1815,8 @@ friend class CNode; std::vector vPendingMasternodes; mutable RecursiveMutex cs_vPendingMasternodes; - std::map, std::set> masternodeQuorumNodes GUARDED_BY(cs_vPendingMasternodes); - std::map, std::set> masternodeQuorumRelayMembers GUARDED_BY(cs_vPendingMasternodes); + std::map, std::unordered_set> masternodeQuorumNodes GUARDED_BY(cs_vPendingMasternodes); + std::map, std::unordered_set> masternodeQuorumRelayMembers GUARDED_BY(cs_vPendingMasternodes); std::set masternodePendingProbes GUARDED_BY(cs_vPendingMasternodes); mutable Mutex cs_mapSocketToNode; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index da550cc066..10ed771d6b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/src/policy/feerate.cpp b/src/policy/feerate.cpp index 2bd7eb87a1..cf95d0b566 100644 --- a/src/policy/feerate.cpp +++ b/src/policy/feerate.cpp @@ -3,8 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include - #include CFeeRate::CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes) diff --git a/src/policy/feerate.h b/src/policy/feerate.h index 69e66d6ab5..823f7ed392 100644 --- a/src/policy/feerate.h +++ b/src/policy/feerate.h @@ -9,7 +9,10 @@ #include #include + +#include #include +#include const std::string CURRENCY_UNIT = "DASH"; // One formatted unit const std::string CURRENCY_ATOM = "duff"; // One indivisible minimum value unit diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 47e117a17f..0384e19979 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -6,12 +6,30 @@ #include #include +#include #include #include +#include +#include +#include +#include #include +#include +#include #include +#include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include static const char* FEE_ESTIMATES_FILENAME = "fee_estimates.dat"; diff --git a/src/policy/fees.h b/src/policy/fees.h index 7b5b625170..748430d07e 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -7,20 +7,20 @@ #include #include -#include #include #include +#include +#include #include #include #include +#include #include #include class CAutoFile; -class CFeeRate; class CTxMemPoolEntry; -class CTxMemPool; class TxConfirmStats; /* Identifier for each of the 3 different TxConfirmStats which will track diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp index cfd0539965..67918c9dec 100644 --- a/src/policy/packages.cpp +++ b/src/policy/packages.cpp @@ -2,12 +2,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include #include +#include #include #include #include +#include +#include +#include +#include #include #include @@ -60,3 +64,20 @@ bool CheckPackage(const Package& txns, PackageValidationState& state) } return true; } + +bool IsChildWithParents(const Package& package) +{ + assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); + if (package.size() < 2) return false; + + // The package is expected to be sorted, so the last transaction is the child. + const auto& child = package.back(); + std::unordered_set input_txids; + std::transform(child->vin.cbegin(), child->vin.cend(), + std::inserter(input_txids, input_txids.end()), + [](const auto& input) { return input.prevout.hash; }); + + // Every transaction must be a parent of the last transaction in the package. + return std::all_of(package.cbegin(), package.cend() - 1, + [&input_txids](const auto& ptx) { return input_txids.count(ptx->GetHash()) > 0; }); +} diff --git a/src/policy/packages.h b/src/policy/packages.h index bb2e4b3b47..47c6717b03 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -5,10 +5,12 @@ #ifndef BITCOIN_POLICY_PACKAGES_H #define BITCOIN_POLICY_PACKAGES_H +#include #include #include #include +#include #include /** Default maximum number of transactions in a package. */ @@ -17,6 +19,15 @@ static constexpr uint32_t MAX_PACKAGE_COUNT{25}; static constexpr uint32_t MAX_PACKAGE_SIZE{101}; static_assert(MAX_PACKAGE_SIZE * 1000 >= MAX_STANDARD_TX_SIZE); +// If a package is submitted, it must be within the mempool's ancestor/descendant limits. Since a +// submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor +// of the child), package limits are ultimately bounded by mempool package limits. Ensure that the +// defaults reflect this constraint. +static_assert(DEFAULT_DESCENDANT_LIMIT >= MAX_PACKAGE_COUNT); +static_assert(DEFAULT_ANCESTOR_LIMIT >= MAX_PACKAGE_COUNT); +static_assert(DEFAULT_ANCESTOR_SIZE_LIMIT >= MAX_PACKAGE_SIZE); +static_assert(DEFAULT_DESCENDANT_SIZE_LIMIT >= MAX_PACKAGE_SIZE); + /** A "reason" why a package was invalid. It may be that one or more of the included * transactions is invalid or the package itself violates our rules. * We don't distinguish between consensus and policy violations right now. @@ -25,6 +36,7 @@ enum class PackageValidationResult { PCKG_RESULT_UNSET = 0, //!< Initial value. The package has not yet been rejected. PCKG_POLICY, //!< The package itself is invalid (e.g. too many transactions). PCKG_TX, //!< At least one tx is invalid. + PCKG_MEMPOOL_ERROR, //!< Mempool logic error. }; /** A package is an ordered list of transactions. The transactions cannot conflict with (spend the @@ -41,4 +53,10 @@ class PackageValidationState : public ValidationState { */ bool CheckPackage(const Package& txns, PackageValidationState& state); +/** Context-free check that a package is exactly one child and its parents; not all parents need to + * be present, but the package must not contain any transactions that are not the child's parents. + * It is expected to be sorted, which means the last transaction must be the child. + */ +bool IsChildWithParents(const Package& package); + #endif // BITCOIN_POLICY_PACKAGES_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 85e4ce3164..90e0b71816 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -8,8 +8,20 @@ #include #include -#include - +#include +#include +#include +#include +#include +#include