diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 2b2c58862..b689b8310 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1,21 +1,20 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2018 The Bitcoin Core developers // 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 -#include #include +#include #include #include #include @@ -23,25 +22,24 @@ #include #include #include -#include #include #include -#include // For NDEBUG compile time check -#include -#include -#include +#include +#include +#include +#include #include -#include +#include + +#if defined(NDEBUG) +# error "Bitcoin cannot be compiled without assertions." +#endif /** Expiration time for orphan transactions in seconds */ static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; /** Minimum time between orphan transactions expire time checks in seconds */ static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; -/** How long to cache transactions in mapRelay for normal relay */ -static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME = std::chrono::minutes{15}; -/** How long a transaction has to be in the mempool before it can unconditionally be relayed (even when not in mapRelay). */ -static constexpr std::chrono::seconds UNCONDITIONAL_RELAY_DELAY = std::chrono::minutes{2}; /** Headers download timeout expressed in microseconds * Timeout = base + per_header * (expected number of headers) */ static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes @@ -66,114 +64,44 @@ static constexpr int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; /// Age after which a block is considered historical for purposes of rate /// limiting block relay. Set to one week, denominated in seconds. static constexpr int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60; -/** Time between pings automatically sent out for latency probing and keepalive */ -static constexpr std::chrono::minutes PING_INTERVAL{2}; -/** The maximum number of entries in a locator */ -static const unsigned int MAX_LOCATOR_SZ = 101; -/** The maximum number of entries in an 'inv' protocol message */ -static const unsigned int MAX_INV_SZ = 50000; -/** Maximum number of in-flight transaction requests from a peer. It is not a hard limit, but the threshold at which - * point the OVERLOADED_PEER_TX_DELAY kicks in. */ -static constexpr int32_t MAX_PEER_TX_REQUEST_IN_FLIGHT = 100; -/** Maximum number of transactions to consider for requesting, per peer. It provides a reasonable DoS limit to - * per-peer memory usage spent on announcements, while covering peers continuously sending INVs at the maximum - * rate (by our own policy, see INVENTORY_BROADCAST_PER_SECOND) for several minutes, while not receiving - * the actual transaction (from any peer) in response to requests for them. */ -static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 5000; -/** How long to delay requesting transactions via txids, if we have wtxid-relaying peers */ -static constexpr auto TXID_RELAY_DELAY = std::chrono::seconds{2}; -/** How long to delay requesting transactions from non-preferred peers */ -static constexpr auto NONPREF_PEER_TX_DELAY = std::chrono::seconds{2}; -/** How long to delay requesting transactions from overloaded peers (see MAX_PEER_TX_REQUEST_IN_FLIGHT). */ -static constexpr auto OVERLOADED_PEER_TX_DELAY = std::chrono::seconds{2}; -/** How long to wait (in microseconds) before downloading a transaction from an additional peer */ -static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seconds{60}}; -/** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */ -static const unsigned int MAX_GETDATA_SZ = 1000; -/** Number of blocks that can be requested at any given time from a single peer. */ -static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16; -/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */ -static const unsigned int BLOCK_STALLING_TIMEOUT = 2; -/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends - * less than this number, we reached its tip. Changing this value is a protocol upgrade. */ -static const unsigned int MAX_HEADERS_RESULTS = 2000; -/** Maximum depth of blocks we're willing to serve as compact blocks to peers - * when requested. For older blocks, a regular BLOCK response will be sent. */ -static const int MAX_CMPCTBLOCK_DEPTH = 5; -/** Maximum depth of blocks we're willing to respond to GETBLOCKTXN requests for. */ -static const int MAX_BLOCKTXN_DEPTH = 10; -/** Size of the "block download window": how far ahead of our current height do we fetch? - * Larger windows tolerate larger download speed differences between peer, but increase the potential - * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably - * want to make this a per-peer adaptive value at some point. */ -static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; -/** Block download timeout base, expressed in millionths of the block interval (i.e. 10 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 1000000; -/** Additional block download timeout per parallel downloading peer (i.e. 5 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 500000; -/** Maximum number of headers to announce when relaying blocks with headers message.*/ -static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; -/** Maximum number of unconnecting headers announcements before DoS score */ -static const int MAX_UNCONNECTING_HEADERS = 10; -/** Minimum blocks required to signal NODE_NETWORK_LIMITED */ -static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; -/** Average delay between local address broadcasts */ -static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24}; -/** Average delay between peer address broadcasts */ -static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30}; -/** Average delay between trickled inventory transmissions in seconds. - * Blocks and peers with noban permission bypass this, outbound peers get half this delay. */ -static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; -/** Maximum rate of inventory items to send per second. - * Limits the impact of low-fee transaction floods. */ -static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND = 7; -/** Maximum number of inventory items to send per transmission. */ -static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * INVENTORY_BROADCAST_INTERVAL; -/** The number of most recently announced transactions a peer can request. */ -static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500; -/** Verify that INVENTORY_MAX_RECENT_RELAY is enough to cache everything typically - * relayed before unconditional relay from the mempool kicks in. This is only a - * lower bound, and it should be larger to account for higher inv rate to outbound - * peers, and random variations in the broadcast mechanism. */ -static_assert(INVENTORY_MAX_RECENT_RELAY >= INVENTORY_BROADCAST_PER_SECOND * UNCONDITIONAL_RELAY_DELAY / std::chrono::seconds{1}, "INVENTORY_RELAY_MAX too low"); -/** Average delay between feefilter broadcasts in seconds. */ -static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; -/** Maximum feefilter broadcast delay after significant change. */ -static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; -/** Maximum number of compact filters that may be requested with one getcfilters. See BIP 157. */ -static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000; -/** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */ -static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000; -/** the maximum percentage of addresses from our addrman to return in response to a getaddr message. */ -static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23; struct COrphanTx { // When modifying, adapt the copy of this definition in tests/DoS_tests. CTransactionRef tx; NodeId fromPeer; int64_t nTimeExpire; - size_t list_pos; }; - -/** Guards orphan transactions and extra txs for compact blocks */ -RecursiveMutex g_cs_orphans; -/** Map from txid to orphan transaction record. Limited by - * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ +static CCriticalSection g_cs_orphans; std::map mapOrphanTransactions GUARDED_BY(g_cs_orphans); -/** Index from wtxid into the mapOrphanTransactions to lookup orphan - * transactions using their witness ids. */ -std::map::iterator> g_orphans_by_wtxid GUARDED_BY(g_cs_orphans); void EraseOrphansFor(NodeId peer); +/** Increase a node's misbehavior score. */ +void Misbehaving(NodeId nodeid, int howmuch, const std::string& message="") EXCLUSIVE_LOCKS_REQUIRED(cs_main); + +/** Average delay between local address broadcasts in seconds. */ +static constexpr unsigned int AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24 * 60 * 60; +/** Average delay between peer address broadcasts in seconds. */ +static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30; +/** Average delay between trickled inventory transmissions in seconds. + * Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */ +static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; +/** Maximum number of inventory items to send per transmission. + * Limits the impact of low-fee transaction floods. */ +static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL; +/** Average delay between feefilter broadcasts in seconds. */ +static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; +/** Maximum feefilter broadcast delay after significant change. */ +static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; + // Internal stuff namespace { /** Number of nodes with fSyncStarted. */ int nSyncStarted GUARDED_BY(cs_main) = 0; /** - * Sources of received blocks, saved to be able punish them when processing - * happens afterwards. + * Sources of received blocks, saved to be able to send them reject + * messages or ban them when processing happens afterwards. * Set mapBlockSource[hash].second to false if the node should not be * punished if the block is invalid. */ @@ -196,34 +124,11 @@ namespace { * million to make it highly unlikely for users to have issues with this * filter. * - * We typically only add wtxids to this filter. For non-segwit - * transactions, the txid == wtxid, so this only prevents us from - * re-downloading non-segwit transactions when communicating with - * non-wtxidrelay peers -- which is important for avoiding malleation - * attacks that could otherwise interfere with transaction relay from - * non-wtxidrelay peers. For communicating with wtxidrelay peers, having - * the reject filter store wtxids is exactly what we want to avoid - * redownload of a rejected transaction. - * - * In cases where we can tell that a segwit transaction will fail - * validation no matter the witness, we may add the txid of such - * transaction to the filter as well. This can be helpful when - * communicating with txid-relay peers or if we were to otherwise fetch a - * transaction via txid (eg in our orphan handling). - * * Memory used: 1.3 MB */ std::unique_ptr recentRejects GUARDED_BY(cs_main); uint256 hashRecentRejectsChainTip GUARDED_BY(cs_main); - /* - * Filter for transactions that have been recently confirmed. - * We use this to avoid requesting transactions that have already been - * confirnmed. - */ - Mutex g_cs_recent_confirmed_transactions; - std::unique_ptr g_recent_confirmed_transactions GUARDED_BY(g_cs_recent_confirmed_transactions); - /** Blocks that are in flight, and that are in the queue to be downloaded. */ struct QueuedBlock { uint256 hash; @@ -242,21 +147,20 @@ namespace { /** Number of peers from which we're downloading blocks. */ int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0; - /** Number of peers with wtxid relay. */ - int g_wtxid_relay_peers GUARDED_BY(cs_main) = 0; - /** Number of outbound peers with m_chain_sync.m_protect. */ int g_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0; /** When our tip was last updated. */ std::atomic g_last_tip_update(0); - /** Relay map (txid or wtxid -> CTransactionRef) */ + /** Relay map */ typedef std::map MapRelay; MapRelay mapRelay GUARDED_BY(cs_main); /** Expiration-time ordered list of (expire time, relay map entry) pairs. */ std::deque> vRelayExpiration GUARDED_BY(cs_main); + std::atomic nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block + struct IteratorComparator { template @@ -265,22 +169,19 @@ namespace { return &(*a) < &(*b); } }; - - /** Index from the parents' COutPoint into the mapOrphanTransactions. Used - * to remove orphan transactions from the mapOrphanTransactions */ std::map::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); - /** Orphan transactions in vector for quick random eviction */ - std::vector::iterator> g_orphan_list GUARDED_BY(g_cs_orphans); - /** Orphan/conflicted/etc transactions that are kept for compact block reconstruction. - * The last -blockreconstructionextratxn/DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN of - * these are kept in a ring buffer */ - static std::vector> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); - /** Offset into vExtraTxnForCompact to insert the next tx */ static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; + static std::vector> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); } // namespace namespace { +struct CBlockReject { + unsigned char chRejectCode; + std::string strRejectReason; + uint256 hashBlock; +}; + /** * Maintain validation-specific state about nodes, protected by cs_main, instead * by CNode's own locks. This simplifies asynchronous operation, where @@ -292,6 +193,14 @@ struct CNodeState { const CService address; //! Whether we have a fully established connection. bool fCurrentlyConnected; + //! Accumulated misbehaviour score for this peer. + int nMisbehavior; + //! Whether this peer should be disconnected and banned (unless whitelisted). + bool fShouldBan; + //! String name of this peer (debugging/logging purposes). + const std::string name; + //! List of asynchronously-determined block rejections to notify this peer about. + std::vector rejects; //! The best known block we know this peer has announced. const CBlockIndex *pindexBestKnownBlock; //! The hash of the last unknown block this peer has announced. @@ -335,17 +244,10 @@ struct CNodeState { */ bool fSupportsDesiredCmpctVersion; - /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. - * - * Both are only in effect for outbound, non-manual, non-protected connections. - * Any peer protected (m_protect = true) is not chosen for eviction. A peer is - * marked as protected if all of these are true: - * - its connection type is IsBlockOnlyConn() == false - * - it gave us a valid connecting header - * - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet - * - it has a better chain than we have - * - * CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip, + /** State used to enforce CHAIN_SYNC_TIMEOUT + * Only in effect for outbound, non-manual connections, with + * m_protect == false + * Algorithm: if a peer's best known block has less work than our tip, * set a timeout CHAIN_SYNC_TIMEOUT seconds in the future: * - If at timeout their best known block now has more work than our tip * when the timeout was set, then either reset the timeout or clear it @@ -355,9 +257,6 @@ struct CNodeState { * and set a shorter timeout, HEADERS_RESPONSE_TIME seconds in future. * If their best known block is still behind when that new timeout is * reached, disconnect. - * - * EXTRA_PEER_CHECK_INTERVAL: after each interval, if we have too many outbound peers, - * drop the outbound one that least recently announced us a new block. */ struct ChainSyncTimeoutState { //! A timeout used for checking whether our peer has sufficiently synced @@ -375,22 +274,10 @@ struct CNodeState { //! Time of last new block announcement int64_t m_last_block_announcement; - //! Whether this peer is an inbound connection - bool m_is_inbound; - - //! Whether this peer is a manual connection - bool m_is_manual_connection; - - //! A rolling bloom filter of all announced tx CInvs to this peer. - CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; - - //! Whether this peer relays txs via wtxid - bool m_wtxid_relay{false}; - - CNodeState(CAddress addrIn, bool is_inbound, bool is_manual) - : address(addrIn), m_is_inbound(is_inbound), m_is_manual_connection(is_manual) - { + CNodeState(CAddress addrIn, std::string addrNameIn) : address(addrIn), name(addrNameIn) { fCurrentlyConnected = false; + nMisbehavior = 0; + fShouldBan = false; pindexBestKnownBlock = nullptr; hashLastUnknownBlock.SetNull(); pindexLastCommonBlock = nullptr; @@ -411,7 +298,6 @@ struct CNodeState { fSupportsDesiredCmpctVersion = false; m_chain_sync = { 0, nullptr, false, false }; m_last_block_announcement = 0; - m_recently_announced_invs.reset(); } }; @@ -425,91 +311,29 @@ static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return &it->second; } -/** - * Data structure for an individual peer. This struct is not protected by - * cs_main since it does not contain validation-critical data. - * - * Memory is owned by shared pointers and this object is destructed when - * the refcount drops to zero. - * - * TODO: move most members from CNodeState to this structure. - * TODO: move remaining application-layer data members from CNode to this structure. - */ -struct Peer { - /** Same id as the CNode object for this peer */ - const NodeId m_id{0}; - - /** Protects misbehavior data members */ - Mutex m_misbehavior_mutex; - /** Accumulated misbehavior score for this peer */ - int m_misbehavior_score GUARDED_BY(m_misbehavior_mutex){0}; - /** Whether this peer should be disconnected and marked as discouraged (unless it has the noban permission). */ - bool m_should_discourage GUARDED_BY(m_misbehavior_mutex){false}; - - /** Set of txids to reconsider once their parent transactions have been accepted **/ - std::set m_orphan_work_set GUARDED_BY(g_cs_orphans); - - /** Protects m_getdata_requests **/ - Mutex m_getdata_requests_mutex; - /** Work queue of items requested by this peer **/ - std::deque m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); - - Peer(NodeId id) : m_id(id) {} -}; - -using PeerRef = std::shared_ptr; - -/** - * Map of all Peer objects, keyed by peer id. This map is protected - * by the global g_peer_mutex. Once a shared pointer reference is - * taken, the lock may be released. Individual fields are protected by - * their own locks. - */ -Mutex g_peer_mutex; -static std::map g_peer_map GUARDED_BY(g_peer_mutex); - -/** Get a shared pointer to the Peer object. - * May return nullptr if the Peer object can't be found. */ -static PeerRef GetPeerRef(NodeId id) -{ - LOCK(g_peer_mutex); - auto it = g_peer_map.find(id); - return it != g_peer_map.end() ? it->second : nullptr; -} - -static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { nPreferredDownload -= state->fPreferredDownload; // Whether this node should be marked as a preferred download node. - state->fPreferredDownload = (!node.IsInboundConn() || node.HasPermission(PF_NOBAN)) && !node.IsAddrFetchConn() && !node.fClient; + state->fPreferredDownload = (!node->fInbound || node->fWhitelisted) && !node->fOneShot && !node->fClient; nPreferredDownload += state->fPreferredDownload; } -static void PushNodeVersion(CNode& pnode, CConnman& connman, int64_t nTime) +static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) { - // Note that pnode->GetLocalServices() is a reflection of the local - // services we were offering when the CNode object was created for this - // peer. - ServiceFlags nLocalNodeServices = pnode.GetLocalServices(); - uint64_t nonce = pnode.GetLocalNonce(); - int nNodeStartingHeight = pnode.GetMyStartingHeight(); - NodeId nodeid = pnode.GetId(); - CAddress addr = pnode.addr; - - CAddress addrYou = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ? - addr : - CAddress(CService(), addr.nServices); - CAddress addrMe = CAddress(CService(), nLocalNodeServices); - - connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, - nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode.m_tx_relay != nullptr)); + ServiceFlags nLocalNodeServices = pnode->GetLocalServices(); + uint64_t nonce = pnode->GetLocalNonce(); + int nNodeStartingHeight = pnode->GetMyStartingHeight(); + NodeId nodeid = pnode->GetId(); + CAddress addr = pnode->addr; - if (strCommand == NetMsgType::ENG_ANNOUNCEMENT) { - // Enigma Announcement - return true; - } + CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr) ? addr : CAddress(CService(), addr.nServices)); + CAddress addrMe = CAddress(CService(), nLocalNodeServices); + + connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, + nonce, strSubVersion, nNodeStartingHeight, ::fRelayTxes)); if (fLogIPs) { LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); @@ -532,7 +356,7 @@ static bool MarkBlockAsReceived(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs } if (state->vBlocksInFlight.begin() == itInFlight->second.second) { // First block on the queue was received, update the start download time for the next one - state->nDownloadingSince = std::max(state->nDownloadingSince, count_microseconds(GetTime())); + state->nDownloadingSince = std::max(state->nDownloadingSince, GetTimeMicros()); } state->vBlocksInFlight.erase(itInFlight->second.second); state->nBlocksInFlight--; @@ -545,7 +369,7 @@ static bool MarkBlockAsReceived(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs // returns false, still setting pit, if the block was already in flight from the same peer // pit will only be valid as long as the same cs_main lock is being held -static bool MarkBlockAsInFlight(CTxMemPool& mempool, NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = nullptr, std::list::iterator** pit = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { +static bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = nullptr, std::list::iterator** pit = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { CNodeState *state = State(nodeid); assert(state != nullptr); @@ -567,7 +391,7 @@ static bool MarkBlockAsInFlight(CTxMemPool& mempool, NodeId nodeid, const uint25 state->nBlocksInFlightValidHeaders += it->fValidatedHeaders; if (state->nBlocksInFlight == 1) { // We're starting a block download (batch) from this peer. - state->nDownloadingSince = GetTime().count(); + state->nDownloadingSince = GetTimeMicros(); } if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) { nPeersWithValidatedDownloads++; @@ -619,7 +443,7 @@ static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIV * lNodesAnnouncingHeaderAndIDs, and keeping that list under a certain size by * removing the first element if necessary. */ -static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) { AssertLockHeld(cs_main); CNodeState* nodestate = State(nodeid); @@ -635,19 +459,20 @@ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman& connma return; } } - connman.ForNode(nodeid, [&connman](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); + connman->ForNode(nodeid, [connman](CNode* pfrom){ + AssertLockHeld(cs_main); uint64_t nCMPCTBLOCKVersion = (pfrom->GetLocalServices() & NODE_WITNESS) ? 2 : 1; if (lNodesAnnouncingHeaderAndIDs.size() >= 3) { // As per BIP152, we only get 3 of our peers to announce // blocks using compact encodings. - connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [&connman, nCMPCTBLOCKVersion](CNode* pnodeStop){ - connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion)); + connman->ForNode(lNodesAnnouncingHeaderAndIDs.front(), [connman, nCMPCTBLOCKVersion](CNode* pnodeStop){ + AssertLockHeld(cs_main); + connman->PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion)); return true; }); lNodesAnnouncingHeaderAndIDs.pop_front(); } - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion)); + connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion)); lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId()); return true; }); @@ -665,7 +490,7 @@ static bool TipMayBeStale(const Consensus::Params &consensusParams) EXCLUSIVE_LO static bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - return ::ChainActive().Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; + return chainActive.Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; } static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -691,7 +516,7 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec // Make sure pindexBestKnownBlock is up to date, we'll need it. ProcessBlockAvailability(nodeid); - if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < ::ChainActive().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < chainActive.Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has nothing interesting. return; } @@ -699,7 +524,7 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec if (state->pindexLastCommonBlock == nullptr) { // Bootstrap quickly by guessing a parent of our best tip is the forking point. // Guessing wrong in either direction is not a problem. - state->pindexLastCommonBlock = ::ChainActive()[std::min(state->pindexBestKnownBlock->nHeight, ::ChainActive().Height())]; + state->pindexLastCommonBlock = chainActive[std::min(state->pindexBestKnownBlock->nHeight, chainActive.Height())]; } // If the peer reorganized, our previous pindexLastCommonBlock may not be an ancestor @@ -741,8 +566,8 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec // We wouldn't download this block or its descendants from this peer. return; } - if (pindex->nStatus & BLOCK_HAVE_DATA || ::ChainActive().Contains(pindex)) { - if (pindex->HaveTxsDownloaded()) + if (pindex->nStatus & BLOCK_HAVE_DATA || chainActive.Contains(pindex)) { + if (pindex->nChainTx) state->pindexLastCommonBlock = pindex; } else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) { // The block is not already downloaded, and not yet in flight. @@ -768,33 +593,6 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec } // namespace -void PeerManager::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, std::chrono::microseconds current_time) -{ - AssertLockHeld(::cs_main); // For m_txrequest - NodeId nodeid = node.GetId(); - if (!node.HasPermission(PF_RELAY) && m_txrequest.Count(nodeid) >= MAX_PEER_TX_ANNOUNCEMENTS) { - // Too many queued announcements from this peer - return; - } - const CNodeState* state = State(nodeid); - - // Decide the TxRequestTracker parameters for this announcement: - // - "preferred": if fPreferredDownload is set (= outbound, or PF_NOBAN permission) - // - "reqtime": current time plus delays for: - // - NONPREF_PEER_TX_DELAY for announcements from non-preferred connections - // - TXID_RELAY_DELAY for txid announcements while wtxid peers are available - // - OVERLOADED_PEER_TX_DELAY for announcements from peers which have at least - // MAX_PEER_TX_REQUEST_IN_FLIGHT requests in flight (and don't have PF_RELAY). - auto delay = std::chrono::microseconds{0}; - const bool preferred = state->fPreferredDownload; - if (!preferred) delay += NONPREF_PEER_TX_DELAY; - if (!gtxid.IsWtxid() && g_wtxid_relay_peers > 0) delay += TXID_RELAY_DELAY; - const bool overloaded = !node.HasPermission(PF_RELAY) && - m_txrequest.CountInFlight(nodeid) >= MAX_PEER_TX_REQUEST_IN_FLIGHT; - if (overloaded) delay += OVERLOADED_PEER_TX_DELAY; - m_txrequest.ReceivedInv(nodeid, gtxid, preferred, current_time + delay); -} - // This function is used for testing the stale tip eviction logic, see // denialofservice_tests.cpp void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) @@ -804,66 +602,35 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) if (state) state->m_last_block_announcement = time_in_seconds; } -void PeerManager::InitializeNode(CNode *pnode) { +// Returns true for outbound peers, excluding manual connections, feelers, and +// one-shots +static bool IsOutboundDisconnectionCandidate(const CNode *node) +{ + return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot); +} + +void PeerLogicValidation::InitializeNode(CNode *pnode) { CAddress addr = pnode->addr; std::string addrName = pnode->GetAddrName(); NodeId nodeid = pnode->GetId(); { LOCK(cs_main); - mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, pnode->IsInboundConn(), pnode->IsManualConn())); - assert(m_txrequest.Count(nodeid) == 0); - } - { - PeerRef peer = std::make_shared(nodeid); - LOCK(g_peer_mutex); - g_peer_map.emplace_hint(g_peer_map.end(), nodeid, std::move(peer)); - } - if (!pnode->IsInboundConn()) { - PushNodeVersion(*pnode, m_connman, GetTime()); - } -} - -void PeerManager::ReattemptInitialBroadcast(CScheduler& scheduler) const -{ - std::set unbroadcast_txids = m_mempool.GetUnbroadcastTxs(); - - for (const auto& txid : unbroadcast_txids) { - CTransactionRef tx = m_mempool.get(txid); - - if (tx != nullptr) { - LOCK(cs_main); - RelayTransaction(txid, tx->GetWitnessHash(), m_connman); - } else { - m_mempool.RemoveUnbroadcastTx(txid, true); - } + mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName))); } - - // Schedule next run for 10-15 minutes in the future. - // We add randomness on every cycle to avoid the possibility of P2P fingerprinting. - const std::chrono::milliseconds delta = std::chrono::minutes{10} + GetRandMillis(std::chrono::minutes{5}); - scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); + if(!pnode->fInbound) + PushNodeVersion(pnode, connman, GetTime()); } -void PeerManager::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) { - NodeId nodeid = node.GetId(); +void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTime) { fUpdateConnectionTime = false; LOCK(cs_main); - int misbehavior{0}; - { - PeerRef peer = GetPeerRef(nodeid); - assert(peer != nullptr); - misbehavior = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score); - LOCK(g_peer_mutex); - g_peer_map.erase(nodeid); - } CNodeState *state = State(nodeid); assert(state != nullptr); if (state->fSyncStarted) nSyncStarted--; - if (misbehavior == 0 && state->fCurrentlyConnected && !node.IsBlockOnlyConn()) { - // Note: we avoid changing visible addrman state for block-relay-only peers + if (state->nMisbehavior == 0 && state->fCurrentlyConnected) { fUpdateConnectionTime = true; } @@ -871,14 +638,11 @@ void PeerManager::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) { mapBlocksInFlight.erase(entry.hash); } EraseOrphansFor(nodeid); - m_txrequest.DisconnectedPeer(nodeid); nPreferredDownload -= state->fPreferredDownload; nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); assert(nPeersWithValidatedDownloads >= 0); g_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect; assert(g_outbound_peers_with_protect_from_disconnect >= 0); - g_wtxid_relay_peers -= state->m_wtxid_relay; - assert(g_wtxid_relay_peers >= 0); mapNodeState.erase(nodeid); @@ -888,30 +652,22 @@ void PeerManager::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) { assert(nPreferredDownload == 0); assert(nPeersWithValidatedDownloads == 0); assert(g_outbound_peers_with_protect_from_disconnect == 0); - assert(g_wtxid_relay_peers == 0); - assert(m_txrequest.Size() == 0); } LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid); } bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) { - { - LOCK(cs_main); - CNodeState* state = State(nodeid); - if (state == nullptr) - return false; - stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1; - stats.nCommonHeight = state->pindexLastCommonBlock ? state->pindexLastCommonBlock->nHeight : -1; - for (const QueuedBlock& queue : state->vBlocksInFlight) { - if (queue.pindex) - stats.vHeightInFlight.push_back(queue.pindex->nHeight); - } + LOCK(cs_main); + CNodeState *state = State(nodeid); + if (state == nullptr) + return false; + stats.nMisbehavior = state->nMisbehavior; + stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1; + stats.nCommonHeight = state->pindexLastCommonBlock ? state->pindexLastCommonBlock->nHeight : -1; + for (const QueuedBlock& queue : state->vBlocksInFlight) { + if (queue.pindex) + stats.vHeightInFlight.push_back(queue.pindex->nHeight); } - - PeerRef peer = GetPeerRef(nodeid); - if (peer == nullptr) return false; - stats.m_misbehavior_score = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score); - return true; } @@ -951,11 +707,8 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE return false; } - auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, g_orphan_list.size()}); + auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME}); assert(ret.second); - g_orphan_list.push_back(ret.first); - // Allow for lookups in the orphan pool by wtxid, as well as txid - g_orphans_by_wtxid.emplace(tx->GetWitnessHash(), ret.first); for (const CTxIn& txin : tx->vin) { mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first); } @@ -981,19 +734,6 @@ int static EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) if (itPrev->second.empty()) mapOrphanTransactionsByPrev.erase(itPrev); } - - size_t old_pos = it->second.list_pos; - assert(g_orphan_list[old_pos] == it); - if (old_pos + 1 != g_orphan_list.size()) { - // Unless we're deleting the last entry in g_orphan_list, move the last - // entry to the position we're deleting. - auto it_last = g_orphan_list.back(); - g_orphan_list[old_pos] = it_last; - it_last->second.list_pos = old_pos; - } - g_orphan_list.pop_back(); - g_orphans_by_wtxid.erase(it->second.tx->GetWitnessHash()); - mapOrphanTransactions.erase(it); return 1; } @@ -1040,111 +780,47 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); } - FastRandomContext rng; while (mapOrphanTransactions.size() > nMaxOrphans) { // Evict a random orphan: - size_t randompos = rng.randrange(g_orphan_list.size()); - EraseOrphanTx(g_orphan_list[randompos]->first); + uint256 randomhash = GetRandHash(); + std::map::iterator it = mapOrphanTransactions.lower_bound(randomhash); + if (it == mapOrphanTransactions.end()) + it = mapOrphanTransactions.begin(); + EraseOrphanTx(it->first); ++nEvicted; } return nEvicted; } -void PeerManager::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) +/** + * Mark a misbehaving peer to be banned depending upon the value of `-banscore`. + */ +void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - assert(howmuch > 0); + if (howmuch == 0) + return; - PeerRef peer = GetPeerRef(pnode); - if (peer == nullptr) return; + CNodeState *state = State(pnode); + if (state == nullptr) + return; - LOCK(peer->m_misbehavior_mutex); - peer->m_misbehavior_score += howmuch; - const std::string message_prefixed = message.empty() ? "" : (": " + message); - if (peer->m_misbehavior_score >= DISCOURAGEMENT_THRESHOLD && peer->m_misbehavior_score - howmuch < DISCOURAGEMENT_THRESHOLD) { - LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d) DISCOURAGE THRESHOLD EXCEEDED%s\n", pnode, peer->m_misbehavior_score - howmuch, peer->m_misbehavior_score, message_prefixed); - peer->m_should_discourage = true; - } else { - LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s\n", pnode, peer->m_misbehavior_score - howmuch, peer->m_misbehavior_score, message_prefixed); - } + state->nMisbehavior += howmuch; + int banscore = gArgs.GetArg("-banscore", DEFAULT_BANSCORE_THRESHOLD); + std::string message_prefixed = message.empty() ? "" : (": " + message); + if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore) + { + LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d) BAN THRESHOLD EXCEEDED%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed); + state->fShouldBan = true; + } else + LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d)%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed); } -bool PeerManager::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, - bool via_compact_block, const std::string& message) -{ - switch (state.GetResult()) { - case BlockValidationResult::BLOCK_RESULT_UNSET: - break; - // The node is providing invalid data: - case BlockValidationResult::BLOCK_CONSENSUS: - case BlockValidationResult::BLOCK_MUTATED: - if (!via_compact_block) { - Misbehaving(nodeid, 100, message); - return true; - } - break; - case BlockValidationResult::BLOCK_CACHED_INVALID: - { - LOCK(cs_main); - CNodeState *node_state = State(nodeid); - if (node_state == nullptr) { - break; - } - // Discourage outbound (but not inbound) peers if on an invalid chain. - // Exempt HB compact block peers and manual connections. - if (!via_compact_block && !node_state->m_is_inbound && !node_state->m_is_manual_connection) { - Misbehaving(nodeid, 100, message); - return true; - } - break; - } - case BlockValidationResult::BLOCK_INVALID_HEADER: - case BlockValidationResult::BLOCK_CHECKPOINT: - case BlockValidationResult::BLOCK_INVALID_PREV: - Misbehaving(nodeid, 100, message); - return true; - // Conflicting (but not necessarily invalid) data or different policy: - case BlockValidationResult::BLOCK_MISSING_PREV: - // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) - Misbehaving(nodeid, 10, message); - return true; - case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: - case BlockValidationResult::BLOCK_TIME_FUTURE: - break; - } - if (message != "") { - LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); - } - return false; -} -bool PeerManager::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message) -{ - switch (state.GetResult()) { - case TxValidationResult::TX_RESULT_UNSET: - break; - // The node is providing invalid data: - case TxValidationResult::TX_CONSENSUS: - Misbehaving(nodeid, 100, message); - return true; - // Conflicting (but not necessarily invalid) data or different policy: - case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE: - case TxValidationResult::TX_INPUTS_NOT_STANDARD: - case TxValidationResult::TX_NOT_STANDARD: - case TxValidationResult::TX_MISSING_INPUTS: - case TxValidationResult::TX_PREMATURE_SPEND: - case TxValidationResult::TX_WITNESS_MUTATED: - case TxValidationResult::TX_WITNESS_STRIPPED: - case TxValidationResult::TX_CONFLICT: - case TxValidationResult::TX_MEMPOOL_POLICY: - break; - } - if (message != "") { - LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); - } - return false; -} + + + ////////////////////////////////////////////////////////////////////////////// @@ -1156,122 +832,68 @@ bool PeerManager::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& s // active chain if they are no more than a month older (both in time, and in // best equivalent proof of work) than the best header chain we know about and // we fully-validated them at some point. -static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams) { AssertLockHeld(cs_main); - if (::ChainActive().Contains(pindex)) return true; + if (chainActive.Contains(pindex)) return true; return pindex->IsValid(BLOCK_VALID_SCRIPTS) && (pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() < STALE_RELAY_AGE_LIMIT) && (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); } -PeerManager::PeerManager(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool) - : m_chainparams(chainparams), - m_connman(connman), - m_banman(banman), - m_chainman(chainman), - m_mempool(pool), - m_stale_tip_check_time(0) -{ +PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler, bool enable_bip61) + : connman(connmanIn), m_stale_tip_check_time(0), m_enable_bip61(enable_bip61) { + // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); - // Blocks don't typically have more than 4000 transactions, so this should - // be at least six blocks (~1 hr) worth of transactions that we can store, - // inserting both a txid and wtxid for every observed transaction. - // If the number of transactions appearing in a block goes up, or if we are - // seeing getdata requests more than an hour after initial announcement, we - // can increase this number. - // The false positive rate of 1/1M should come out to less than 1 - // transaction per day that would be inadvertently ignored (which is the - // same probability that we have in the reject filter). - g_recent_confirmed_transactions.reset(new CRollingBloomFilter(48000, 0.000001)); - + const Consensus::Params& consensusParams = Params().GetConsensus(); // Stale tip checking and peer eviction are on two different timers, but we // don't want them to get out of sync due to drift in the scheduler, so we // combine them in one function and schedule at the quicker (peer-eviction) // timer. static_assert(EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, "peer eviction timer should be less than stale tip check timer"); - scheduler.scheduleEvery([this] { this->CheckForStaleTipAndEvictPeers(); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL}); - - // schedule next run for 10-15 minutes in the future - const std::chrono::milliseconds delta = std::chrono::minutes{10} + GetRandMillis(std::chrono::minutes{5}); - scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); + scheduler.scheduleEvery(std::bind(&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this, consensusParams), EXTRA_PEER_CHECK_INTERVAL * 1000); } /** * Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected - * block, remember the recently confirmed transactions, and delete tracked - * announcements for them. Also save the time of the last tip update. + * block. Also save the time of the last tip update. */ -void PeerManager::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) -{ - { - LOCK(g_cs_orphans); - - std::vector vOrphanErase; - - for (const CTransactionRef& ptx : pblock->vtx) { - const CTransaction& tx = *ptx; +void PeerLogicValidation::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex, const std::vector& vtxConflicted) { + LOCK(g_cs_orphans); - // Which orphan pool entries must we evict? - for (const auto& txin : tx.vin) { - auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout); - if (itByPrev == mapOrphanTransactionsByPrev.end()) continue; - for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) { - const CTransaction& orphanTx = *(*mi)->second.tx; - const uint256& orphanHash = orphanTx.GetHash(); - vOrphanErase.push_back(orphanHash); - } - } - } + std::vector vOrphanErase; - // Erase orphan transactions included or precluded by this block - if (vOrphanErase.size()) { - int nErased = 0; - for (const uint256& orphanHash : vOrphanErase) { - nErased += EraseOrphanTx(orphanHash); - } - LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); - } + for (const CTransactionRef& ptx : pblock->vtx) { + const CTransaction& tx = *ptx; - g_last_tip_update = GetTime(); - } - { - LOCK(g_cs_recent_confirmed_transactions); - for (const auto& ptx : pblock->vtx) { - g_recent_confirmed_transactions->insert(ptx->GetHash()); - if (ptx->GetHash() != ptx->GetWitnessHash()) { - g_recent_confirmed_transactions->insert(ptx->GetWitnessHash()); + // Which orphan pool entries must we evict? + for (const auto& txin : tx.vin) { + auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout); + if (itByPrev == mapOrphanTransactionsByPrev.end()) continue; + for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) { + const CTransaction& orphanTx = *(*mi)->second.tx; + const uint256& orphanHash = orphanTx.GetHash(); + vOrphanErase.push_back(orphanHash); } } } - { - LOCK(cs_main); - for (const auto& ptx : pblock->vtx) { - m_txrequest.ForgetTxHash(ptx->GetHash()); - m_txrequest.ForgetTxHash(ptx->GetWitnessHash()); + + // Erase orphan transactions included or precluded by this block + if (vOrphanErase.size()) { + int nErased = 0; + for (uint256 &orphanHash : vOrphanErase) { + nErased += EraseOrphanTx(orphanHash); } + LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); } -} -void PeerManager::BlockDisconnected(const std::shared_ptr &block, const CBlockIndex* pindex) -{ - // To avoid relay problems with transactions that were previously - // confirmed, clear our filter of recently confirmed transactions whenever - // there's a reorg. - // This means that in a 1-block reorg (where 1 block is disconnected and - // then another block reconnected), our filter will drop to having only one - // block's worth of transactions in it, but that should be fine, since - // presumably the most common case of relaying a confirmed transaction - // should be just after a new block containing it is found. - LOCK(g_cs_recent_confirmed_transactions); - g_recent_confirmed_transactions->reset(); + g_last_tip_update = GetTime(); } // All of the following cache a recent block, and are protected by cs_most_recent_block -static RecursiveMutex cs_most_recent_block; +static CCriticalSection cs_most_recent_block; static std::shared_ptr most_recent_block GUARDED_BY(cs_most_recent_block); static std::shared_ptr most_recent_compact_block GUARDED_BY(cs_most_recent_block); static uint256 most_recent_block_hash GUARDED_BY(cs_most_recent_block); @@ -1281,7 +903,7 @@ static bool fWitnessesPresentInMostRecentCompactBlock GUARDED_BY(cs_most_recent_ * Maintain state about the best-seen block and fast-announce a compact block * to compatible peers. */ -void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr& pblock) { +void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr& pblock) { std::shared_ptr pcmpctblock = std::make_shared (*pblock, true); const CNetMsgMaker msgMaker(PROTOCOL_VERSION); @@ -1292,7 +914,7 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ return; nHighestFastAnnounce = pindex->nHeight; - bool fWitnessEnabled = IsWitnessEnabled(pindex->pprev, m_chainparams.GetConsensus()); + bool fWitnessEnabled = IsWitnessEnabled(pindex->pprev, Params().GetConsensus()); uint256 hashBlock(pblock->GetHash()); { @@ -1303,11 +925,11 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled; } - m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); + connman->ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) { + AssertLockHeld(cs_main); // TODO: Avoid the repeated-serialization here - if (pnode->GetCommonVersion() < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) + if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) return; ProcessBlockAvailability(pnode->GetId()); CNodeState &state = *State(pnode->GetId()); @@ -1316,9 +938,9 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ if (state.fPreferHeaderAndIDs && (!fWitnessEnabled || state.fWantsCmpctWitness) && !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) { - LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerManager::NewPoWValidBlock", + LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerLogicValidation::NewPoWValidBlock", hashBlock.ToString(), pnode->GetId()); - m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock)); + connman->PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock)); state.pindexBestHeaderSent = pindex; } }); @@ -1326,11 +948,11 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ /** * Update our best height and announce any block hashes which weren't previously - * in ::ChainActive() to our peers. + * in chainActive to our peers. */ -void PeerManager::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { +void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { const int nNewHeight = pindexNew->nHeight; - m_connman.SetBestHeight(nNewHeight); + connman->SetBestHeight(nNewHeight); SetServiceFlagsIBDCache(!fInitialDownload); if (!fInitialDownload) { @@ -1347,34 +969,38 @@ void PeerManager::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockInde } } // Relay inventory, but don't relay old inventory during initial block download. - m_connman.ForEachNode([nNewHeight, &vHashes](CNode* pnode) { - LOCK(pnode->cs_inventory); + connman->ForEachNode([nNewHeight, &vHashes](CNode* pnode) { if (nNewHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 0)) { for (const uint256& hash : reverse_iterate(vHashes)) { - pnode->vBlockHashesToAnnounce.push_back(hash); + pnode->PushBlockHash(hash); } } }); - m_connman.WakeMessageHandler(); + connman->WakeMessageHandler(); } + + nTimeBestReceived = GetTime(); } /** - * Handle invalid block rejection and consequent peer discouragement, maintain which + * Handle invalid block rejection and consequent peer banning, maintain which * peers announce compact blocks. */ -void PeerManager::BlockChecked(const CBlock& block, const BlockValidationState& state) { +void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationState& state) { LOCK(cs_main); const uint256 hash(block.GetHash()); std::map>::iterator it = mapBlockSource.find(hash); - // If the block failed validation, we know where it came from and we're still connected - // to that peer, maybe punish. - if (state.IsInvalid() && - it != mapBlockSource.end() && - State(it->second.first)) { - MaybePunishNodeForBlock(/*nodeid=*/ it->second.first, state, /*via_compact_block=*/ !it->second.second); + int nDoS = 0; + if (state.IsInvalid(nDoS)) { + // Don't send reject message with code 0 or an internal reject code. + if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) { + CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), hash}; + State(it->second.first)->rejects.push_back(reject); + if (nDoS > 0 && it->second.second) + Misbehaving(it->second.first, nDoS); + } } // Check that: // 1. The block is valid @@ -1383,10 +1009,10 @@ void PeerManager::BlockChecked(const CBlock& block, const BlockValidationState& // the tip yet so we have no way to check this directly here. Instead we // just check that there are currently no other blocks in flight. else if (state.IsValid() && - !::ChainstateActive().IsInitialBlockDownload() && + !IsInitialBlockDownload() && mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) { if (it != mapBlockSource.end()) { - MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first, m_connman); + MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first, connman); } } if (it != mapBlockSource.end()) @@ -1399,76 +1025,67 @@ void PeerManager::BlockChecked(const CBlock& block, const BlockValidationState& // -bool static AlreadyHaveTx(const GenTxid& gtxid, const CTxMemPool& mempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - assert(recentRejects); - if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip) { - // If the chain tip has changed previously rejected transactions - // might be now valid, e.g. due to a nLockTime'd tx becoming valid, - // or a double-spend. Reset the rejects filter and give those - // txs a second chance. - hashRecentRejectsChainTip = ::ChainActive().Tip()->GetBlockHash(); - recentRejects->reset(); - } + switch (inv.type) + { + case MSG_TX: + case MSG_WITNESS_TX: + { + assert(recentRejects); + if (chainActive.Tip()->GetBlockHash() != hashRecentRejectsChainTip) + { + // If the chain tip has changed previously rejected transactions + // might be now valid, e.g. due to a nLockTime'd tx becoming valid, + // or a double-spend. Reset the rejects filter and give those + // txs a second chance. + hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash(); + recentRejects->reset(); + } - const uint256& hash = gtxid.GetHash(); + { + LOCK(g_cs_orphans); + if (mapOrphanTransactions.count(inv.hash)) return true; + } - { - LOCK(g_cs_orphans); - if (!gtxid.IsWtxid() && mapOrphanTransactions.count(hash)) { - return true; - } else if (gtxid.IsWtxid() && g_orphans_by_wtxid.count(hash)) { - return true; + return recentRejects->contains(inv.hash) || + mempool.exists(inv.hash) || + pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1 + pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1)); } + case MSG_BLOCK: + case MSG_WITNESS_BLOCK: + return LookupBlockIndex(inv.hash) != nullptr; } - - { - LOCK(g_cs_recent_confirmed_transactions); - if (g_recent_confirmed_transactions->contains(hash)) return true; - } - - return recentRejects->contains(hash) || mempool.exists(gtxid); -} - -bool static AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -{ - return LookupBlockIndex(block_hash) != nullptr; + // Don't know what it is, just say we already got one + return true; } -void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman) +static void RelayTransaction(const CTransaction& tx, CConnman* connman) { - connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); - - CNodeState* state = State(pnode->GetId()); - if (state == nullptr) return; - if (state->m_wtxid_relay) { - pnode->PushTxInventory(wtxid); - } else { - pnode->PushTxInventory(txid); - } + CInv inv(MSG_TX, tx.GetHash()); + connman->ForEachNode([&inv](CNode* pnode) + { + pnode->PushInventory(inv); }); } -static void RelayAddress(const CAddress& addr, bool fReachable, const CConnman& connman) +static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connman) { - if (!fReachable && !addr.IsRelayable()) return; + unsigned int nRelayNodes = fReachable ? 2 : 1; // limited relaying of addresses outside our network(s) // Relay to a limited number of other nodes // Use deterministic randomness to send to the same nodes for 24 hours - // at a time so the m_addr_knowns of the chosen nodes prevent repeats + // at a time so the addrKnowns of the chosen nodes prevent repeats uint64_t hashAddr = addr.GetHash(); - const CSipHasher hasher = connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY).Write(hashAddr << 32).Write((GetTime() + hashAddr) / (24 * 60 * 60)); + const CSipHasher hasher = connman->GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY).Write(hashAddr << 32).Write((GetTime() + hashAddr) / (24*60*60)); FastRandomContext insecure_rand; - // Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers. - unsigned int nRelayNodes = (fReachable || (hasher.Finalize() & 1)) ? 2 : 1; - std::array,2> best{{{0, nullptr}, {0, nullptr}}}; assert(nRelayNodes <= best.size()); auto sortfunc = [&best, &hasher, nRelayNodes](CNode* pnode) { - if (pnode->RelayAddrsWithConn()) { + if (pnode->nVersion >= CADDR_TIME_VERSION) { uint64_t hashKey = CSipHasher(hasher).Write(pnode->GetId()).Finalize(); for (unsigned int i = 0; i < nRelayNodes; i++) { if (hashKey > best[i].first) { @@ -1486,10 +1103,10 @@ static void RelayAddress(const CAddress& addr, bool fReachable, const CConnman& } }; - connman.ForEachNodeThen(std::move(sortfunc), std::move(pushfunc)); + connman->ForEachNodeThen(std::move(sortfunc), std::move(pushfunc)); } -void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, const CInv& inv, CConnman& connman) +void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, const CInv& inv, CConnman* connman) { bool send = false; std::shared_ptr a_recent_block; @@ -1508,7 +1125,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c LOCK(cs_main); const CBlockIndex* pindex = LookupBlockIndex(inv.hash); if (pindex) { - if (pindex->HaveTxsDownloaded() && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && + if (pindex->nChainTx && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && pindex->IsValid(BLOCK_VALID_TREE)) { // If we have the block and all of its parents, but have not yet validated it, // we might be in the middle of connecting it (ie in the unlock of cs_main @@ -1520,9 +1137,9 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c } } // release cs_main before calling ActivateBestChain if (need_activate_chain) { - BlockValidationState state; - if (!ActivateBestChain(state, chainparams, a_recent_block)) { - LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); + CValidationState state; + if (!ActivateBestChain(state, Params(), a_recent_block)) { + LogPrint(BCLog::NET, "failed to activate chain (%s)\n", FormatStateMessage(state)); } } @@ -1531,30 +1148,28 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c if (pindex) { send = BlockRequestAllowed(pindex, consensusParams); if (!send) { - LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId()); + LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom->GetId()); } } - const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); + const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks - if (send && - connman.OutboundTargetReached(true) && - (((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) && - !pfrom.HasPermission(PF_DOWNLOAD) // nodes with the download permission may exceed target - ) { - LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom.GetId()); + // never disconnect whitelisted nodes + if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted) + { + LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom->GetId()); //disconnect node - pfrom.fDisconnect = true; + pfrom->fDisconnect = true; send = false; } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold - if (send && !pfrom.HasPermission(PF_NOBAN) && ( - (((pfrom.GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom.GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (::ChainActive().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) + if (send && !pfrom->fWhitelisted && ( + (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (chainActive.Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) )) { - LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom.GetId()); + LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom->GetId()); //disconnect node and prevent it from stalling (would otherwise wait for the missing block) - pfrom.fDisconnect = true; + pfrom->fDisconnect = true; send = false; } // Pruned nodes may have deleted the block, so check whether @@ -1564,14 +1179,14 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c std::shared_ptr pblock; if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) { pblock = a_recent_block; - } else if (inv.IsMsgWitnessBlk()) { + } else if (inv.type == MSG_WITNESS_BLOCK) { // Fast-path: in this case it is possible to serve the block directly from disk, // as the network format matches the format on disk std::vector block_data; if (!ReadRawBlockFromDisk(block_data, pindex, chainparams.MessageStart())) { assert(!"cannot load block from disk"); } - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data))); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data))); // Don't set pblock as we've sent the block } else { // Send block from disk @@ -1581,22 +1196,23 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c pblock = pblockRead; } if (pblock) { - if (inv.IsMsgBlk()) { - connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock)); - } else if (inv.IsMsgWitnessBlk()) { - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); - } else if (inv.IsMsgFilteredBlk()) { + if (inv.type == MSG_BLOCK) + connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock)); + else if (inv.type == MSG_WITNESS_BLOCK) + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); + else if (inv.type == MSG_FILTERED_BLOCK) + { bool sendMerkleBlock = false; CMerkleBlock merkleBlock; - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - if (pfrom.m_tx_relay->pfilter) { + { + LOCK(pfrom->cs_filter); + if (pfrom->pfilter) { sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter); + merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter); } } if (sendMerkleBlock) { - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock)); // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see // This avoids hurting performance by pointlessly requiring a round-trip // Note that there is currently no way for a node to request any single transactions we didn't send here - @@ -1605,204 +1221,149 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c // however we MUST always provide at least what the remote peer needs typedef std::pair PairType; for (PairType& pair : merkleBlock.vMatchedTxn) - connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first])); + connman->PushMessage(pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first])); } // else // no response - } else if (inv.IsMsgCmpctBlk()) { + } + else if (inv.type == MSG_CMPCT_BLOCK) + { // If a peer is asking for old blocks, we're almost guaranteed // they won't have a useful mempool to match against a compact block, // and we don't feel like constructing the object for them, so // instead we respond with the full, non-compact block. - bool fPeerWantsWitness = State(pfrom.GetId())->fWantsCmpctWitness; + bool fPeerWantsWitness = State(pfrom->GetId())->fWantsCmpctWitness; int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - if (CanDirectFetch(consensusParams) && pindex->nHeight >= ::ChainActive().Height() - MAX_CMPCTBLOCK_DEPTH) { + if (CanDirectFetch(consensusParams) && pindex->nHeight >= chainActive.Height() - MAX_CMPCTBLOCK_DEPTH) { if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); } else { CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); } } else { - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); } } } // Trigger the peer node to send a getblocks request for the next batch of inventory - if (inv.hash == pfrom.hashContinue) + if (inv.hash == pfrom->hashContinue) { - // Send immediately. This must send even if redundant, + // Bypass PushInventory, this must send even if redundant, // and we want it right after the last block so they don't // wait for other stuff first. std::vector vInv; - vInv.push_back(CInv(MSG_BLOCK, ::ChainActive().Tip()->GetBlockHash())); - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv)); - pfrom.hashContinue.SetNull(); + vInv.push_back(CInv(MSG_BLOCK, chainActive.Tip()->GetBlockHash())); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::INV, vInv)); + pfrom->hashContinue.SetNull(); } } } -//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). -static CTransactionRef FindTxForGetData(const CTxMemPool& mempool, const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main) +void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnman* connman, const std::atomic& interruptMsgProc) { - auto txinfo = mempool.info(gtxid); - if (txinfo.tx) { - // If a TX could have been INVed in reply to a MEMPOOL request, - // or is older than UNCONDITIONAL_RELAY_DELAY, permit the request - // unconditionally. - if ((mempool_req.count() && txinfo.m_time <= mempool_req) || txinfo.m_time <= now - UNCONDITIONAL_RELAY_DELAY) { - return std::move(txinfo.tx); - } - } + AssertLockNotHeld(cs_main); + std::deque::iterator it = pfrom->vRecvGetData.begin(); + std::vector vNotFound; + const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); { LOCK(cs_main); - // Otherwise, the transaction must have been announced recently. - if (State(peer.GetId())->m_recently_announced_invs.contains(gtxid.GetHash())) { - // If it was, it can be relayed from either the mempool... - if (txinfo.tx) return std::move(txinfo.tx); - // ... or the relay pool. - auto mi = mapRelay.find(gtxid.GetHash()); - if (mi != mapRelay.end()) return mi->second; - } - } - - return {}; -} - -void static ProcessGetData(CNode& pfrom, Peer& peer, const CChainParams& chainparams, CConnman& connman, CTxMemPool& mempool, const std::atomic& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(!cs_main, peer.m_getdata_requests_mutex) -{ - AssertLockNotHeld(cs_main); - std::deque::iterator it = peer.m_getdata_requests.begin(); - std::vector vNotFound; - const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); - - const std::chrono::seconds now = GetTime(); - // Get last mempool request time - const std::chrono::seconds mempool_req = pfrom.m_tx_relay != nullptr ? pfrom.m_tx_relay->m_last_mempool_req.load() - : std::chrono::seconds::min(); - - // Process as many TX items from the front of the getdata queue as - // possible, since they're common and it's efficient to batch process - // them. - while (it != peer.m_getdata_requests.end() && it->IsGenTxMsg()) { - if (interruptMsgProc) return; - // The send buffer provides backpressure. If there's no space in - // the buffer, pause processing until the next call. - if (pfrom.fPauseSend) break; - - const CInv &inv = *it++; - - if (pfrom.m_tx_relay == nullptr) { - // Ignore GETDATA requests for transactions from blocks-only peers. - continue; - } + while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) { + if (interruptMsgProc) + return; + // Don't bother if send buffer is too full to respond anyway + if (pfrom->fPauseSend) + break; - CTransactionRef tx = FindTxForGetData(mempool, pfrom, ToGenTxid(inv), mempool_req, now); - if (tx) { - // WTX and WITNESS_TX imply we serialize with witness - int nSendFlags = (inv.IsMsgTx() ? SERIALIZE_TRANSACTION_NO_WITNESS : 0); - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *tx)); - mempool.RemoveUnbroadcastTx(tx->GetHash()); - // As we're going to send tx, make sure its unconfirmed parents are made requestable. - std::vector parent_ids_to_add; - { - LOCK(mempool.cs); - auto txiter = mempool.GetIter(tx->GetHash()); - if (txiter) { - const CTxMemPoolEntry::Parents& parents = (*txiter)->GetMemPoolParentsConst(); - parent_ids_to_add.reserve(parents.size()); - for (const CTxMemPoolEntry& parent : parents) { - if (parent.GetTime() > now - UNCONDITIONAL_RELAY_DELAY) { - parent_ids_to_add.push_back(parent.GetTx().GetHash()); - } - } + const CInv &inv = *it; + it++; + + // Send stream from relay memory + bool push = false; + auto mi = mapRelay.find(inv.hash); + int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0); + if (mi != mapRelay.end()) { + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second)); + push = true; + } else if (pfrom->timeLastMempoolReq) { + auto txinfo = mempool.info(inv.hash); + // To protect privacy, do not answer getdata using the mempool when + // that TX couldn't have been INVed in reply to a MEMPOOL request. + if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) { + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx)); + push = true; } } - for (const uint256& parent_txid : parent_ids_to_add) { - // Relaying a transaction with a recent but unconfirmed parent. - if (WITH_LOCK(pfrom.m_tx_relay->cs_tx_inventory, return !pfrom.m_tx_relay->filterInventoryKnown.contains(parent_txid))) { - LOCK(cs_main); - State(pfrom.GetId())->m_recently_announced_invs.insert(parent_txid); - } + if (!push) { + vNotFound.push_back(inv); } - } else { - vNotFound.push_back(inv); } - } + } // release cs_main - // Only process one BLOCK item per call, since they're uncommon and can be - // expensive to process. - if (it != peer.m_getdata_requests.end() && !pfrom.fPauseSend) { - const CInv &inv = *it++; - if (inv.IsGenBlkMsg()) { + if (it != pfrom->vRecvGetData.end() && !pfrom->fPauseSend) { + const CInv &inv = *it; + if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) { + it++; ProcessGetBlockData(pfrom, chainparams, inv, connman); } - // else: If the first item on the queue is an unknown type, we erase it - // and continue processing the queue on the next call. } - peer.m_getdata_requests.erase(peer.m_getdata_requests.begin(), it); + pfrom->vRecvGetData.erase(pfrom->vRecvGetData.begin(), it); if (!vNotFound.empty()) { // Let the peer know that we didn't find what it asked for, so it doesn't - // have to wait around forever. - // SPV clients care about this message: it's needed when they are - // recursively walking the dependencies of relevant unconfirmed - // transactions. SPV clients want to do that because they want to know - // about (and store and rebroadcast and risk analyze) the dependencies - // of transactions relevant to them, without having to download the - // entire memory pool. - // Also, other nodes can use these messages to automatically request a - // transaction from some other peer that annnounced it, and stop - // waiting for us to respond. - // In normal operation, we often send NOTFOUND messages for parents of - // transactions that we relay; if a peer is missing a parent, they may - // assume we have them and request the parents from us. - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound)); + // have to wait around forever. Currently only SPV clients actually care + // about this message: it's needed when they are recursively walking the + // dependencies of relevant unconfirmed transactions. SPV clients want to + // do that because they want to know about (and store and rebroadcast and + // risk analyze) the dependencies of transactions relevant to them, without + // having to download the entire memory pool. + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound)); } } -static uint32_t GetFetchFlags(const CNode& pfrom) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { +static uint32_t GetFetchFlags(CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { uint32_t nFetchFlags = 0; - if ((pfrom.GetLocalServices() & NODE_WITNESS) && State(pfrom.GetId())->fHaveWitness) { + if ((pfrom->GetLocalServices() & NODE_WITNESS) && State(pfrom->GetId())->fHaveWitness) { nFetchFlags |= MSG_WITNESS_FLAG; } return nFetchFlags; } -void PeerManager::SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) { +inline void static SendBlockTransactions(const CBlock& block, const BlockTransactionsRequest& req, CNode* pfrom, CConnman* connman) { BlockTransactions resp(req); for (size_t i = 0; i < req.indexes.size(); i++) { if (req.indexes[i] >= block.vtx.size()) { - Misbehaving(pfrom.GetId(), 100, "getblocktxn with out-of-bounds tx indices"); + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 100, strprintf("Peer %d sent us a getblocktxn with out-of-bounds tx indices", pfrom->GetId())); return; } resp.txn[i] = block.vtx[req.indexes[i]]; } LOCK(cs_main); - const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); - int nSendFlags = State(pfrom.GetId())->fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); + const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); + int nSendFlags = State(pfrom->GetId())->fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; + connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } -void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vector& headers, bool via_compact_block) +bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector& headers, const CChainParams& chainparams, bool punish_duplicate_invalid) { - const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); + const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); size_t nCount = headers.size(); if (nCount == 0) { // Nothing interesting. Stop asking this peers for more headers. - return; + return true; } bool received_new_header = false; const CBlockIndex *pindexLast = nullptr; { LOCK(cs_main); - CNodeState *nodestate = State(pfrom.GetId()); + CNodeState *nodestate = State(pfrom->GetId()); // If this looks like it could be a block announcement (nCount < // MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers that @@ -1814,30 +1375,28 @@ void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vectornUnconnectingHeaders++; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256())); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), uint256())); LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", headers[0].GetHash().ToString(), headers[0].hashPrevBlock.ToString(), pindexBestHeader->nHeight, - pfrom.GetId(), nodestate->nUnconnectingHeaders); + pfrom->GetId(), nodestate->nUnconnectingHeaders); // Set hashLastUnknownBlock for this peer, so that if we // eventually get the headers - even from a different peer - // we can use this peer to download. - UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()); - - + UpdateBlockAvailability(pfrom->GetId(), headers.back().GetHash()); if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) { - Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders)); + Misbehaving(pfrom->GetId(), 20); } - return; + return true; } uint256 hashLastBlock; for (const CBlockHeader& header : headers) { if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) { - Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence"); - return; + Misbehaving(pfrom->GetId(), 20, "non-continuous headers sequence"); + return false; } hashLastBlock = header.GetHash(); } @@ -1849,52 +1408,93 @@ void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vector 0) { + Misbehaving(pfrom->GetId(), nDoS, "invalid header received"); + } else { + LogPrint(BCLog::NET, "peer=%d: invalid header received\n", pfrom->GetId()); + } + if (punish_duplicate_invalid && LookupBlockIndex(first_invalid_header.GetHash())) { + // Goal: don't allow outbound peers to use up our outbound + // connection slots if they are on incompatible chains. + // + // We ask the caller to set punish_invalid appropriately based + // on the peer and the method of header delivery (compact + // blocks are allowed to be invalid in some circumstances, + // under BIP 152). + // Here, we try to detect the narrow situation that we have a + // valid block header (ie it was valid at the time the header + // was received, and hence stored in mapBlockIndex) but know the + // block is invalid, and that a peer has announced that same + // block as being on its active chain. + // Disconnect the peer in such a situation. + // + // Note: if the header that is invalid was not accepted to our + // mapBlockIndex at all, that may also be grounds for + // disconnecting the peer, as the chain they are on is likely + // to be incompatible. However, there is a circumstance where + // that does not hold: if the header's timestamp is more than + // 2 hours ahead of our current time. In that case, the header + // may become valid in the future, and we don't want to + // disconnect a peer merely for serving us one too-far-ahead + // block header, to prevent an attacker from splitting the + // network by mining a block right at the 2 hour boundary. + // + // TODO: update the DoS logic (or, rather, rewrite the + // DoS-interface between validation and net_processing) so that + // the interface is cleaner, and so that we disconnect on all the + // reasons that a peer's headers chain is incompatible + // with ours (eg block->nVersion softforks, MTP violations, + // etc), and not just the duplicate-invalid case. + pfrom->fDisconnect = true; + } + return false; } } { LOCK(cs_main); - CNodeState *nodestate = State(pfrom.GetId()); + CNodeState *nodestate = State(pfrom->GetId()); if (nodestate->nUnconnectingHeaders > 0) { - LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom.GetId(), nodestate->nUnconnectingHeaders); + LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom->GetId(), nodestate->nUnconnectingHeaders); } nodestate->nUnconnectingHeaders = 0; assert(pindexLast); - UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash()); + UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash()); // From here, pindexBestKnownBlock should be guaranteed to be non-null, // because it is set in UpdateBlockAvailability. Some nullptr checks // are still present, however, as belt-and-suspenders. - if (received_new_header && pindexLast->nChainWork > ::ChainActive().Tip()->nChainWork) { + if (received_new_header && pindexLast->nChainWork > chainActive.Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } if (nCount == MAX_HEADERS_RESULTS) { // Headers message had its maximum size; the peer may have more headers. - // TODO: optimize: if pindexLast is an ancestor of ::ChainActive().Tip or pindexBestHeader, continue + // TODO: optimize: if pindexLast is an ancestor of chainActive.Tip or pindexBestHeader, continue // from there instead. - LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom.GetId(), pfrom.nStartingHeight); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexLast), uint256())); + LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->GetId(), pfrom->nStartingHeight); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexLast), uint256())); } - bool fCanDirectFetch = CanDirectFetch(m_chainparams.GetConsensus()); + bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus()); // If this set of headers is valid and ends in a block with at least as // much work as our tip, download as much as possible. - if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && ::ChainActive().Tip()->nChainWork <= pindexLast->nChainWork) { + if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && chainActive.Tip()->nChainWork <= pindexLast->nChainWork) { std::vector vToFetch; const CBlockIndex *pindexWalk = pindexLast; // Calculate all the blocks we'd need to switch to pindexLast, up to a limit. - while (pindexWalk && !::ChainActive().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + while (pindexWalk && !chainActive.Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && !mapBlocksInFlight.count(pindexWalk->GetBlockHash()) && - (!IsWitnessEnabled(pindexWalk->pprev, m_chainparams.GetConsensus()) || State(pfrom.GetId())->fHaveWitness)) { + (!IsWitnessEnabled(pindexWalk->pprev, chainparams.GetConsensus()) || State(pfrom->GetId())->fHaveWitness)) { // We don't have this block, and it's not yet in flight. vToFetch.push_back(pindexWalk); } @@ -1904,7 +1504,7 @@ void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vectorGetBlockHash().ToString(), pindexLast->nHeight); @@ -1918,9 +1518,9 @@ void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vectorGetBlockHash())); - MarkBlockAsInFlight(m_mempool, pfrom.GetId(), pindex->GetBlockHash(), pindex); - LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", - pindex->GetBlockHash().ToString(), pfrom.GetId()); + MarkBlockAsInFlight(pfrom->GetId(), pindex->GetBlockHash(), pindex); + LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", + pindex->GetBlockHash().ToString(), pfrom->GetId()); } if (vGetData.size() > 1) { LogPrint(BCLog::NET, "Downloading blocks toward %s (%d) via headers direct fetch\n", @@ -1931,373 +1531,110 @@ void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vectorPushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData)); } } } // If we're in IBD, we want outbound peers that will serve us a useful // chain. Disconnect peers that are on chains with insufficient work. - if (::ChainstateActive().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { + if (IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { // When nCount < MAX_HEADERS_RESULTS, we know we have no more // headers to fetch from this peer. if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has too little work on their headers chain to help - // us sync -- disconnect if it is an outbound disconnection - // candidate. + // us sync -- disconnect if using an outbound slot (unless + // whitelisted or addnode). // Note: We compare their tip to nMinimumChainWork (rather than - // ::ChainActive().Tip()) because we won't start block download + // chainActive.Tip()) because we won't start block download // until we have a headers chain that has at least // nMinimumChainWork, even if a peer has a chain past our tip, // as an anti-DoS measure. - if (pfrom.IsOutboundOrBlockRelayConn()) { - LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId()); - pfrom.fDisconnect = true; + if (IsOutboundDisconnectionCandidate(pfrom)) { + LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom->GetId()); + pfrom->fDisconnect = true; } } } - // If this is an outbound full-relay peer, check to see if we should protect - // it from the bad/lagging chain logic. - // Note that outbound block-relay peers are excluded from this protection, and - // thus always subject to eviction under the bad/lagging chain logic. - // See ChainSyncTimeoutState. - if (!pfrom.fDisconnect && pfrom.IsFullOutboundConn() && nodestate->pindexBestKnownBlock != nullptr) { - if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { - LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom.GetId()); + if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) { + // If this is an outbound peer, check to see if we should protect + // it from the bad/lagging chain logic. + if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= chainActive.Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { + LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId()); nodestate->m_chain_sync.m_protect = true; ++g_outbound_peers_with_protect_from_disconnect; } } } - return; + return true; } -/** - * Reconsider orphan transactions after a parent has been accepted to the mempool. - * - * @param[in/out] orphan_work_set The set of orphan transactions to reconsider. Generally only one - * orphan will be reconsidered on each call of this function. This set - * may be added to if accepting an orphan causes its children to be - * reconsidered. - */ -void PeerManager::ProcessOrphanTx(std::set& orphan_work_set) +bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic& interruptMsgProc, bool enable_bip61) { - AssertLockHeld(cs_main); - AssertLockHeld(g_cs_orphans); - - while (!orphan_work_set.empty()) { - const uint256 orphanHash = *orphan_work_set.begin(); - orphan_work_set.erase(orphan_work_set.begin()); - - auto orphan_it = mapOrphanTransactions.find(orphanHash); - if (orphan_it == mapOrphanTransactions.end()) continue; - - const CTransactionRef porphanTx = orphan_it->second.tx; - TxValidationState state; - std::list removed_txn; - - if (AcceptToMemoryPool(m_mempool, state, porphanTx, &removed_txn, false /* bypass_limits */)) { - LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); - RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), m_connman); - for (unsigned int i = 0; i < porphanTx->vout.size(); i++) { - auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i)); - if (it_by_prev != mapOrphanTransactionsByPrev.end()) { - for (const auto& elem : it_by_prev->second) { - orphan_work_set.insert(elem->first); - } - } - } - EraseOrphanTx(orphanHash); - for (const CTransactionRef& removedTx : removed_txn) { - AddToCompactExtraTransactions(removedTx); - } - break; - } else if (state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) { - if (state.IsInvalid()) { - LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s from peer=%d. %s\n", - orphanHash.ToString(), - orphan_it->second.fromPeer, - state.ToString()); - // Maybe punish peer that gave us an invalid orphan tx - MaybePunishNodeForTx(orphan_it->second.fromPeer, state); - } - // Has inputs but not accepted to mempool - // Probably non-standard or insufficient fee - LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString()); - if (state.GetResult() != TxValidationResult::TX_WITNESS_STRIPPED) { - // We can add the wtxid of this transaction to our reject filter. - // Do not add txids of witness transactions or witness-stripped - // transactions to the filter, as they can have been malleated; - // adding such txids to the reject filter would potentially - // interfere with relay of valid transactions from peers that - // do not support wtxid-based relay. See - // https://github.com/bitcoin/bitcoin/issues/8279 for details. - // We can remove this restriction (and always add wtxids to - // the filter even for witness stripped transactions) once - // wtxid-based relay is broadly deployed. - // See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034 - // for concerns around weakening security of unupgraded nodes - // if we start doing this too early. - assert(recentRejects); - recentRejects->insert(porphanTx->GetWitnessHash()); - // If the transaction failed for TX_INPUTS_NOT_STANDARD, - // then we know that the witness was irrelevant to the policy - // failure, since this check depends only on the txid - // (the scriptPubKey being spent is covered by the txid). - // Add the txid to the reject filter to prevent repeated - // processing of this transaction in the event that child - // transactions are later received (resulting in - // parent-fetching by txid via the orphan-handling logic). - if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && porphanTx->GetWitnessHash() != porphanTx->GetHash()) { - // We only add the txid if it differs from the wtxid, to - // avoid wasting entries in the rolling bloom filter. - recentRejects->insert(porphanTx->GetHash()); - } - } - EraseOrphanTx(orphanHash); - break; + LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(strCommand), vRecv.size(), pfrom->GetId()); + if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0) + { + LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n"); + return true; + } + + if (!(pfrom->GetLocalServices() & NODE_BLOOM) && + (strCommand == NetMsgType::FILTERLOAD || + strCommand == NetMsgType::FILTERADD)) + { + if (pfrom->nVersion >= NO_BLOOM_VERSION) { + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 100); + return false; + } else { + pfrom->fDisconnect = true; + return false; } } - m_mempool.check(&::ChainstateActive().CoinsTip()); -} -/** - * Validation logic for compact filters request handling. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] chain_params Chain parameters - * @param[in] filter_type The filter type the request is for. Must be basic filters. - * @param[in] start_height The start height for the request - * @param[in] stop_hash The stop_hash for the request - * @param[in] max_height_diff The maximum number of items permitted to request, as specified in BIP 157 - * @param[out] stop_index The CBlockIndex for the stop_hash block, if the request can be serviced. - * @param[out] filter_index The filter index, if the request can be serviced. - * @return True if the request can be serviced. - */ -static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params, - BlockFilterType filter_type, uint32_t start_height, - const uint256& stop_hash, uint32_t max_height_diff, - const CBlockIndex*& stop_index, - BlockFilterIndex*& filter_index) -{ - const bool supported_filter_type = - (filter_type == BlockFilterType::BASIC && - (peer.GetLocalServices() & NODE_COMPACT_FILTERS)); - if (!supported_filter_type) { - LogPrint(BCLog::NET, "peer %d requested unsupported block filter type: %d\n", - peer.GetId(), static_cast(filter_type)); - peer.fDisconnect = true; - return false; + if (strCommand == NetMsgType::ENG_ANNOUNCEMENT) + { + // Enigma Announcement + return true; } + if (strCommand == NetMsgType::REJECT) { - LOCK(cs_main); - stop_index = LookupBlockIndex(stop_hash); + if (LogAcceptCategory(BCLog::NET)) { + try { + std::string strMsg; unsigned char ccode; std::string strReason; + vRecv >> LIMITED_STRING(strMsg, CMessageHeader::COMMAND_SIZE) >> ccode >> LIMITED_STRING(strReason, MAX_REJECT_MESSAGE_LENGTH); - // Check that the stop block exists and the peer would be allowed to fetch it. - if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) { - LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n", - peer.GetId(), stop_hash.ToString()); - peer.fDisconnect = true; - return false; + std::ostringstream ss; + ss << strMsg << " code " << itostr(ccode) << ": " << strReason; + + if (strMsg == NetMsgType::BLOCK || strMsg == NetMsgType::TX) + { + uint256 hash; + vRecv >> hash; + ss << ": hash " << hash.ToString(); + } + LogPrint(BCLog::NET, "Reject %s\n", SanitizeString(ss.str())); + } catch (const std::ios_base::failure&) { + // Avoid feedback loops by preventing reject messages from triggering a new reject message. + LogPrint(BCLog::NET, "Unparseable reject message received\n"); + } } + return true; } - uint32_t stop_height = stop_index->nHeight; - if (start_height > stop_height) { - LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */ - "start height %d and stop height %d\n", - peer.GetId(), start_height, stop_height); - peer.fDisconnect = true; - return false; - } - if (stop_height - start_height >= max_height_diff) { - LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n", - peer.GetId(), stop_height - start_height + 1, max_height_diff); - peer.fDisconnect = true; - return false; - } - - filter_index = GetBlockFilterIndex(filter_type); - if (!filter_index) { - LogPrint(BCLog::NET, "Filter index for supported type %s not found\n", BlockFilterTypeName(filter_type)); - return false; - } - - return true; -} - -/** - * Handle a cfilters request. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] vRecv The raw message received - * @param[in] chain_params Chain parameters - * @param[in] connman Pointer to the connection manager - */ -static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, - CConnman& connman) -{ - uint8_t filter_type_ser; - uint32_t start_height; - uint256 stop_hash; - - vRecv >> filter_type_ser >> start_height >> stop_hash; - - const BlockFilterType filter_type = static_cast(filter_type_ser); - - const CBlockIndex* stop_index; - BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, start_height, stop_hash, - MAX_GETCFILTERS_SIZE, stop_index, filter_index)) { - return; - } - - std::vector filters; - if (!filter_index->LookupFilterRange(start_height, stop_index, filters)) { - LogPrint(BCLog::NET, "Failed to find block filter in index: filter_type=%s, start_height=%d, stop_hash=%s\n", - BlockFilterTypeName(filter_type), start_height, stop_hash.ToString()); - return; - } - - for (const auto& filter : filters) { - CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) - .Make(NetMsgType::CFILTER, filter); - connman.PushMessage(&peer, std::move(msg)); - } -} - -/** - * Handle a cfheaders request. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] vRecv The raw message received - * @param[in] chain_params Chain parameters - * @param[in] connman Pointer to the connection manager - */ -static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, - CConnman& connman) -{ - uint8_t filter_type_ser; - uint32_t start_height; - uint256 stop_hash; - - vRecv >> filter_type_ser >> start_height >> stop_hash; - - const BlockFilterType filter_type = static_cast(filter_type_ser); - - const CBlockIndex* stop_index; - BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, start_height, stop_hash, - MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) { - return; - } - - uint256 prev_header; - if (start_height > 0) { - const CBlockIndex* const prev_block = - stop_index->GetAncestor(static_cast(start_height - 1)); - if (!filter_index->LookupFilterHeader(prev_block, prev_header)) { - LogPrint(BCLog::NET, "Failed to find block filter header in index: filter_type=%s, block_hash=%s\n", - BlockFilterTypeName(filter_type), prev_block->GetBlockHash().ToString()); - return; - } - } - - std::vector filter_hashes; - if (!filter_index->LookupFilterHashRange(start_height, stop_index, filter_hashes)) { - LogPrint(BCLog::NET, "Failed to find block filter hashes in index: filter_type=%s, start_height=%d, stop_hash=%s\n", - BlockFilterTypeName(filter_type), start_height, stop_hash.ToString()); - return; - } - - CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) - .Make(NetMsgType::CFHEADERS, - filter_type_ser, - stop_index->GetBlockHash(), - prev_header, - filter_hashes); - connman.PushMessage(&peer, std::move(msg)); -} - -/** - * Handle a getcfcheckpt request. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] vRecv The raw message received - * @param[in] chain_params Chain parameters - * @param[in] connman Pointer to the connection manager - */ -static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, - CConnman& connman) -{ - uint8_t filter_type_ser; - uint256 stop_hash; - - vRecv >> filter_type_ser >> stop_hash; - - const BlockFilterType filter_type = static_cast(filter_type_ser); - - const CBlockIndex* stop_index; - BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, /*start_height=*/0, stop_hash, - /*max_height_diff=*/std::numeric_limits::max(), - stop_index, filter_index)) { - return; - } - - std::vector headers(stop_index->nHeight / CFCHECKPT_INTERVAL); - - // Populate headers. - const CBlockIndex* block_index = stop_index; - for (int i = headers.size() - 1; i >= 0; i--) { - int height = (i + 1) * CFCHECKPT_INTERVAL; - block_index = block_index->GetAncestor(height); - - if (!filter_index->LookupFilterHeader(block_index, headers[i])) { - LogPrint(BCLog::NET, "Failed to find block filter header in index: filter_type=%s, block_hash=%s\n", - BlockFilterTypeName(filter_type), block_index->GetBlockHash().ToString()); - return; - } - } - - CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) - .Make(NetMsgType::CFCHECKPT, - filter_type_ser, - stop_index->GetBlockHash(), - headers); - connman.PushMessage(&peer, std::move(msg)); -} - -void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, - const std::chrono::microseconds time_received, - const std::atomic& interruptMsgProc) -{ - LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(msg_type), vRecv.size(), pfrom.GetId()); - if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0) + else if (strCommand == NetMsgType::VERSION) { - LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n"); - return; - } - - PeerRef peer = GetPeerRef(pfrom.GetId()); - if (peer == nullptr) return; - - if (msg_type == NetMsgType::VERSION) { // Each connection can only send one version message - if (pfrom.nVersion != 0) + if (pfrom->nVersion != 0) { - Misbehaving(pfrom.GetId(), 1, "redundant version message"); - return; + if (enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_DUPLICATE, std::string("Duplicate version message"))); + } + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 1); + return false; } int64_t nTime; @@ -2307,34 +1644,47 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat uint64_t nServiceInt; ServiceFlags nServices; int nVersion; + int nSendVersion; + std::string strSubVer; std::string cleanSubVer; int nStartingHeight = -1; bool fRelay = true; vRecv >> nVersion >> nServiceInt >> nTime >> addrMe; + nSendVersion = std::min(nVersion, PROTOCOL_VERSION); nServices = ServiceFlags(nServiceInt); - if (!pfrom.IsInboundConn()) + if (!pfrom->fInbound) { - m_connman.SetServices(pfrom.addr, nServices); + connman->SetServices(pfrom->addr, nServices); } - if (pfrom.ExpectServicesFromConn() && !HasAllDesirableServiceFlags(nServices)) + if (!pfrom->fInbound && !pfrom->fFeeler && !pfrom->m_manual_connection && !HasAllDesirableServiceFlags(nServices)) { - LogPrint(BCLog::NET, "peer=%d does not offer the expected services (%08x offered, %08x expected); disconnecting\n", pfrom.GetId(), nServices, GetDesirableServiceFlags(nServices)); - pfrom.fDisconnect = true; - return; + LogPrint(BCLog::NET, "peer=%d does not offer the expected services (%08x offered, %08x expected); disconnecting\n", pfrom->GetId(), nServices, GetDesirableServiceFlags(nServices)); + if (enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_NONSTANDARD, + strprintf("Expected to offer services %08x", GetDesirableServiceFlags(nServices)))); + } + pfrom->fDisconnect = true; + return false; } - if (nVersion < MIN_PEER_PROTO_VERSION) { + if (nVersion < MIN_PEER_PROTO_VERSION) + { // disconnect from peers older than this proto version - LogPrint(BCLog::NET, "peer=%d using obsolete version %i; disconnecting\n", pfrom.GetId(), nVersion); - pfrom.fDisconnect = true; - return; + LogPrint(BCLog::NET, "peer=%d using obsolete version %i; disconnecting\n", pfrom->GetId(), nVersion); + if (enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, + strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION))); + } + pfrom->fDisconnect = true; + return false; } + if (nVersion == 10300) + nVersion = 300; if (!vRecv.empty()) vRecv >> addrFrom >> nNonce; if (!vRecv.empty()) { - std::string strSubVer; vRecv >> LIMITED_STRING(strSubVer, MAX_SUBVERSION_LENGTH); cleanSubVer = SanitizeString(strSubVer); } @@ -2344,180 +1694,157 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (!vRecv.empty()) vRecv >> fRelay; // Disconnect if we connected to ourself - if (pfrom.IsInboundConn() && !m_connman.CheckIncomingNonce(nNonce)) + if (pfrom->fInbound && !connman->CheckIncomingNonce(nNonce)) { - LogPrintf("connected to self at %s, disconnecting\n", pfrom.addr.ToString()); - pfrom.fDisconnect = true; - return; + LogPrintf("connected to self at %s, disconnecting\n", pfrom->addr.ToString()); + pfrom->fDisconnect = true; + return true; } - if (pfrom.IsInboundConn() && addrMe.IsRoutable()) + if (pfrom->fInbound && addrMe.IsRoutable()) { SeenLocal(addrMe); } // Be shy and don't send version until we hear - if (pfrom.IsInboundConn()) - PushNodeVersion(pfrom, m_connman, GetAdjustedTime()); + if (pfrom->fInbound) + PushNodeVersion(pfrom, connman, GetAdjustedTime()); - // Change version - const int greatest_common_version = std::min(nVersion, PROTOCOL_VERSION); - pfrom.SetCommonVersion(greatest_common_version); - pfrom.nVersion = nVersion; - - const CNetMsgMaker msg_maker(greatest_common_version); - - if (greatest_common_version >= WTXID_RELAY_VERSION) { - m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::WTXIDRELAY)); - } + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK)); - // Signal ADDRv2 support (BIP155). - if (greatest_common_version >= 70016) { - // BIP155 defines addrv2 and sendaddrv2 for all protocol versions, but some - // implementations reject messages they don't know. As a courtesy, don't send - // it to nodes with a version before 70016, as no software is known to support - // BIP155 that doesn't announce at least that protocol version number. - m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2)); - } - - m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK)); - - pfrom.nServices = nServices; - pfrom.SetAddrLocal(addrMe); + pfrom->nServices = nServices; + pfrom->SetAddrLocal(addrMe); { - LOCK(pfrom.cs_SubVer); - pfrom.cleanSubVer = cleanSubVer; + LOCK(pfrom->cs_SubVer); + pfrom->strSubVer = strSubVer; + pfrom->cleanSubVer = cleanSubVer; } - pfrom.nStartingHeight = nStartingHeight; + pfrom->nStartingHeight = nStartingHeight; // set nodes not relaying blocks and tx and not serving (parts) of the historical blockchain as "clients" - pfrom.fClient = (!(nServices & NODE_NETWORK) && !(nServices & NODE_NETWORK_LIMITED)); + pfrom->fClient = (!(nServices & NODE_NETWORK) && !(nServices & NODE_NETWORK_LIMITED)); // set nodes not capable of serving the complete blockchain history as "limited nodes" - pfrom.m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED)); - - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - pfrom.m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message + pfrom->m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED)); + { + LOCK(pfrom->cs_filter); + pfrom->fRelayTxes = fRelay; // set to true after we get the first filter* message } + // Change version + pfrom->SetSendVersion(nSendVersion); + pfrom->nVersion = nVersion; + if((nServices & NODE_WITNESS)) { LOCK(cs_main); - State(pfrom.GetId())->fHaveWitness = true; + State(pfrom->GetId())->fHaveWitness = true; } // Potentially mark this peer as a preferred download peer. { LOCK(cs_main); - UpdatePreferredDownload(pfrom, State(pfrom.GetId())); + UpdatePreferredDownload(pfrom, State(pfrom->GetId())); } - if (!pfrom.IsInboundConn() && !pfrom.IsBlockOnlyConn()) { - // For outbound peers, we try to relay our address (so that other - // nodes can try to find us more quickly, as we have no guarantee - // that an outbound peer is even aware of how to reach us) and do a - // one-time address fetch (to help populate/update our addrman). If - // we're starting up for the first time, our addrman may be pretty - // empty and no one will know who we are, so these mechanisms are - // important to help us connect to the network. - // - // We skip this for BLOCK_RELAY peers to avoid potentially leaking - // information about our BLOCK_RELAY connections via address relay. - if (fListen && !::ChainstateActive().IsInitialBlockDownload()) + if (!pfrom->fInbound) + { + // Advertise our address + if (fListen && !IsInitialBlockDownload()) { - CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices()); + CAddress addr = GetLocalAddress(&pfrom->addr, pfrom->GetLocalServices()); FastRandomContext insecure_rand; if (addr.IsRoutable()) { LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString()); - pfrom.PushAddress(addr, insecure_rand); - } else if (IsPeerAddrLocalGood(&pfrom)) { + pfrom->PushAddress(addr, insecure_rand); + } else if (IsPeerAddrLocalGood(pfrom)) { addr.SetIP(addrMe); LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString()); - pfrom.PushAddress(addr, insecure_rand); + pfrom->PushAddress(addr, insecure_rand); } } // Get recent addresses - m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR)); - pfrom.fGetAddr = true; - } - - if (!pfrom.IsInboundConn()) { - // For non-inbound connections, we update the addrman to record - // connection success so that addrman will have an up-to-date - // notion of which peers are online and available. - // - // While we strive to not leak information about block-relay-only - // connections via the addrman, not moving an address to the tried - // table is also potentially detrimental because new-table entries - // are subject to eviction in the event of addrman collisions. We - // mitigate the information-leak by never calling - // CAddrMan::Connected() on block-relay-only peers; see - // FinalizeNode(). - // - // This moves an address from New to Tried table in Addrman, - // resolves tried-table collisions, etc. - m_connman.MarkAddressGood(pfrom.addr); + if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION || connman->GetAddressCount() < 1000) + { + connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR)); + pfrom->fGetAddr = true; + } + connman->MarkAddressGood(pfrom->addr); } std::string remoteAddr; if (fLogIPs) - remoteAddr = ", peeraddr=" + pfrom.addr.ToString(); + remoteAddr = ", peeraddr=" + pfrom->addr.ToString(); LogPrint(BCLog::NET, "receive version message: %s: version %d, blocks=%d, us=%s, peer=%d%s\n", - cleanSubVer, pfrom.nVersion, - pfrom.nStartingHeight, addrMe.ToString(), pfrom.GetId(), + cleanSubVer, pfrom->nVersion, + pfrom->nStartingHeight, addrMe.ToString(), pfrom->GetId(), remoteAddr); int64_t nTimeOffset = nTime - GetTime(); - pfrom.nTimeOffset = nTimeOffset; - AddTimeData(pfrom.addr, nTimeOffset); + pfrom->nTimeOffset = nTimeOffset; + AddTimeData(pfrom->addr, nTimeOffset); + + /* note: now handled in node send messages... + // Ask the first connected node for block updates + static int nAskedForBlocks = 0; + if (!pfrom->fClient && !pfrom->fOneShot && + (pfrom->nStartingHeight > (nBestHeight - 144)) && + (pfrom->nVersion < NOBLKS_VERSION_START || + pfrom->nVersion >= NOBLKS_VERSION_END) && + (nAskedForBlocks < 1 || vNodes.size() <= 1)) { + nAskedForBlocks++; + pfrom->PushGetBlocks(pindexBest, uint256(0)); + }*/ // If the peer is old enough to have the old alert system, send it the final alert. - if (greatest_common_version <= 70012) { + if (pfrom->nVersion <= 70012) { CDataStream finalAlert(ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01ffffff7f00000000ffffff7f00ffffff7f002f555247454e543a20416c657274206b657920636f6d70726f6d697365642c2075706772616465207265717569726564004630440220653febd6410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3abd5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fecaae66ecf689bf71b50"), SER_NETWORK, PROTOCOL_VERSION); - m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make("alert", finalAlert)); + connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert)); } // Feeler connections exist only to verify if address is online. - if (pfrom.IsFeelerConn()) { - pfrom.fDisconnect = true; + if (pfrom->fFeeler) { + assert(pfrom->fInbound == false); + pfrom->fDisconnect = true; } - return; + return true; } - - if (pfrom.nVersion == 0) { + + else if (pfrom->nVersion == 0) + { // Must have a version message before anything else - Misbehaving(pfrom.GetId(), 1, "non-version message before version handshake"); - return; + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 1); + return false; } // At this point, the outgoing message serialization version can't change. - const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); + const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); - if (msg_type == NetMsgType::VERACK) { - if (pfrom.fSuccessfullyConnected) return; + if (strCommand == NetMsgType::VERACK) + { + pfrom->SetRecvVersion(std::min(pfrom->nVersion.load(), PROTOCOL_VERSION)); - if (!pfrom.IsInboundConn()) { + if (!pfrom->fInbound) { // Mark this node as currently connected, so we update its timestamp later. LOCK(cs_main); - State(pfrom.GetId())->fCurrentlyConnected = true; - LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n", - pfrom.nVersion.load(), pfrom.nStartingHeight, - pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToString()) : ""), - pfrom.m_tx_relay == nullptr ? "block-relay" : "full-relay"); + State(pfrom->GetId())->fCurrentlyConnected = true; + LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s\n", + pfrom->nVersion.load(), pfrom->nStartingHeight, pfrom->GetId(), + (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : "")); } - if (pfrom.GetCommonVersion() >= SENDHEADERS_VERSION) { + if (pfrom->nVersion >= SENDHEADERS_VERSION) { // Tell our peer we prefer to receive headers rather than inv's // We send this to non-NODE NETWORK peers as well, because even // non-NODE NETWORK peers can announce blocks (such as pruning // nodes) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDHEADERS)); } - if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) { + if (pfrom->nVersion >= SHORT_IDS_BLOCKS_VERSION) { // Tell our peer we are willing to provide version 1 or 2 cmpctblocks // However, we do not request new block announcements using // cmpctblock messages. @@ -2525,70 +1852,35 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // they may wish to request compact blocks from us bool fAnnounceUsingCMPCTBLOCK = false; uint64_t nCMPCTBLOCKVersion = 2; - if (pfrom.GetLocalServices() & NODE_WITNESS) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); + if (pfrom->GetLocalServices() & NODE_WITNESS) + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); nCMPCTBLOCKVersion = 1; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); } - pfrom.fSuccessfullyConnected = true; - return; + pfrom->fSuccessfullyConnected = true; } - // Feature negotiation of wtxidrelay should happen between VERSION and - // VERACK, to avoid relay problems from switching after a connection is up - if (msg_type == NetMsgType::WTXIDRELAY) { - if (pfrom.fSuccessfullyConnected) { - // Disconnect peers that send wtxidrelay message after VERACK; this - // must be negotiated between VERSION and VERACK. - pfrom.fDisconnect = true; - return; - } - if (pfrom.GetCommonVersion() >= WTXID_RELAY_VERSION) { - LOCK(cs_main); - if (!State(pfrom.GetId())->m_wtxid_relay) { - State(pfrom.GetId())->m_wtxid_relay = true; - g_wtxid_relay_peers++; - } - } - return; - } - - if (msg_type == NetMsgType::SENDADDRV2) { - if (pfrom.fSuccessfullyConnected) { - // Disconnect peers that send SENDADDRV2 message after VERACK; this - // must be negotiated between VERSION and VERACK. - pfrom.fDisconnect = true; - return; - } - pfrom.m_wants_addrv2 = true; - return; - } - - if (!pfrom.fSuccessfullyConnected) { - LogPrint(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", SanitizeString(msg_type), pfrom.GetId()); - return; + else if (!pfrom->fSuccessfullyConnected) + { + // Must have a verack message before anything else + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 1); + return false; } - if (msg_type == NetMsgType::ADDR || msg_type == NetMsgType::ADDRV2) { - int stream_version = vRecv.GetVersion(); - if (msg_type == NetMsgType::ADDRV2) { - // Add ADDRV2_FORMAT to the version so that the CNetAddr and CAddress - // unserialize methods know that an address in v2 format is coming. - stream_version |= ADDRV2_FORMAT; - } - - OverrideStream s(&vRecv, vRecv.GetType(), stream_version); + else if (strCommand == NetMsgType::ADDR) + { std::vector vAddr; + vRecv >> vAddr; - s >> vAddr; - - if (!pfrom.RelayAddrsWithConn()) { - return; - } - if (vAddr.size() > MAX_ADDR_TO_SEND) + // Don't want addr from older versions unless seeding + if (pfrom->nVersion < CADDR_TIME_VERSION && connman->GetAddressCount() > 1000) + return true; + if (vAddr.size() > 1000) { - Misbehaving(pfrom.GetId(), 20, strprintf("%s message size = %u", msg_type, vAddr.size())); - return; + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 20, strprintf("message addr size() = %u", vAddr.size())); + return false; } // Store the new addresses @@ -2598,7 +1890,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat for (CAddress& addr : vAddr) { if (interruptMsgProc) - return; + return true; // We only bother storing full nodes, though this may include // things which we would not make an outbound connection to, in @@ -2608,165 +1900,184 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) addr.nTime = nNow - 5 * 24 * 60 * 60; - pfrom.AddAddressKnown(addr); - if (m_banman && (m_banman->IsDiscouraged(addr) || m_banman->IsBanned(addr))) { - // Do not process banned/discouraged addresses beyond remembering we received them - continue; - } + pfrom->AddAddressKnown(addr); bool fReachable = IsReachable(addr); - if (addr.nTime > nSince && !pfrom.fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) + if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { // Relay to a limited number of other nodes - RelayAddress(addr, fReachable, m_connman); + RelayAddress(addr, fReachable, connman); } // Do not store addresses outside our network if (fReachable) vAddrOk.push_back(addr); } - m_connman.AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60); + connman->AddNewAddresses(vAddrOk, pfrom->addr, 2 * 60 * 60); if (vAddr.size() < 1000) - pfrom.fGetAddr = false; - if (pfrom.IsAddrFetchConn()) - pfrom.fDisconnect = true; - return; + pfrom->fGetAddr = false; + if (pfrom->fOneShot) + pfrom->fDisconnect = true; } - if (msg_type == NetMsgType::SENDHEADERS) { + else if (strCommand == NetMsgType::SENDHEADERS) + { LOCK(cs_main); - State(pfrom.GetId())->fPreferHeaders = true; - return; + State(pfrom->GetId())->fPreferHeaders = true; } - if (msg_type == NetMsgType::SENDCMPCT) { + else if (strCommand == NetMsgType::SENDCMPCT) + { bool fAnnounceUsingCMPCTBLOCK = false; uint64_t nCMPCTBLOCKVersion = 0; vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion; - if (nCMPCTBLOCKVersion == 1 || ((pfrom.GetLocalServices() & NODE_WITNESS) && nCMPCTBLOCKVersion == 2)) { + if (nCMPCTBLOCKVersion == 1 || ((pfrom->GetLocalServices() & NODE_WITNESS) && nCMPCTBLOCKVersion == 2)) { LOCK(cs_main); // fProvidesHeaderAndIDs is used to "lock in" version of compact blocks we send (fWantsCmpctWitness) - if (!State(pfrom.GetId())->fProvidesHeaderAndIDs) { - State(pfrom.GetId())->fProvidesHeaderAndIDs = true; - State(pfrom.GetId())->fWantsCmpctWitness = nCMPCTBLOCKVersion == 2; - } - if (State(pfrom.GetId())->fWantsCmpctWitness == (nCMPCTBLOCKVersion == 2)) // ignore later version announces - State(pfrom.GetId())->fPreferHeaderAndIDs = fAnnounceUsingCMPCTBLOCK; - if (!State(pfrom.GetId())->fSupportsDesiredCmpctVersion) { - if (pfrom.GetLocalServices() & NODE_WITNESS) - State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2); + if (!State(pfrom->GetId())->fProvidesHeaderAndIDs) { + State(pfrom->GetId())->fProvidesHeaderAndIDs = true; + State(pfrom->GetId())->fWantsCmpctWitness = nCMPCTBLOCKVersion == 2; + } + if (State(pfrom->GetId())->fWantsCmpctWitness == (nCMPCTBLOCKVersion == 2)) // ignore later version announces + State(pfrom->GetId())->fPreferHeaderAndIDs = fAnnounceUsingCMPCTBLOCK; + if (!State(pfrom->GetId())->fSupportsDesiredCmpctVersion) { + if (pfrom->GetLocalServices() & NODE_WITNESS) + State(pfrom->GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2); else - State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 1); + State(pfrom->GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 1); } } - return; } - if (msg_type == NetMsgType::INV) { + else if (strCommand == NetMsgType::INV) + { std::vector vInv; vRecv >> vInv; if (vInv.size() > MAX_INV_SZ) { - Misbehaving(pfrom.GetId(), 20, strprintf("inv message size = %u", vInv.size())); - return; + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 20, strprintf("message inv size() = %u", vInv.size())); + return false; + } + + // find last block in inv vector + unsigned int nLastBlock = (unsigned int)(-1); + for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) { + if (vInv[vInv.size() - 1 - nInv].type == MSG_BLOCK) { + nLastBlock = vInv.size() - 1 - nInv; + break; + } } - // We won't accept tx inv's if we're in blocks-only mode, or this is a - // block-relay-only peer - bool fBlocksOnly = !g_relay_txes || (pfrom.m_tx_relay == nullptr); + bool fBlocksOnly = !fRelayTxes; - // Allow peers with relay permission to send data other than blocks in blocks only mode - if (pfrom.HasPermission(PF_RELAY)) { + // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true + if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) fBlocksOnly = false; - } LOCK(cs_main); - const auto current_time = GetTime(); - uint256* best_block{nullptr}; + uint32_t nFetchFlags = GetFetchFlags(pfrom); - for (CInv& inv : vInv) { - if (interruptMsgProc) return; + unsigned nInv = 0; - // Ignore INVs that don't match wtxidrelay setting. - // Note that orphan parent fetching always uses MSG_TX GETDATAs regardless of the wtxidrelay setting. - // This is fine as no INV messages are involved in that process. - if (State(pfrom.GetId())->m_wtxid_relay) { - if (inv.IsMsgTx()) continue; - } else { - if (inv.IsMsgWtx()) continue; + for (CInv &inv : vInv) + { + if (interruptMsgProc) + return true; + + bool fAlreadyHave = AlreadyHave(inv); + LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->GetId()); + + if (inv.type == MSG_TX) { + inv.type |= nFetchFlags; } - if (inv.IsMsgBlk()) { - const bool fAlreadyHave = AlreadyHaveBlock(inv.hash); - LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); + if (inv.type == MSG_BLOCK) { + + UpdateBlockAvailability(pfrom->GetId(), inv.hash); - UpdateBlockAvailability(pfrom.GetId(), inv.hash); + if (vInv.size() == 1 && pfrom->nVersion == INIT_PROTO_VERSION) + { + // old 60018 version sends topblock when the last block from an INV is requested. + // this breaks things if not handled correctly... + } + + bool canHandlePoSHeaders = pfrom->nVersion >= VERSION_GETHEADERS_POS; if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) { - // Headers-first is the primary method of announcement on - // the network. If a node fell back to sending blocks by inv, - // it's probably for a re-org. The final block hash - // provided should be the highest, so send a getheaders and - // then fetch the blocks we need to catch up. - best_block = &inv.hash; + if (canHandlePoSHeaders) + { + // We used to request the full block here, but since headers-announcements are now the + // primary method of announcement on the network, and since, in the case that a node + // fell back to inv we probably have a reorg which we should get the headers for first, + // we now only provide a getheaders response here. When we receive the headers, we will + // then ask for the blocks we need. + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash)); + LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->GetId()); + } + else + { + // old client, ask for block in next INV request + pfrom->AskFor(inv); + } } - } else if (inv.IsGenTxMsg()) { - const GenTxid gtxid = ToGenTxid(inv); - const bool fAlreadyHave = AlreadyHaveTx(gtxid, m_mempool); - LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); - pfrom.AddKnownTx(inv.hash); + if (nInv == nLastBlock) + { + if (canHandlePoSHeaders) { + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash)); + LogPrint(BCLog::NET, "getblocks (%d) to peer=%d\n", pindexBestHeader->nHeight, pfrom->GetId()); + } + else { + // older client, revert to using getblocks + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETBLOCKS, chainActive.GetLocator(pindexBestHeader), inv.hash)); + LogPrint(BCLog::NET, "getblocks (%d) to peer=%d\n", pindexBestHeader->nHeight, pfrom->GetId()); + } + } + } + else + { + pfrom->AddInventoryKnown(inv); if (fBlocksOnly) { - LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol, disconnecting peer=%d\n", inv.hash.ToString(), pfrom.GetId()); - pfrom.fDisconnect = true; - return; - } else if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { - AddTxAnnouncement(pfrom, gtxid, current_time); + LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId()); + } + else if (!fAlreadyHave && !fImporting && !fReindex && !IsInitialBlockDownload()) { + pfrom->AskFor(inv, IsInitialBlockDownload()); } - } else { - LogPrint(BCLog::NET, "Unknown inv type \"%s\" received from peer=%d\n", inv.ToString(), pfrom.GetId()); } + nInv++; } - - if (best_block != nullptr) { - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), *best_block)); - LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, best_block->ToString(), pfrom.GetId()); - } - - return; } - - if (msg_type == NetMsgType::GETDATA) { + else if (strCommand == NetMsgType::GETDATA) + { std::vector vInv; vRecv >> vInv; if (vInv.size() > MAX_INV_SZ) { - Misbehaving(pfrom.GetId(), 20, strprintf("getdata message size = %u", vInv.size())); - return; + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 20, strprintf("message getdata size() = %u", vInv.size())); + return false; } - LogPrint(BCLog::NET, "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom.GetId()); + LogPrint(BCLog::NET, "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom->GetId()); if (vInv.size() > 0) { - LogPrint(BCLog::NET, "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom.GetId()); + LogPrint(BCLog::NET, "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom->GetId()); } - { - LOCK(peer->m_getdata_requests_mutex); - peer->m_getdata_requests.insert(peer->m_getdata_requests.end(), vInv.begin(), vInv.end()); - ProcessGetData(pfrom, *peer, m_chainparams, m_connman, m_mempool, interruptMsgProc); - } - - return; + pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end()); + ProcessGetData(pfrom, chainparams, connman, interruptMsgProc); } - if (msg_type == NetMsgType::GETBLOCKS) { + + else if (strCommand == NetMsgType::GETBLOCKS) + { CBlockLocator locator; uint256 hashStop; vRecv >> locator >> hashStop; if (locator.vHave.size() > MAX_LOCATOR_SZ) { - LogPrint(BCLog::NET, "getblocks locator size %lld > %d, disconnect peer=%d\n", locator.vHave.size(), MAX_LOCATOR_SZ, pfrom.GetId()); - pfrom.fDisconnect = true; - return; + LogPrint(BCLog::NET, "getblocks locator size %lld > %d, disconnect peer=%d\n", locator.vHave.size(), MAX_LOCATOR_SZ, pfrom->GetId()); + pfrom->fDisconnect = true; + return true; } // We might have announced the currently-being-connected tip using a @@ -2782,23 +2093,23 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat LOCK(cs_most_recent_block); a_recent_block = most_recent_block; } - BlockValidationState state; - if (!ActivateBestChain(state, m_chainparams, a_recent_block)) { - LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); + CValidationState state; + if (!ActivateBestChain(state, Params(), a_recent_block)) { + LogPrint(BCLog::NET, "failed to activate chain (%s)\n", FormatStateMessage(state)); } } LOCK(cs_main); // Find the last block the caller has in the main chain - const CBlockIndex* pindex = FindForkInGlobalIndex(::ChainActive(), locator); + const CBlockIndex* pindex = FindForkInGlobalIndex(chainActive, locator); // Send the rest of the chain if (pindex) - pindex = ::ChainActive().Next(pindex); + pindex = chainActive.Next(pindex); int nLimit = 500; - LogPrint(BCLog::NET, "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit, pfrom.GetId()); - for (; pindex; pindex = ::ChainActive().Next(pindex)) + LogPrint(BCLog::NET, "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit, pfrom->GetId()); + for (; pindex; pindex = chainActive.Next(pindex)) { if (pindex->GetBlockHash() == hashStop) { @@ -2807,26 +2118,27 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat } // If pruning, don't inv blocks unless we have on disk and are likely to still have // for some reasonable time window (1 hour) that block relay might require. - const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / m_chainparams.GetConsensus().nPowTargetSpacing; - if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= ::ChainActive().Tip()->nHeight - nPrunedBlocksLikelyToHave)) + const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / chainparams.GetConsensus().nPowTargetSpacing; + if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= chainActive.Tip()->nHeight - nPrunedBlocksLikelyToHave)) { LogPrint(BCLog::NET, " getblocks stopping, pruned or too old block at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); break; } - WITH_LOCK(pfrom.cs_inventory, pfrom.vInventoryBlockToSend.push_back(pindex->GetBlockHash())); + pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash())); if (--nLimit <= 0) { // When this block is requested, we'll send an inv that'll // trigger the peer to getblocks the next batch of inventory. LogPrint(BCLog::NET, " getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); - pfrom.hashContinue = pindex->GetBlockHash(); + pfrom->hashContinue = pindex->GetBlockHash(); break; } } - return; } - if (msg_type == NetMsgType::GETBLOCKTXN) { + + else if (strCommand == NetMsgType::GETBLOCKTXN) + { BlockTransactionsRequest req; vRecv >> req; @@ -2838,97 +2150,96 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // Unlock cs_most_recent_block to avoid cs_main lock inversion } if (recent_block) { - SendBlockTransactions(pfrom, *recent_block, req); - return; + SendBlockTransactions(*recent_block, req, pfrom, connman); + return true; } - { - LOCK(cs_main); - - const CBlockIndex* pindex = LookupBlockIndex(req.blockhash); - if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { - LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have\n", pfrom.GetId()); - return; - } + LOCK(cs_main); - if (pindex->nHeight >= ::ChainActive().Height() - MAX_BLOCKTXN_DEPTH) { - CBlock block; - bool ret = ReadBlockFromDisk(block, pindex, m_chainparams.GetConsensus()); - assert(ret); + const CBlockIndex* pindex = LookupBlockIndex(req.blockhash); + if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { + LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have\n", pfrom->GetId()); + return true; + } - SendBlockTransactions(pfrom, block, req); - return; - } + if (pindex->nHeight < chainActive.Height() - MAX_BLOCKTXN_DEPTH) { + // If an older block is requested (should never happen in practice, + // but can happen in tests) send a block response instead of a + // blocktxn response. Sending a full block response instead of a + // small blocktxn response is preferable in the case where a peer + // might maliciously send lots of getblocktxn requests to trigger + // expensive disk reads, because it will require the peer to + // actually receive all the data read from disk over the network. + LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep\n", pfrom->GetId(), MAX_BLOCKTXN_DEPTH); + CInv inv; + inv.type = State(pfrom->GetId())->fWantsCmpctWitness ? MSG_WITNESS_BLOCK : MSG_BLOCK; + inv.hash = req.blockhash; + pfrom->vRecvGetData.push_back(inv); + // The message processing loop will go around again (without pausing) and we'll respond then (without cs_main) + return true; } - // If an older block is requested (should never happen in practice, - // but can happen in tests) send a block response instead of a - // blocktxn response. Sending a full block response instead of a - // small blocktxn response is preferable in the case where a peer - // might maliciously send lots of getblocktxn requests to trigger - // expensive disk reads, because it will require the peer to - // actually receive all the data read from disk over the network. - LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep\n", pfrom.GetId(), MAX_BLOCKTXN_DEPTH); - CInv inv; - WITH_LOCK(cs_main, inv.type = State(pfrom.GetId())->fWantsCmpctWitness ? MSG_WITNESS_BLOCK : MSG_BLOCK); - inv.hash = req.blockhash; - WITH_LOCK(peer->m_getdata_requests_mutex, peer->m_getdata_requests.push_back(inv)); - // The message processing loop will go around again (without pausing) and we'll respond then - return; + CBlock block; + bool ret = ReadBlockFromDisk(block, pindex, chainparams.GetConsensus()); + assert(ret); + + SendBlockTransactions(block, req, pfrom, connman); } - if (msg_type == NetMsgType::GETHEADERS) { + + else if (strCommand == NetMsgType::GETHEADERS) + { CBlockLocator locator; uint256 hashStop; vRecv >> locator >> hashStop; if (locator.vHave.size() > MAX_LOCATOR_SZ) { - LogPrint(BCLog::NET, "getheaders locator size %lld > %d, disconnect peer=%d\n", locator.vHave.size(), MAX_LOCATOR_SZ, pfrom.GetId()); - pfrom.fDisconnect = true; - return; + LogPrint(BCLog::NET, "getheaders locator size %lld > %d, disconnect peer=%d\n", locator.vHave.size(), MAX_LOCATOR_SZ, pfrom->GetId()); + pfrom->fDisconnect = true; + return true; } LOCK(cs_main); - if (::ChainstateActive().IsInitialBlockDownload() && !pfrom.HasPermission(PF_DOWNLOAD)) { - LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom.GetId()); - return; + if (IsInitialBlockDownload() && !pfrom->fWhitelisted) { + LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom->GetId()); + return true; } - CNodeState *nodestate = State(pfrom.GetId()); + CNodeState *nodestate = State(pfrom->GetId()); const CBlockIndex* pindex = nullptr; if (locator.IsNull()) { // If locator is null, return the hashStop block pindex = LookupBlockIndex(hashStop); if (!pindex) { - return; + return true; } - if (!BlockRequestAllowed(pindex, m_chainparams.GetConsensus())) { - LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block header that isn't in the main chain\n", __func__, pfrom.GetId()); - return; + if (!BlockRequestAllowed(pindex, chainparams.GetConsensus())) { + LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block header that isn't in the main chain\n", __func__, pfrom->GetId()); + return true; } } else { // Find the last block the caller has in the main chain - pindex = FindForkInGlobalIndex(::ChainActive(), locator); + pindex = FindForkInGlobalIndex(chainActive, locator); if (pindex) - pindex = ::ChainActive().Next(pindex); + pindex = chainActive.Next(pindex); } // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end std::vector vHeaders; int nLimit = MAX_HEADERS_RESULTS; - LogPrint(BCLog::NET, "getheaders %d to %s from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), pfrom.GetId()); - for (; pindex; pindex = ::ChainActive().Next(pindex)) + LogPrint(BCLog::NET, "getheaders %d to %s from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), pfrom->GetId()); + for (; pindex; pindex = chainActive.Next(pindex)) { vHeaders.push_back(pindex->GetBlockHeader()); if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) break; } - // pindex can be nullptr either if we sent ::ChainActive().Tip() OR - // if our peer has ::ChainActive().Tip() (and thus we are sending an empty + // pindex can be nullptr either if we sent chainActive.Tip() OR + // if our peer has chainActive.Tip() (and thus we are sending an empty // headers message). In both cases it's safe to update // pindexBestHeaderSent to be our tip. // @@ -2939,147 +2250,134 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // without the new block. By resetting the BestHeaderSent, we ensure we // will re-announce the new block via headers (or compact blocks again) // in the SendMessages logic. - nodestate->pindexBestHeaderSent = pindex ? pindex : ::ChainActive().Tip(); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders)); - return; + nodestate->pindexBestHeaderSent = pindex ? pindex : chainActive.Tip(); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders)); } - if (msg_type == NetMsgType::TX) { + + else if (strCommand == NetMsgType::TX) + { // Stop processing the transaction early if - // 1) We are in blocks only mode and peer has no relay permission - // 2) This peer is a block-relay-only peer - if ((!g_relay_txes && !pfrom.HasPermission(PF_RELAY)) || (pfrom.m_tx_relay == nullptr)) + // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off + if (!fRelayTxes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) { - LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom.GetId()); - pfrom.fDisconnect = true; - return; + LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); + return true; } + std::deque vWorkQueue; + std::vector vEraseQueue; CTransactionRef ptx; vRecv >> ptx; const CTransaction& tx = *ptx; - const uint256& txid = ptx->GetHash(); - const uint256& wtxid = ptx->GetWitnessHash(); + CInv inv(MSG_TX, tx.GetHash()); + pfrom->AddInventoryKnown(inv); LOCK2(cs_main, g_cs_orphans); - CNodeState* nodestate = State(pfrom.GetId()); - - const uint256& hash = nodestate->m_wtxid_relay ? wtxid : txid; - pfrom.AddKnownTx(hash); - if (nodestate->m_wtxid_relay && txid != wtxid) { - // Insert txid into filterInventoryKnown, even for - // wtxidrelay peers. This prevents re-adding of - // unconfirmed parents to the recently_announced - // filter, when a child tx is requested. See - // ProcessGetData(). - pfrom.AddKnownTx(txid); - } - - m_txrequest.ReceivedResponse(pfrom.GetId(), txid); - if (tx.HasWitness()) m_txrequest.ReceivedResponse(pfrom.GetId(), wtxid); - - // We do the AlreadyHaveTx() check using wtxid, rather than txid - in the - // absence of witness malleation, this is strictly better, because the - // recent rejects filter may contain the wtxid but rarely contains - // the txid of a segwit transaction that has been rejected. - // In the presence of witness malleation, it's possible that by only - // doing the check with wtxid, we could overlook a transaction which - // was confirmed with a different witness, or exists in our mempool - // with a different witness, but this has limited downside: - // mempool validation does its own lookup of whether we have the txid - // already; and an adversary can already relay us old transactions - // (older than our recency filter) if trying to DoS us, without any need - // for witness malleation. - if (AlreadyHaveTx(GenTxid(/* is_wtxid=*/true, wtxid), m_mempool)) { - if (pfrom.HasPermission(PF_FORCERELAY)) { - // Always relay transactions received from peers with forcerelay - // permission, even if they were already in the mempool, allowing - // the node to function as a gateway for nodes hidden behind it. - if (!m_mempool.exists(tx.GetHash())) { - LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId()); - } else { - LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId()); - RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), m_connman); - } - } - return; - } + bool fMissingInputs = false; + CValidationState state; + + pfrom->setAskFor.erase(inv.hash); + mapAlreadyAskedFor.erase(inv.hash); - TxValidationState state; std::list lRemovedTxn; - if (AcceptToMemoryPool(m_mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */)) { - m_mempool.check(&::ChainstateActive().CoinsTip()); - // As this version of the transaction was acceptable, we can forget about any - // requests for it. - m_txrequest.ForgetTxHash(tx.GetHash()); - m_txrequest.ForgetTxHash(tx.GetWitnessHash()); - RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), m_connman); + if (!AlreadyHave(inv) && + AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { + mempool.check(pcoinsTip.get()); + RelayTransaction(tx, connman); for (unsigned int i = 0; i < tx.vout.size(); i++) { - auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(txid, i)); - if (it_by_prev != mapOrphanTransactionsByPrev.end()) { - for (const auto& elem : it_by_prev->second) { - peer->m_orphan_work_set.insert(elem->first); - } - } + vWorkQueue.emplace_back(inv.hash, i); } - pfrom.nLastTXTime = GetTime(); + pfrom->nLastTXTime = GetTime(); LogPrint(BCLog::MEMPOOL, "AcceptToMemoryPool: peer=%d: accepted %s (poolsz %u txn, %u kB)\n", - pfrom.GetId(), + pfrom->GetId(), tx.GetHash().ToString(), - m_mempool.size(), m_mempool.DynamicMemoryUsage() / 1000); + mempool.size(), mempool.DynamicMemoryUsage() / 1000); - for (const CTransactionRef& removedTx : lRemovedTxn) { - AddToCompactExtraTransactions(removedTx); + // Recursively process any orphan transactions that depended on this one + std::set setMisbehaving; + while (!vWorkQueue.empty()) { + auto itByPrev = mapOrphanTransactionsByPrev.find(vWorkQueue.front()); + vWorkQueue.pop_front(); + if (itByPrev == mapOrphanTransactionsByPrev.end()) + continue; + for (auto mi = itByPrev->second.begin(); + mi != itByPrev->second.end(); + ++mi) + { + const CTransactionRef& porphanTx = (*mi)->second.tx; + const CTransaction& orphanTx = *porphanTx; + const uint256& orphanHash = orphanTx.GetHash(); + NodeId fromPeer = (*mi)->second.fromPeer; + bool fMissingInputs2 = false; + // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan + // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get + // anyone relaying LegitTxX banned) + CValidationState stateDummy; + + if (setMisbehaving.count(fromPeer)) + continue; + if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, &fMissingInputs2, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { + LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); + RelayTransaction(orphanTx, connman); + for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { + vWorkQueue.emplace_back(orphanHash, i); + } + vEraseQueue.push_back(orphanHash); + } + else if (!fMissingInputs2) + { + int nDos = 0; + if (stateDummy.IsInvalid(nDos) && nDos > 0) + { + // Punish peer that gave us an invalid orphan tx + Misbehaving(fromPeer, nDos); + setMisbehaving.insert(fromPeer); + LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString()); + } + // Has inputs but not accepted to mempool + // Probably non-standard or insufficient fee + LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString()); + vEraseQueue.push_back(orphanHash); + if (!orphanTx.HasWitness() && !stateDummy.CorruptionPossible()) { + // Do not use rejection cache for witness transactions or + // witness-stripped transactions, as they can have been malleated. + // See https://github.com/bitcoin/bitcoin/issues/8279 for details. + assert(recentRejects); + recentRejects->insert(orphanHash); + } + } + mempool.check(pcoinsTip.get()); + } } - // Recursively process any orphan transactions that depended on this one - ProcessOrphanTx(peer->m_orphan_work_set); + for (uint256 hash : vEraseQueue) + EraseOrphanTx(hash); } - else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) + else if (fMissingInputs) { bool fRejectedParents = false; // It may be the case that the orphans parents have all been rejected - - // Deduplicate parent txids, so that we don't have to loop over - // the same parent txid more than once down below. - std::vector unique_parents; - unique_parents.reserve(tx.vin.size()); for (const CTxIn& txin : tx.vin) { - // We start with all parents, and then remove duplicates below. - unique_parents.push_back(txin.prevout.hash); - } - std::sort(unique_parents.begin(), unique_parents.end()); - unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end()); - for (const uint256& parent_txid : unique_parents) { - if (recentRejects->contains(parent_txid)) { + if (recentRejects->contains(txin.prevout.hash)) { fRejectedParents = true; break; } } if (!fRejectedParents) { - const auto current_time = GetTime(); - - for (const uint256& parent_txid : unique_parents) { - // Here, we only have the txid (and not wtxid) of the - // inputs, so we only request in txid mode, even for - // wtxidrelay peers. - // Eventually we should replace this with an improved - // protocol for getting all unconfirmed parents. - const GenTxid gtxid{/* is_wtxid=*/false, parent_txid}; - pfrom.AddKnownTx(parent_txid); - if (!AlreadyHaveTx(gtxid, m_mempool)) AddTxAnnouncement(pfrom, gtxid, current_time); + uint32_t nFetchFlags = GetFetchFlags(pfrom); + for (const CTxIn& txin : tx.vin) { + CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash); + pfrom->AddInventoryKnown(_inv); + if (!AlreadyHave(_inv)) pfrom->AskFor(_inv); } - AddOrphanTx(ptx, pfrom.GetId()); - - // Once added to the orphan pool, a tx is considered AlreadyHave, and we shouldn't request it anymore. - m_txrequest.ForgetTxHash(tx.GetHash()); - m_txrequest.ForgetTxHash(tx.GetWitnessHash()); + AddOrphanTx(ptx, pfrom->GetId()); - // DoS prevention: do not allow mapOrphanTransactions to grow unbounded (see CVE-2012-3789) + // DoS prevention: do not allow mapOrphanTransactions to grow unbounded unsigned int nMaxOrphanTx = (unsigned int)std::max((int64_t)0, gArgs.GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS)); unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); if (nEvicted > 0) { @@ -3089,85 +2387,63 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat LogPrint(BCLog::MEMPOOL, "not keeping orphan with rejected parents %s\n",tx.GetHash().ToString()); // We will continue to reject this tx since it has rejected // parents so avoid re-requesting it from other peers. - // Here we add both the txid and the wtxid, as we know that - // regardless of what witness is provided, we will not accept - // this, so we don't need to allow for redownload of this txid - // from any of our non-wtxidrelay peers. recentRejects->insert(tx.GetHash()); - recentRejects->insert(tx.GetWitnessHash()); - m_txrequest.ForgetTxHash(tx.GetHash()); - m_txrequest.ForgetTxHash(tx.GetWitnessHash()); } } else { - if (state.GetResult() != TxValidationResult::TX_WITNESS_STRIPPED) { - // We can add the wtxid of this transaction to our reject filter. - // Do not add txids of witness transactions or witness-stripped - // transactions to the filter, as they can have been malleated; - // adding such txids to the reject filter would potentially - // interfere with relay of valid transactions from peers that - // do not support wtxid-based relay. See - // https://github.com/bitcoin/bitcoin/issues/8279 for details. - // We can remove this restriction (and always add wtxids to - // the filter even for witness stripped transactions) once - // wtxid-based relay is broadly deployed. - // See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034 - // for concerns around weakening security of unupgraded nodes - // if we start doing this too early. + if (!tx.HasWitness() && !state.CorruptionPossible()) { + // Do not use rejection cache for witness transactions or + // witness-stripped transactions, as they can have been malleated. + // See https://github.com/bitcoin/bitcoin/issues/8279 for details. assert(recentRejects); - recentRejects->insert(tx.GetWitnessHash()); - m_txrequest.ForgetTxHash(tx.GetWitnessHash()); - // If the transaction failed for TX_INPUTS_NOT_STANDARD, - // then we know that the witness was irrelevant to the policy - // failure, since this check depends only on the txid - // (the scriptPubKey being spent is covered by the txid). - // Add the txid to the reject filter to prevent repeated - // processing of this transaction in the event that child - // transactions are later received (resulting in - // parent-fetching by txid via the orphan-handling logic). - if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && tx.GetWitnessHash() != tx.GetHash()) { - recentRejects->insert(tx.GetHash()); - m_txrequest.ForgetTxHash(tx.GetHash()); - } + recentRejects->insert(tx.GetHash()); if (RecursiveDynamicUsage(*ptx) < 100000) { AddToCompactExtraTransactions(ptx); } + } else if (tx.HasWitness() && RecursiveDynamicUsage(*ptx) < 100000) { + AddToCompactExtraTransactions(ptx); + } + + if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { + // Always relay transactions received from whitelisted peers, even + // if they were already in the mempool or rejected from it due + // to policy, allowing the node to function as a gateway for + // nodes hidden behind it. + // + // Never relay transactions that we would assign a non-zero DoS + // score for, as we expect peers to do the same with us in that + // case. + int nDoS = 0; + if (!state.IsInvalid(nDoS) || nDoS == 0) { + LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); + RelayTransaction(tx, connman); + } else { + LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); + } } } - // If a tx has been detected by recentRejects, we will have reached - // this point and the tx will have been ignored. Because we haven't run - // the tx through AcceptToMemoryPool, we won't have computed a DoS - // score for it or determined exactly why we consider it invalid. - // - // This means we won't penalize any peer subsequently relaying a DoSy - // tx (even if we penalized the first peer who gave it to us) because - // we have to account for recentRejects showing false positives. In - // other words, we shouldn't penalize a peer if we aren't *sure* they - // submitted a DoSy tx. - // - // Note that recentRejects doesn't just record DoSy or invalid - // transactions, but any tx not accepted by the mempool, which may be - // due to node policy (vs. consensus). So we can't blanket penalize a - // peer simply for relaying a tx that our recentRejects has caught, - // regardless of false positives. + for (const CTransactionRef& removedTx : lRemovedTxn) + AddToCompactExtraTransactions(removedTx); - if (state.IsInvalid()) { + int nDoS = 0; + if (state.IsInvalid(nDoS)) + { LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), - pfrom.GetId(), - state.ToString()); - MaybePunishNodeForTx(pfrom.GetId(), state); + pfrom->GetId(), + FormatStateMessage(state)); + if (enable_bip61 && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) { // Never send AcceptToMemoryPool's internal codes over P2P + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(), + state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash)); + } + if (nDoS > 0) { + Misbehaving(pfrom->GetId(), nDoS); + } } - return; } - if (msg_type == NetMsgType::CMPCTBLOCK) - { - // Ignore cmpctblock received while importing - if (fImporting || fReindex) { - LogPrint(BCLog::NET, "Unexpected cmpctblock message received from peer %d\n", pfrom.GetId()); - return; - } + else if (strCommand == NetMsgType::CMPCTBLOCK && !fImporting && !fReindex) // Ignore blocks received while importing + { CBlockHeaderAndShortTxIDs cmpctblock; vRecv >> cmpctblock; @@ -3178,9 +2454,9 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (!LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { // Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers - if (!::ChainstateActive().IsInitialBlockDownload()) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256())); - return; + if (!IsInitialBlockDownload()) + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), uint256())); + return true; } if (!LookupBlockIndex(cmpctblock.header.GetHash())) { @@ -3189,11 +2465,17 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat } const CBlockIndex *pindex = nullptr; - BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, m_chainparams, &pindex)) { - if (state.IsInvalid()) { - MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock"); - return; + CValidationState state; + if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) { + int nDoS; + if (state.IsInvalid(nDoS)) { + if (nDoS > 0) { + LOCK(cs_main); + Misbehaving(pfrom->GetId(), nDoS, strprintf("Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId())); + } else { + LogPrint(BCLog::NET, "Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId()); + } + return true; } } @@ -3217,13 +2499,13 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat LOCK2(cs_main, g_cs_orphans); // If AcceptBlockHeader returned true, it set pindex assert(pindex); - UpdateBlockAvailability(pfrom.GetId(), pindex->GetBlockHash()); + UpdateBlockAvailability(pfrom->GetId(), pindex->GetBlockHash()); - CNodeState *nodestate = State(pfrom.GetId()); + CNodeState *nodestate = State(pfrom->GetId()); // If this was a new header with more work than our tip, update the // peer's last block announcement time - if (received_new_header && pindex->nChainWork > ::ChainActive().Tip()->nChainWork) { + if (received_new_header && pindex->nChainWork > chainActive.Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } @@ -3231,58 +2513,58 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat bool fAlreadyInFlight = blockInFlightIt != mapBlocksInFlight.end(); if (pindex->nStatus & BLOCK_HAVE_DATA) // Nothing to do here - return; + return true; - if (pindex->nChainWork <= ::ChainActive().Tip()->nChainWork || // We know something better + if (pindex->nChainWork <= chainActive.Tip()->nChainWork || // We know something better pindex->nTx != 0) { // We had this block at some point, but pruned it if (fAlreadyInFlight) { // We requested this block for some reason, but our mempool will probably be useless // so we just grab the block via normal getdata std::vector vInv(1); vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash()); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); } - return; + return true; } // If we're not close to tip yet, give up and let parallel block fetch work its magic - if (!fAlreadyInFlight && !CanDirectFetch(m_chainparams.GetConsensus())) - return; + if (!fAlreadyInFlight && !CanDirectFetch(chainparams.GetConsensus())) + return true; - if (IsWitnessEnabled(pindex->pprev, m_chainparams.GetConsensus()) && !nodestate->fSupportsDesiredCmpctVersion) { + if (IsWitnessEnabled(pindex->pprev, chainparams.GetConsensus()) && !nodestate->fSupportsDesiredCmpctVersion) { // Don't bother trying to process compact blocks from v1 peers // after segwit activates. - return; + return true; } // We want to be a bit conservative just to be extra careful about DoS // possibilities in compact block processing... - if (pindex->nHeight <= ::ChainActive().Height() + 2) { + if (pindex->nHeight <= chainActive.Height() + 2) { if ((!fAlreadyInFlight && nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) || - (fAlreadyInFlight && blockInFlightIt->second.first == pfrom.GetId())) { + (fAlreadyInFlight && blockInFlightIt->second.first == pfrom->GetId())) { std::list::iterator* queuedBlockIt = nullptr; - if (!MarkBlockAsInFlight(m_mempool, pfrom.GetId(), pindex->GetBlockHash(), pindex, &queuedBlockIt)) { + if (!MarkBlockAsInFlight(pfrom->GetId(), pindex->GetBlockHash(), pindex, &queuedBlockIt)) { if (!(*queuedBlockIt)->partialBlock) - (*queuedBlockIt)->partialBlock.reset(new PartiallyDownloadedBlock(&m_mempool)); + (*queuedBlockIt)->partialBlock.reset(new PartiallyDownloadedBlock(&mempool)); else { // The block was already in flight using compact blocks from the same peer LogPrint(BCLog::NET, "Peer sent us compact block we were already syncing!\n"); - return; + return true; } } PartiallyDownloadedBlock& partialBlock = *(*queuedBlockIt)->partialBlock; ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact); if (status == READ_STATUS_INVALID) { - MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect - Misbehaving(pfrom.GetId(), 100, "invalid compact block"); - return; + MarkBlockAsReceived(pindex->GetBlockHash()); // Reset in-flight state in case of whitelist + Misbehaving(pfrom->GetId(), 100, strprintf("Peer %d sent us invalid compact block\n", pfrom->GetId())); + return true; } else if (status == READ_STATUS_FAILED) { // Duplicate txindexes, the block is now in-flight, so just request it std::vector vInv(1); vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash()); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); - return; + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); + return true; } BlockTransactionsRequest req; @@ -3298,7 +2580,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat fProcessBLOCKTXN = true; } else { req.blockhash = pindex->GetBlockHash(); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req)); } } else { // This block is either already in flight from a different @@ -3306,11 +2588,11 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // download from. // Optimistically try to reconstruct anyway since we might be // able to without any round trips. - PartiallyDownloadedBlock tempBlock(&m_mempool); + PartiallyDownloadedBlock tempBlock(&mempool); ReadStatus status = tempBlock.InitData(cmpctblock, vExtraTxnForCompact); if (status != READ_STATUS_OK) { // TODO: don't ignore failures - return; + return true; } std::vector dummy; status = tempBlock.FillBlock(*pblock, dummy); @@ -3324,8 +2606,8 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // mempool will probably be useless - request the block normally std::vector vInv(1); vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash()); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); - return; + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); + return true; } else { // If this was an announce-cmpctblock, we want the same treatment as a header message fRevertToHeaderProcessing = true; @@ -3333,17 +2615,17 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat } } // cs_main - if (fProcessBLOCKTXN) { - return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, time_received, interruptMsgProc); - } + if (fProcessBLOCKTXN) + return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, interruptMsgProc, enable_bip61); if (fRevertToHeaderProcessing) { // Headers received from HB compact block peers are permitted to be // relayed before full validation (see BIP 152), so we don't want to disconnect // the peer if the header turns out to be for an invalid block. // Note that if a peer tries to build on an invalid chain, that - // will be detected and the peer will be disconnected/discouraged. - return ProcessHeadersMessage(pfrom, {cmpctblock.header}, /*via_compact_block=*/true); + // will be detected and the peer will be banned. + bool success = ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_duplicate_invalid=*/false); + return success; } if (fBlockReconstructed) { @@ -3351,7 +2633,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // block that is in flight from some other peer. { LOCK(cs_main); - mapBlockSource.emplace(pblock->GetHash(), std::make_pair(pfrom.GetId(), false)); + mapBlockSource.emplace(pblock->GetHash(), std::make_pair(pfrom->GetId(), false)); } bool fNewBlock = false; // Setting fForceProcessing to true means that we bypass some of @@ -3363,11 +2645,9 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // we have a chain with at least nMinimumChainWork), and we ignore // compact blocks with less work than our tip, it is safe to treat // reconstructed compact blocks as having been requested. - m_chainman.ProcessNewBlock(m_chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock); - std::string bhhh = pblock->GetHash().GetHex(); - + ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, pblock->IsProofOfStake(), &fNewBlock); if (fNewBlock) { - pfrom.nLastBlockTime = GetTime(); + pfrom->nLastBlockTime = GetTime(); } else { LOCK(cs_main); mapBlockSource.erase(pblock->GetHash()); @@ -3381,17 +2661,11 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat MarkBlockAsReceived(pblock->GetHash()); } } - return; + } - if (msg_type == NetMsgType::BLOCKTXN) + else if (strCommand == NetMsgType::BLOCKTXN && !fImporting && !fReindex) // Ignore blocks received while importing { - // Ignore blocktxn received while importing - if (fImporting || fReindex) { - LogPrint(BCLog::NET, "Unexpected blocktxn message received from peer %d\n", pfrom.GetId()); - return; - } - BlockTransactions resp; vRecv >> resp; @@ -3402,22 +2676,22 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat std::map::iterator> >::iterator it = mapBlocksInFlight.find(resp.blockhash); if (it == mapBlocksInFlight.end() || !it->second.second->partialBlock || - it->second.first != pfrom.GetId()) { - LogPrint(BCLog::NET, "Peer %d sent us block transactions for block we weren't expecting\n", pfrom.GetId()); - return; + it->second.first != pfrom->GetId()) { + LogPrint(BCLog::NET, "Peer %d sent us block transactions for block we weren't expecting\n", pfrom->GetId()); + return true; } PartiallyDownloadedBlock& partialBlock = *it->second.second->partialBlock; ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); if (status == READ_STATUS_INVALID) { - MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect - Misbehaving(pfrom.GetId(), 100, "invalid compact block/non-matching block transactions"); - return; + MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case of whitelist + Misbehaving(pfrom->GetId(), 100, strprintf("Peer %d sent us invalid compact block/non-matching block transactions\n", pfrom->GetId())); + return true; } else if (status == READ_STATUS_FAILED) { // Might have collided, fall back to getdata now :( std::vector invs; invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(pfrom), resp.blockhash)); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, invs)); } else { // Block is either okay, or possibly we received // READ_STATUS_CHECKBLOCK_FAILED. @@ -3430,21 +2704,20 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // 3. the block is otherwise invalid (eg invalid coinbase, // block is too big, too many legacy sigops, etc). // So if CheckBlock failed, #3 is the only possibility. - // Under BIP 152, we don't discourage the peer unless proof of work is + // Under BIP 152, we don't DoS-ban unless proof of work is // invalid (we don't require all the stateless checks to have // been run). This is handled below, so just treat this as // though the block was successfully read, and rely on the // handling in ProcessNewBlock to ensure the block index is - // updated, etc. + // updated, reject messages go out, etc. MarkBlockAsReceived(resp.blockhash); // it is now an empty pointer fBlockRead = true; - // mapBlockSource is used for potentially punishing peers and - // updating which peers send us compact blocks, so the race - // between here and cs_main in ProcessNewBlock is fine. + // mapBlockSource is only used for sending reject messages and DoS scores, + // so the race between here and cs_main in ProcessNewBlock is fine. // BIP 152 permits peers to relay compact blocks after validating // the header only; we should not punish peers if the block turns // out to be invalid. - mapBlockSource.emplace(resp.blockhash, std::make_pair(pfrom.GetId(), false)); + mapBlockSource.emplace(resp.blockhash, std::make_pair(pfrom->GetId(), false)); } } // Don't hold cs_main when we call into ProcessNewBlock if (fBlockRead) { @@ -3455,62 +2728,51 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // disk-space attacks), but this should be safe due to the // protections in the compact block handler -- see related comment // in compact block optimistic reconstruction handling. - m_chainman.ProcessNewBlock(m_chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock); + ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, pblock->IsProofOfStake(), &fNewBlock); if (fNewBlock) { - pfrom.nLastBlockTime = GetTime(); + pfrom->nLastBlockTime = GetTime(); } else { LOCK(cs_main); mapBlockSource.erase(pblock->GetHash()); } } - return; } - if (msg_type == NetMsgType::HEADERS) - { - // Ignore headers received while importing - if (fImporting || fReindex) { - LogPrint(BCLog::NET, "Unexpected headers message received from peer %d\n", pfrom.GetId()); - return; - } + else if (strCommand == NetMsgType::HEADERS && !fImporting && !fReindex) // Ignore headers received while importing + { std::vector headers; // Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks. unsigned int nCount = ReadCompactSize(vRecv); if (nCount > MAX_HEADERS_RESULTS) { - Misbehaving(pfrom.GetId(), 20, strprintf("headers message size = %u", nCount)); - return; + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 20, strprintf("headers message size = %u", nCount)); + return false; } headers.resize(nCount); for (unsigned int n = 0; n < nCount; n++) { - //vRecv >> headers[n]; - //ReadCompactSize(vRecv); // ignore tx count; assume it is 0. - CBlock b; + CBlock b; vRecv >> b; - ReadCompactSize(vRecv); // ignore tx count; assume it is 0. headers[n] = b.GetBlockHeader(); - // int size = ReadCompactSize(vRecv); // ignore tx count; assume it is 0. - int xx = 1; - + // int size = ReadCompactSize(vRecv); // ignore tx count; assume it is 0. } - return ProcessHeadersMessage(pfrom, headers, /*via_compact_block=*/false); + // Headers received via a HEADERS message should be valid, and reflect + // the chain the peer is on. If we receive a known-invalid header, + // disconnect the peer if it is using one of our outbound connection + // slots. + bool should_punish = !pfrom->fInbound && !pfrom->m_manual_connection; + bool success = ProcessHeadersMessage(pfrom, connman, headers, chainparams, should_punish); + return success; } - if (msg_type == NetMsgType::BLOCK) + else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) // Ignore blocks received while importing { - // Ignore block received while importing - if (fImporting || fReindex) { - LogPrint(BCLog::NET, "Unexpected block message received from peer %d\n", pfrom.GetId()); - return; - } - std::shared_ptr pblock = std::make_shared(); vRecv >> *pblock; - - LogPrint(BCLog::NET, "received block %s peer=%d\n", pblock->GetHash().ToString(), pfrom.GetId()); - + LogPrint(BCLog::NET, "received block %s peer=%d\n", pblock->GetHash().ToString(), pfrom->GetId()); + bool forceProcessing = false; const uint256 hash(pblock->GetHash()); { @@ -3518,85 +2780,106 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // Also always process if we requested the block explicitly, as we may // need it even though it is not a candidate for a new best tip. forceProcessing |= MarkBlockAsReceived(hash); - // mapBlockSource is only used for punishing peers and setting - // which peers send us compact blocks, so the race between here and - // cs_main in ProcessNewBlock is fine. - mapBlockSource.emplace(hash, std::make_pair(pfrom.GetId(), true)); + // mapBlockSource is only used for sending reject messages and DoS scores, + // so the race between here and cs_main in ProcessNewBlock is fine. + mapBlockSource.emplace(hash, std::make_pair(pfrom->GetId(), true)); } bool fNewBlock = false; - m_chainman.ProcessNewBlock(m_chainparams, pblock, forceProcessing, &fNewBlock); + + // ppcoin: prune excessive orphan blocks + PruneOrphanBlocks(); + + // ppcoin: verify hash target and signature of coinstake tx + if (pblock->IsProofOfStake()) + { + uint256 hashProofOfStake = uint256(); + if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, hashProofOfStake)) + { + //if (IsInitialBlockDownload() == false) + //{ + // printf("WARNING: ProcessBlock(): check proof-of-stake failed for block %s\n", pblock->GetHash().ToString().c_str()); + // /* + // // Re-request 'em immediately -- see Peershares codebase, pull request #110 + // if (pfrom) + // { + // std::vector invs; + // invs.push_back(CInv(MSG_BLOCK, pblock->GetHash())); + // connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, invs)); + // }*/ + //} + LogPrintf("Got new topblock with hash: %s\n", pblock->GetHash().ToString()); + }else + { + if (!mapProofOfStake.count(pblock->GetHash())) // add to mapProofOfStake + mapProofOfStake.insert(std::make_pair(pblock->GetHash(), hashProofOfStake)); + + ProcessNewBlock(chainparams, pblock, forceProcessing, pblock->IsProofOfStake(), &fNewBlock); + } + }else + { + ProcessNewBlock(chainparams, pblock, forceProcessing, pblock->IsProofOfStake(), &fNewBlock); + } if (fNewBlock) { - pfrom.nLastBlockTime = GetTime(); + pfrom->nLastBlockTime = GetTime(); } else { LOCK(cs_main); mapBlockSource.erase(pblock->GetHash()); } - return; } - if (msg_type == NetMsgType::GETADDR) { + else if (strCommand == NetMsgType::GETADDR) + { // This asymmetric behavior for inbound and outbound connections was introduced // to prevent a fingerprinting attack: an attacker can send specific fake addresses // to users' AddrMan and later request them by sending getaddr messages. // Making nodes which are behind NAT and can only make outgoing connections ignore // the getaddr message mitigates the attack. - if (!pfrom.IsInboundConn()) { - LogPrint(BCLog::NET, "Ignoring \"getaddr\" from %s connection. peer=%d\n", pfrom.ConnectionTypeAsString(), pfrom.GetId()); - return; + if (!pfrom->fInbound) { + LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom->GetId()); + return true; } // Only send one GetAddr response per connection to reduce resource waste // and discourage addr stamping of INV announcements. - if (pfrom.fSentAddr) { - LogPrint(BCLog::NET, "Ignoring repeated \"getaddr\". peer=%d\n", pfrom.GetId()); - return; + if (pfrom->fSentAddr) { + LogPrint(BCLog::NET, "Ignoring repeated \"getaddr\". peer=%d\n", pfrom->GetId()); + return true; } - pfrom.fSentAddr = true; + pfrom->fSentAddr = true; - pfrom.vAddrToSend.clear(); - std::vector vAddr; - if (pfrom.HasPermission(PF_ADDR)) { - vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); - } else { - vAddr = m_connman.GetAddresses(pfrom, MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); - } + pfrom->vAddrToSend.clear(); + std::vector vAddr = connman->GetAddresses(); FastRandomContext insecure_rand; - for (const CAddress &addr : vAddr) { - pfrom.PushAddress(addr, insecure_rand); - } - return; + for (const CAddress &addr : vAddr) + pfrom->PushAddress(addr, insecure_rand); } - if (msg_type == NetMsgType::MEMPOOL) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM) && !pfrom.HasPermission(PF_MEMPOOL)) + + else if (strCommand == NetMsgType::MEMPOOL) + { + if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) { - if (!pfrom.HasPermission(PF_NOBAN)) - { - LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom.GetId()); - pfrom.fDisconnect = true; - } - return; + LogPrint(BCLog::NET, "mempool request with bloom filters disabled, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + return true; } - if (m_connman.OutboundTargetReached(false) && !pfrom.HasPermission(PF_MEMPOOL)) + if (connman->OutboundTargetReached(false) && !pfrom->fWhitelisted) { - if (!pfrom.HasPermission(PF_NOBAN)) - { - LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom.GetId()); - pfrom.fDisconnect = true; - } - return; + LogPrint(BCLog::NET, "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId()); + pfrom->fDisconnect = true; + return true; } - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_tx_inventory); - pfrom.m_tx_relay->fSendMempool = true; - } - return; + LOCK(pfrom->cs_inventory); + pfrom->fSendMempool = true; } - if (msg_type == NetMsgType::PING) { - if (pfrom.GetCommonVersion() > BIP0031_VERSION) { + + else if (strCommand == NetMsgType::PING) + { + if (pfrom->nVersion > BIP0031_VERSION) + { uint64_t nonce = 0; vRecv >> nonce; // Echo the message back with the nonce. This allows for two useful features: @@ -3610,13 +2893,14 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // it, if the remote node sends a ping once per second and this node takes 5 // seconds to respond to each, the 5th ping the remote sends would appear to // return very quickly. - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::PONG, nonce)); + connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::PONG, nonce)); } - return; } - if (msg_type == NetMsgType::PONG) { - const auto ping_end = time_received; + + else if (strCommand == NetMsgType::PONG) + { + int64_t pingUsecEnd = nTimeReceived; uint64_t nonce = 0; size_t nAvail = vRecv.in_avail(); bool bPingFinished = false; @@ -3626,15 +2910,15 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat vRecv >> nonce; // Only process pong message if there is an outstanding ping (old ping without nonce should never pong) - if (pfrom.nPingNonceSent != 0) { - if (nonce == pfrom.nPingNonceSent) { + if (pfrom->nPingNonceSent != 0) { + if (nonce == pfrom->nPingNonceSent) { // Matching pong received, this ping is no longer outstanding bPingFinished = true; - const auto ping_time = ping_end - pfrom.m_ping_start.load(); - if (ping_time.count() >= 0) { + int64_t pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart; + if (pingUsecTime > 0) { // Successful ping time measurement, replace previous - pfrom.nPingUsecTime = count_microseconds(ping_time); - pfrom.nMinPingUsecTime = std::min(pfrom.nMinPingUsecTime.load(), count_microseconds(ping_time)); + pfrom->nPingUsecTime = pingUsecTime; + pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime.load(), pingUsecTime); } else { // This should never happen sProblem = "Timing mishap"; @@ -3659,45 +2943,41 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (!(sProblem.empty())) { LogPrint(BCLog::NET, "pong peer=%d: %s, %x expected, %x received, %u bytes\n", - pfrom.GetId(), + pfrom->GetId(), sProblem, - pfrom.nPingNonceSent, + pfrom->nPingNonceSent, nonce, nAvail); } if (bPingFinished) { - pfrom.nPingNonceSent = 0; + pfrom->nPingNonceSent = 0; } - return; } - if (msg_type == NetMsgType::FILTERLOAD) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM)) { - pfrom.fDisconnect = true; - return; - } + + else if (strCommand == NetMsgType::FILTERLOAD) + { CBloomFilter filter; vRecv >> filter; if (!filter.IsWithinSizeConstraints()) { // There is no excuse for sending a too-large filter - Misbehaving(pfrom.GetId(), 100, "too-large bloom filter"); + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 100); } - else if (pfrom.m_tx_relay != nullptr) + else { - LOCK(pfrom.m_tx_relay->cs_filter); - pfrom.m_tx_relay->pfilter.reset(new CBloomFilter(filter)); - pfrom.m_tx_relay->fRelayTxes = true; + LOCK(pfrom->cs_filter); + pfrom->pfilter.reset(new CBloomFilter(filter)); + pfrom->pfilter->UpdateEmptyFull(); + pfrom->fRelayTxes = true; } - return; } - if (msg_type == NetMsgType::FILTERADD) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM)) { - pfrom.fDisconnect = true; - return; - } + + else if (strCommand == NetMsgType::FILTERADD) + { std::vector vData; vRecv >> vData; @@ -3706,160 +2986,110 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat bool bad = false; if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { bad = true; - } else if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - if (pfrom.m_tx_relay->pfilter) { - pfrom.m_tx_relay->pfilter->insert(vData); + } else { + LOCK(pfrom->cs_filter); + if (pfrom->pfilter) { + pfrom->pfilter->insert(vData); } else { bad = true; } } if (bad) { - Misbehaving(pfrom.GetId(), 100, "bad filteradd message"); + LOCK(cs_main); + Misbehaving(pfrom->GetId(), 100); } - return; } - if (msg_type == NetMsgType::FILTERCLEAR) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM)) { - pfrom.fDisconnect = true; - return; - } - if (pfrom.m_tx_relay == nullptr) { - return; + + else if (strCommand == NetMsgType::FILTERCLEAR) + { + LOCK(pfrom->cs_filter); + if (pfrom->GetLocalServices() & NODE_BLOOM) { + pfrom->pfilter.reset(new CBloomFilter()); } - LOCK(pfrom.m_tx_relay->cs_filter); - pfrom.m_tx_relay->pfilter = nullptr; - pfrom.m_tx_relay->fRelayTxes = true; - return; + pfrom->fRelayTxes = true; } - if (msg_type == NetMsgType::FEEFILTER) { + else if (strCommand == NetMsgType::FEEFILTER) { CAmount newFeeFilter = 0; vRecv >> newFeeFilter; if (MoneyRange(newFeeFilter)) { - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_feeFilter); - pfrom.m_tx_relay->minFeeFilter = newFeeFilter; + { + LOCK(pfrom->cs_feeFilter); + pfrom->minFeeFilter = newFeeFilter; } - LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom.GetId()); + LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->GetId()); } - return; } - if (msg_type == NetMsgType::GETCFILTERS) { - ProcessGetCFilters(pfrom, vRecv, m_chainparams, m_connman); - return; + else if (strCommand == NetMsgType::NOTFOUND) { + // We do not care about the NOTFOUND message, but logging an Unknown Command + // message would be undesirable as we transmit it ourselves. } - if (msg_type == NetMsgType::GETCFHEADERS) { - ProcessGetCFHeaders(pfrom, vRecv, m_chainparams, m_connman); - return; + else { + // Ignore unknown commands for extensibility + LogPrint(BCLog::NET, "Unknown command \"%s\" from peer=%d\n", SanitizeString(strCommand), pfrom->GetId()); } - if (msg_type == NetMsgType::GETCFCHECKPT) { - ProcessGetCFCheckPt(pfrom, vRecv, m_chainparams, m_connman); - return; - } - if (msg_type == NetMsgType::NOTFOUND) { - std::vector vInv; - vRecv >> vInv; - if (vInv.size() <= MAX_PEER_TX_ANNOUNCEMENTS + MAX_BLOCKS_IN_TRANSIT_PER_PEER) { - LOCK(::cs_main); - for (CInv &inv : vInv) { - if (inv.IsGenTxMsg()) { - // If we receive a NOTFOUND message for a tx we requested, mark the announcement for it as - // completed in TxRequestTracker. - m_txrequest.ReceivedResponse(pfrom.GetId(), inv.hash); - } - } - } - return; - } - // Ignore unknown commands for extensibility - LogPrint(BCLog::NET, "Unknown command \"%s\" from peer=%d\n", SanitizeString(msg_type), pfrom.GetId()); - return; + return true; } -bool PeerManager::MaybeDiscourageAndDisconnect(CNode& pnode) +static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman, bool enable_bip61) { - const NodeId peer_id{pnode.GetId()}; - PeerRef peer = GetPeerRef(peer_id); - if (peer == nullptr) return false; - - { - LOCK(peer->m_misbehavior_mutex); - - // There's nothing to do if the m_should_discourage flag isn't set - if (!peer->m_should_discourage) return false; - - peer->m_should_discourage = false; - } // peer.m_misbehavior_mutex - - if (pnode.HasPermission(PF_NOBAN)) { - // We never disconnect or discourage peers for bad behavior if they have the NOBAN permission flag - LogPrintf("Warning: not punishing noban peer %d!\n", peer_id); - return false; - } - - if (pnode.IsManualConn()) { - // We never disconnect or discourage manual peers for bad behavior - LogPrintf("Warning: not punishing manually connected peer %d!\n", peer_id); - return false; - } - - if (pnode.addr.IsLocal()) { - // We disconnect local peers for bad behavior but don't discourage (since that would discourage - // all peers on the same local address) - LogPrintf("Warning: disconnecting but not discouraging local peer %d!\n", peer_id); - pnode.fDisconnect = true; + AssertLockHeld(cs_main); + CNodeState &state = *State(pnode->GetId()); + + if (enable_bip61) { + for (const CBlockReject& reject : state.rejects) { + connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, std::string(NetMsgType::BLOCK), reject.chRejectCode, reject.strRejectReason, reject.hashBlock)); + } + } + state.rejects.clear(); + + if (state.fShouldBan) { + state.fShouldBan = false; + if (pnode->fWhitelisted) + LogPrintf("Warning: not punishing whitelisted peer %s!\n", pnode->addr.ToString()); + else if (pnode->m_manual_connection) + LogPrintf("Warning: not punishing manually-connected peer %s!\n", pnode->addr.ToString()); + else { + pnode->fDisconnect = true; + if (pnode->addr.IsLocal()) + LogPrintf("Warning: not banning local peer %s!\n", pnode->addr.ToString()); + else + { + connman->Ban(pnode->addr, BanReasonNodeMisbehaving); + } + } return true; } - - // Normal case: Disconnect the peer and discourage all nodes sharing the address - LogPrintf("Disconnecting and discouraging peer %d!\n", peer_id); - if (m_banman) m_banman->Discourage(pnode.addr); - m_connman.DisconnectNode(pnode.addr); - return true; + return false; } -bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic& interruptMsgProc) +bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic& interruptMsgProc) { + const CChainParams& chainparams = Params(); + // + // Message format + // (4) message start + // (12) command + // (4) size + // (4) checksum + // (x) data + // bool fMoreWork = false; - PeerRef peer = GetPeerRef(pfrom->GetId()); - if (peer == nullptr) return false; - - { - LOCK(peer->m_getdata_requests_mutex); - if (!peer->m_getdata_requests.empty()) { - ProcessGetData(*pfrom, *peer, m_chainparams, m_connman, m_mempool, interruptMsgProc); - } - } - - { - LOCK2(cs_main, g_cs_orphans); - if (!peer->m_orphan_work_set.empty()) { - ProcessOrphanTx(peer->m_orphan_work_set); - } - } + if (!pfrom->vRecvGetData.empty()) + ProcessGetData(pfrom, chainparams, connman, interruptMsgProc); if (pfrom->fDisconnect) return false; // this maintains the order of responses - // and prevents m_getdata_requests to grow unbounded - { - LOCK(peer->m_getdata_requests_mutex); - if (!peer->m_getdata_requests.empty()) return true; - } - - { - LOCK(g_cs_orphans); - if (!peer->m_orphan_work_set.empty()) return true; - } + if (!pfrom->vRecvGetData.empty()) return true; // Don't bother if send buffer is too full to respond anyway if (pfrom->fPauseSend) @@ -3872,49 +3102,110 @@ bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic& interruptMsgP return false; // Just take one message msgs.splice(msgs.begin(), pfrom->vProcessMsg, pfrom->vProcessMsg.begin()); - pfrom->nProcessQueueSize -= msgs.front().m_raw_message_size; - pfrom->fPauseRecv = pfrom->nProcessQueueSize > m_connman.GetReceiveFloodSize(); + pfrom->nProcessQueueSize -= msgs.front().vRecv.size() + CMessageHeader::HEADER_SIZE; + pfrom->fPauseRecv = pfrom->nProcessQueueSize > connman->GetReceiveFloodSize(); fMoreWork = !pfrom->vProcessMsg.empty(); } CNetMessage& msg(msgs.front()); - msg.SetVersion(pfrom->GetCommonVersion()); - const std::string& msg_type = msg.m_command; + msg.SetVersion(pfrom->GetRecvVersion()); + // Scan for message start + if (memcmp(msg.hdr.pchMessageStart, chainparams.MessageStart(), CMessageHeader::MESSAGE_START_SIZE) != 0) { + LogPrint(BCLog::NET, "PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n", SanitizeString(msg.hdr.GetCommand()), pfrom->GetId()); + pfrom->fDisconnect = true; + return false; + } + + // Read header + CMessageHeader& hdr = msg.hdr; + if (!hdr.IsValid(chainparams.MessageStart())) + { + LogPrint(BCLog::NET, "PROCESSMESSAGE: ERRORS IN HEADER %s peer=%d\n", SanitizeString(hdr.GetCommand()), pfrom->GetId()); + return fMoreWork; + } + std::string strCommand = hdr.GetCommand(); // Message size - unsigned int nMessageSize = msg.m_message_size; + unsigned int nMessageSize = hdr.nMessageSize; + + // Checksum + CDataStream& vRecv = msg.vRecv; + const uint256& hash = msg.GetMessageHash(); + if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) + { + LogPrint(BCLog::NET, "%s(%s, %u bytes): CHECKSUM ERROR expected %s was %s\n", __func__, + SanitizeString(strCommand), nMessageSize, + HexStr(hash.begin(), hash.begin()+CMessageHeader::CHECKSUM_SIZE), + HexStr(hdr.pchChecksum, hdr.pchChecksum+CMessageHeader::CHECKSUM_SIZE)); + return fMoreWork; + } - try { - ProcessMessage(*pfrom, msg_type, msg.m_recv, msg.m_time, interruptMsgProc); - if (interruptMsgProc) return false; + // Process message + bool fRet = false; + try + { + fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.nTime, chainparams, connman, interruptMsgProc, m_enable_bip61); + if (interruptMsgProc) + return false; + if (!pfrom->vRecvGetData.empty()) + fMoreWork = true; + } + catch (const std::ios_base::failure& e) + { + if (m_enable_bip61) { + connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_MALFORMED, std::string("error parsing message"))); + } + if (strstr(e.what(), "end of data")) + { + // Allow exceptions from under-length message on vRecv + LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught, normally caused by a message being shorter than its stated length\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); + } + else if (strstr(e.what(), "size too large")) + { + // Allow exceptions from over-long size + LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); + } + else if (strstr(e.what(), "non-canonical ReadCompactSize()")) + { + // Allow exceptions from non-canonical encoding + LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); + } + else { - LOCK(peer->m_getdata_requests_mutex); - if (!peer->m_getdata_requests.empty()) fMoreWork = true; + PrintExceptionContinue(&e, "ProcessMessages()"); } - } catch (const std::exception& e) { - LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' (%s) caught\n", __func__, SanitizeString(msg_type), nMessageSize, e.what(), typeid(e).name()); + } + catch (const std::exception& e) { + PrintExceptionContinue(&e, "ProcessMessages()"); } catch (...) { - LogPrint(BCLog::NET, "%s(%s, %u bytes): Unknown exception caught\n", __func__, SanitizeString(msg_type), nMessageSize); + PrintExceptionContinue(nullptr, "ProcessMessages()"); + } + + if (!fRet) { + LogPrint(BCLog::NET, "%s(%s, %u bytes) FAILED peer=%d\n", __func__, SanitizeString(strCommand), nMessageSize, pfrom->GetId()); } + LOCK(cs_main); + SendRejectsAndCheckIfBanned(pfrom, connman, m_enable_bip61); + return fMoreWork; } -void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) +void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds) { AssertLockHeld(cs_main); - CNodeState &state = *State(pto.GetId()); - const CNetMsgMaker msgMaker(pto.GetCommonVersion()); + CNodeState &state = *State(pto->GetId()); + const CNetMsgMaker msgMaker(pto->GetSendVersion()); - if (!state.m_chain_sync.m_protect && pto.IsOutboundOrBlockRelayConn() && state.fSyncStarted) { + if (!state.m_chain_sync.m_protect && IsOutboundDisconnectionCandidate(pto) && state.fSyncStarted) { // This is an outbound peer subject to disconnection if they don't // announce a block with as much work as the current tip within // CHAIN_SYNC_TIMEOUT + HEADERS_RESPONSE_TIME seconds (note: if // their chain has more work than ours, we should sync to it, // unless it's invalid, in which case we should find that out and // disconnect from them elsewhere). - if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork) { + if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= chainActive.Tip()->nChainWork) { if (state.m_chain_sync.m_timeout != 0) { state.m_chain_sync.m_timeout = 0; state.m_chain_sync.m_work_header = nullptr; @@ -3926,7 +3217,7 @@ void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) // where we checked against our tip. // Either way, set a new timeout based on current tip. state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT; - state.m_chain_sync.m_work_header = ::ChainActive().Tip(); + state.m_chain_sync.m_work_header = chainActive.Tip(); state.m_chain_sync.m_sent_getheaders = false; } else if (state.m_chain_sync.m_timeout > 0 && time_in_seconds > state.m_chain_sync.m_timeout) { // No evidence yet that our peer has synced to a chain with work equal to that @@ -3934,12 +3225,12 @@ void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) // message to give the peer a chance to update us. if (state.m_chain_sync.m_sent_getheaders) { // They've run out of time to catch up! - LogPrintf("Disconnecting outbound peer %d for old chain, best known block = %s\n", pto.GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : ""); - pto.fDisconnect = true; + LogPrintf("Disconnecting outbound peer %d for old chain, best known block = %s\n", pto->GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : ""); + pto->fDisconnect = true; } else { assert(state.m_chain_sync.m_work_header); - LogPrint(BCLog::NET, "sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", pto.GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "", state.m_chain_sync.m_work_header->GetBlockHash().ToString()); - m_connman.PushMessage(&pto, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); + LogPrint(BCLog::NET, "sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", pto->GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "", state.m_chain_sync.m_work_header->GetBlockHash().ToString()); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); state.m_chain_sync.m_sent_getheaders = true; constexpr int64_t HEADERS_RESPONSE_TIME = 120; // 2 minutes // Bump the timeout to allow a response, which could clear the timeout @@ -3953,10 +3244,10 @@ void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) } } -void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds) +void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) { // Check whether we have too many outbound peers - int extra_peers = m_connman.GetExtraOutboundCount(); + int extra_peers = connman->GetExtraOutboundCount(); if (extra_peers > 0) { // If we have more outbound peers than we target, disconnect one. // Pick the outbound peer that least recently announced @@ -3965,25 +3256,25 @@ void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds) NodeId worst_peer = -1; int64_t oldest_block_announcement = std::numeric_limits::max(); - m_connman.ForEachNode([&](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); + LOCK(cs_main); + + connman->ForEachNode([&](CNode* pnode) { + AssertLockHeld(cs_main); // Ignore non-outbound peers, or nodes marked for disconnect already - if (!pnode->IsOutboundOrBlockRelayConn() || pnode->fDisconnect) return; + if (!IsOutboundDisconnectionCandidate(pnode) || pnode->fDisconnect) return; CNodeState *state = State(pnode->GetId()); if (state == nullptr) return; // shouldn't be possible, but just in case // Don't evict our protected peers if (state->m_chain_sync.m_protect) return; - // Don't evict our block-relay-only peers. - if (pnode->m_tx_relay == nullptr) return; if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) { worst_peer = pnode->GetId(); oldest_block_announcement = state->m_last_block_announcement; } }); if (worst_peer != -1) { - bool disconnected = m_connman.ForNode(worst_peer, [&](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); + bool disconnected = connman->ForNode(worst_peer, [&](CNode *pnode) { + AssertLockHeld(cs_main); // Only disconnect a peer that has been connected to us for // some reasonable fraction of our check-frequency, to give @@ -4006,28 +3297,29 @@ void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds) // detected a stale tip. Don't try any more extra peers until // we next detect a stale tip, to limit the load we put on the // network from these extra connections. - m_connman.SetTryNewOutboundPeer(false); + connman->SetTryNewOutboundPeer(false); } } } } -void PeerManager::CheckForStaleTipAndEvictPeers() +void PeerLogicValidation::CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams) { - LOCK(cs_main); + if (connman == nullptr) return; int64_t time_in_seconds = GetTime(); EvictExtraOutboundPeers(time_in_seconds); if (time_in_seconds > m_stale_tip_check_time) { + LOCK(cs_main); // Check whether our tip is stale, and if so, allow using an extra // outbound peer - if (!fImporting && !fReindex && m_connman.GetNetworkActive() && m_connman.GetUseAddrmanOutgoing() && TipMayBeStale(m_chainparams.GetConsensus())) { + if (TipMayBeStale(consensusParams)) { LogPrintf("Potential stale tip detected, will try using extra outbound peer (last tip update: %d seconds ago)\n", time_in_seconds - g_last_tip_update); - m_connman.SetTryNewOutboundPeer(true); - } else if (m_connman.GetTryNewOutboundPeer()) { - m_connman.SetTryNewOutboundPeer(false); + connman->SetTryNewOutboundPeer(true); + } else if (connman->GetTryNewOutboundPeer()) { + connman->SetTryNewOutboundPeer(false); } m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL; } @@ -4037,116 +3329,100 @@ namespace { class CompareInvMempoolOrder { CTxMemPool *mp; - bool m_wtxid_relay; public: - explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid) + explicit CompareInvMempoolOrder(CTxMemPool *_mempool) { mp = _mempool; - m_wtxid_relay = use_wtxid; } bool operator()(std::set::iterator a, std::set::iterator b) { /* As std::make_heap produces a max-heap, we want the entries with the * fewest ancestors/highest fee to sort later. */ - return mp->CompareDepthAndScore(*b, *a, m_wtxid_relay); + return mp->CompareDepthAndScore(*b, *a); } }; } -bool PeerManager::SendMessages(CNode* pto) +bool PeerLogicValidation::SendMessages(CNode* pto) { - const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); - - // We must call MaybeDiscourageAndDisconnect first, to ensure that we'll - // disconnect misbehaving peers even before the version handshake is complete. - if (MaybeDiscourageAndDisconnect(*pto)) return true; - - // Don't send anything until the version handshake is complete - if (!pto->fSuccessfullyConnected || pto->fDisconnect) - return true; + const Consensus::Params& consensusParams = Params().GetConsensus(); + { + // Don't send anything until the version handshake is complete + if (!pto->fSuccessfullyConnected || pto->fDisconnect) + return true; - // If we get here, the outgoing message serialization version is set and can't change. - const CNetMsgMaker msgMaker(pto->GetCommonVersion()); + // If we get here, the outgoing message serialization version is set and can't change. + const CNetMsgMaker msgMaker(pto->GetSendVersion()); - // - // Message: ping - // - bool pingSend = false; - if (pto->fPingQueued) { - // RPC ping request by user - pingSend = true; - } - if (pto->nPingNonceSent == 0 && pto->m_ping_start.load() + PING_INTERVAL < GetTime()) { - // Ping automatically sent as a latency probe & keepalive. - pingSend = true; - } - if (pingSend) { - uint64_t nonce = 0; - while (nonce == 0) { - GetRandBytes((unsigned char*)&nonce, sizeof(nonce)); - } - pto->fPingQueued = false; - pto->m_ping_start = GetTime(); - if (pto->GetCommonVersion() > BIP0031_VERSION) { - pto->nPingNonceSent = nonce; - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING, nonce)); - } else { - // Peer is too old to support ping command with nonce, pong will never arrive. - pto->nPingNonceSent = 0; - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING)); + // + // Message: ping + // + bool pingSend = false; + if (pto->fPingQueued) { + // RPC ping request by user + pingSend = true; + } + if (pto->nPingNonceSent == 0 && pto->nPingUsecStart + PING_INTERVAL * 1000000 < GetTimeMicros()) { + // Ping automatically sent as a latency probe & keepalive. + pingSend = true; + } + if (pingSend) { + uint64_t nonce = 0; + while (nonce == 0) { + GetRandBytes((unsigned char*)&nonce, sizeof(nonce)); + } + pto->fPingQueued = false; + pto->nPingUsecStart = GetTimeMicros(); + if (pto->nVersion > BIP0031_VERSION) { + pto->nPingNonceSent = nonce; + connman->PushMessage(pto, msgMaker.Make(NetMsgType::PING, nonce)); + } else { + // Peer is too old to support ping command with nonce, pong will never arrive. + pto->nPingNonceSent = 0; + connman->PushMessage(pto, msgMaker.Make(NetMsgType::PING)); + } } - } - { - LOCK(cs_main); + TRY_LOCK(cs_main, lockMain); // Acquire cs_main for IsInitialBlockDownload() and CNodeState() + if (!lockMain) + return true; + if (SendRejectsAndCheckIfBanned(pto, connman, m_enable_bip61)) + return true; CNodeState &state = *State(pto->GetId()); // Address refresh broadcast - auto current_time = GetTime(); - - if (pto->RelayAddrsWithConn() && !::ChainstateActive().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) { + int64_t nNow = GetTimeMicros(); + if (!IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { AdvertiseLocal(pto); - pto->m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); + pto->nNextLocalAddrSend = PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } // // Message: addr // - if (pto->RelayAddrsWithConn() && pto->m_next_addr_send < current_time) { - pto->m_next_addr_send = PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL); + if (pto->nNextAddrSend < nNow) { + pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL); std::vector vAddr; vAddr.reserve(pto->vAddrToSend.size()); - assert(pto->m_addr_known); - - const char* msg_type; - int make_flags; - if (pto->m_wants_addrv2) { - msg_type = NetMsgType::ADDRV2; - make_flags = ADDRV2_FORMAT; - } else { - msg_type = NetMsgType::ADDR; - make_flags = 0; - } - for (const CAddress& addr : pto->vAddrToSend) { - if (!pto->m_addr_known->contains(addr.GetKey())) + if (!pto->addrKnown.contains(addr.GetKey())) { - pto->m_addr_known->insert(addr.GetKey()); + pto->addrKnown.insert(addr.GetKey()); vAddr.push_back(addr); - // receiver rejects addr messages larger than MAX_ADDR_TO_SEND - if (vAddr.size() >= MAX_ADDR_TO_SEND) + // receiver rejects addr messages larger than 1000 + if (vAddr.size() >= 1000) { - m_connman.PushMessage(pto, msgMaker.Make(make_flags, msg_type, vAddr)); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::ADDR, vAddr)); vAddr.clear(); } } } pto->vAddrToSend.clear(); if (!vAddr.empty()) - m_connman.PushMessage(pto, msgMaker.Make(make_flags, msg_type, vAddr)); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::ADDR, vAddr)); // we only send the big addr message once if (pto->vAddrToSend.capacity() > 40) pto->vAddrToSend.shrink_to_fit(); @@ -4154,16 +3430,16 @@ bool PeerManager::SendMessages(CNode* pto) // Start block sync if (pindexBestHeader == nullptr) - pindexBestHeader = ::ChainActive().Tip(); + pindexBestHeader = chainActive.Tip(); - bool canHandlePoSHeaders = pto->nVersion >= VERSION_GETHEADERS_POS; + bool canHandlePoSHeaders = pto->nVersion >= VERSION_GETHEADERS_POS; - bool fFetch = state.fPreferredDownload || (nPreferredDownload == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. + bool fFetch = state.fPreferredDownload || nPreferredDownload == 0 && !pto->fClient && !pto->fOneShot; // Download if this is a nice peer, or we have no nice peers and this one might do. if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. if ((nSyncStarted == 0 && fFetch) || pindexBestHeader->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { state.fSyncStarted = true; - state.nHeadersSyncTimeout = count_microseconds(current_time) + HEADERS_DOWNLOAD_TIMEOUT_BASE + HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER * (GetAdjustedTime() - pindexBestHeader->GetBlockTime())/(consensusParams.nPowTargetSpacing); + state.nHeadersSyncTimeout = GetTimeMicros() + HEADERS_DOWNLOAD_TIMEOUT_BASE + HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER * (GetAdjustedTime() - pindexBestHeader->GetBlockTime())/(consensusParams.nPowTargetSpacing); nSyncStarted++; const CBlockIndex *pindexStart = pindexBestHeader; /* If possible, start at the block preceding the currently @@ -4175,11 +3451,28 @@ bool PeerManager::SendMessages(CNode* pto) got back an empty response. */ if (pindexStart->pprev) pindexStart = pindexStart->pprev; - LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), pto->nStartingHeight); - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexStart), uint256())); + + if (canHandlePoSHeaders) + { + LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), pto->nStartingHeight); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexStart), uint256())); + }else + { + // older client, revert to using getblocks + connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETBLOCKS, chainActive.GetLocator(pindexStart), uint256())); + LogPrint(BCLog::NET, "getblocks (%d) to peer=%d\n", pindexStart->nHeight, pto->GetId()); + } } } + // Resend wallet transactions that haven't gotten in a block yet + // Except during reindex, importing and IBD, when old wallet + // transactions become unconfirmed and spams other nodes. + if (!fReindex && !fImporting && !IsInitialBlockDownload()) + { + GetMainSignals().Broadcast(nTimeBestReceived, connman); + } + // // Try sending block announcements via headers // @@ -4193,10 +3486,9 @@ bool PeerManager::SendMessages(CNode* pto) // add all to the inv queue. LOCK(pto->cs_inventory); std::vector vHeaders; - bool canHandlePoSHeaders = pto->nVersion >= VERSION_GETHEADERS_POS; bool fRevertToInv = (canHandlePoSHeaders == false || - (!state.fPreferHeaders && + (!state.fPreferHeaders && (!state.fPreferHeaderAndIDs || pto->vBlockHashesToAnnounce.size() > 1)) || pto->vBlockHashesToAnnounce.size() > MAX_BLOCKS_TO_ANNOUNCE); const CBlockIndex *pBestIndex = nullptr; // last header queued for delivery @@ -4206,11 +3498,11 @@ bool PeerManager::SendMessages(CNode* pto) bool fFoundStartingHeader = false; // Try to find first header that our peer doesn't have, and // then send all headers past that one. If we come across any - // headers that aren't on ::ChainActive(), give up. + // headers that aren't on chainActive, give up. for (const uint256 &hash : pto->vBlockHashesToAnnounce) { const CBlockIndex* pindex = LookupBlockIndex(hash); assert(pindex); - if (::ChainActive()[pindex->nHeight] != pindex) { + if (chainActive[pindex->nHeight] != pindex) { // Bail out if we reorged away from this block fRevertToInv = true; break; @@ -4263,10 +3555,10 @@ bool PeerManager::SendMessages(CNode* pto) LOCK(cs_most_recent_block); if (most_recent_block_hash == pBestIndex->GetBlockHash()) { if (state.fWantsCmpctWitness || !fWitnessesPresentInMostRecentCompactBlock) - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *most_recent_compact_block)); + connman->PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *most_recent_compact_block)); else { CBlockHeaderAndShortTxIDs cmpctblock(*most_recent_block, state.fWantsCmpctWitness); - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + connman->PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); } fGotBlockFromCache = true; } @@ -4276,7 +3568,7 @@ bool PeerManager::SendMessages(CNode* pto) bool ret = ReadBlockFromDisk(block, pBestIndex, consensusParams); assert(ret); CBlockHeaderAndShortTxIDs cmpctblock(block, state.fWantsCmpctWitness); - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + connman->PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); } state.pindexBestHeaderSent = pBestIndex; } else if (state.fPreferHeaders) { @@ -4289,7 +3581,7 @@ bool PeerManager::SendMessages(CNode* pto) LogPrint(BCLog::NET, "%s: sending header %s to peer=%d\n", __func__, vHeaders.front().GetHash().ToString(), pto->GetId()); } - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::HEADERS, vHeaders)); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::HEADERS, vHeaders)); state.pindexBestHeaderSent = pBestIndex; } else fRevertToInv = true; @@ -4306,14 +3598,14 @@ bool PeerManager::SendMessages(CNode* pto) // Warn if we're announcing a block that is not on the main chain. // This should be very rare and could be optimized out. // Just log for now. - if (::ChainActive()[pindex->nHeight] != pindex) { + if (chainActive[pindex->nHeight] != pindex) { LogPrint(BCLog::NET, "Announcing block %s not on main chain (tip=%s)\n", - hashToAnnounce.ToString(), ::ChainActive().Tip()->GetBlockHash().ToString()); + hashToAnnounce.ToString(), chainActive.Tip()->GetBlockHash().ToString()); } // If the peer's chain has this block, don't inv it back. if (!PeerHasHeader(&state, pindex)) { - pto->vInventoryBlockToSend.push_back(hashToAnnounce); + pto->PushInventory(CInv(MSG_BLOCK, hashToAnnounce)); LogPrint(BCLog::NET, "%s: sending inv peer=%d hash=%s\n", __func__, pto->GetId(), hashToAnnounce.ToString()); } @@ -4334,157 +3626,135 @@ bool PeerManager::SendMessages(CNode* pto) for (const uint256& hash : pto->vInventoryBlockToSend) { vInv.push_back(CInv(MSG_BLOCK, hash)); if (vInv.size() == MAX_INV_SZ) { - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); vInv.clear(); } } pto->vInventoryBlockToSend.clear(); - if (pto->m_tx_relay != nullptr) { - LOCK(pto->m_tx_relay->cs_tx_inventory); - // Check whether periodic sends should happen - bool fSendTrickle = pto->HasPermission(PF_NOBAN); - if (pto->m_tx_relay->nNextInvSend < current_time) { - fSendTrickle = true; - if (pto->IsInboundConn()) { - pto->m_tx_relay->nNextInvSend = std::chrono::microseconds{m_connman.PoissonNextSendInbound(count_microseconds(current_time), INVENTORY_BROADCAST_INTERVAL)}; - } else { - // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, std::chrono::seconds{INVENTORY_BROADCAST_INTERVAL >> 1}); - } + // Check whether periodic sends should happen + bool fSendTrickle = pto->fWhitelisted; + if (pto->nNextInvSend < nNow) { + fSendTrickle = true; + if (pto->fInbound) { + pto->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); + } else { + // Use half the delay for outbound peers, as there is less privacy concern for them. + pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); } + } - // Time to send but the peer has requested we not relay transactions. - if (fSendTrickle) { - LOCK(pto->m_tx_relay->cs_filter); - if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear(); - } + // Time to send but the peer has requested we not relay transactions. + if (fSendTrickle) { + LOCK(pto->cs_filter); + if (!pto->fRelayTxes) pto->setInventoryTxToSend.clear(); + } - // Respond to BIP35 mempool requests - if (fSendTrickle && pto->m_tx_relay->fSendMempool) { - auto vtxinfo = m_mempool.infoAll(); - pto->m_tx_relay->fSendMempool = false; - CFeeRate filterrate; - { - LOCK(pto->m_tx_relay->cs_feeFilter); - filterrate = CFeeRate(pto->m_tx_relay->minFeeFilter); - } + // Respond to BIP35 mempool requests + if (fSendTrickle && pto->fSendMempool) { + auto vtxinfo = mempool.infoAll(); + pto->fSendMempool = false; + CAmount filterrate = 0; + { + LOCK(pto->cs_feeFilter); + filterrate = pto->minFeeFilter; + } - LOCK(pto->m_tx_relay->cs_filter); + LOCK(pto->cs_filter); - for (const auto& txinfo : vtxinfo) { - const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash(); - CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash); - pto->m_tx_relay->setInventoryTxToSend.erase(hash); - // Don't send transactions that peers will not put into their mempool - if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) { + for (const auto& txinfo : vtxinfo) { + const uint256& hash = txinfo.tx->GetHash(); + CInv inv(MSG_TX, hash); + pto->setInventoryTxToSend.erase(hash); + if (filterrate) { + if (txinfo.feeRate.GetFeePerK() < filterrate) continue; - } - if (pto->m_tx_relay->pfilter) { - if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; - } - pto->m_tx_relay->filterInventoryKnown.insert(hash); - // Responses to MEMPOOL requests bypass the m_recently_announced_invs filter. - vInv.push_back(inv); - if (vInv.size() == MAX_INV_SZ) { - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); - vInv.clear(); - } } - pto->m_tx_relay->m_last_mempool_req = GetTime(); + if (pto->pfilter) { + if (!pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + } + pto->filterInventoryKnown.insert(hash); + vInv.push_back(inv); + if (vInv.size() == MAX_INV_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + vInv.clear(); + } } + pto->timeLastMempoolReq = GetTime(); + } - // Determine transactions to relay - if (fSendTrickle) { - // Produce a vector with all candidates for sending - std::vector::iterator> vInvTx; - vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size()); - for (std::set::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) { - vInvTx.push_back(it); + // Determine transactions to relay + if (fSendTrickle) { + // Produce a vector with all candidates for sending + std::vector::iterator> vInvTx; + vInvTx.reserve(pto->setInventoryTxToSend.size()); + for (std::set::iterator it = pto->setInventoryTxToSend.begin(); it != pto->setInventoryTxToSend.end(); it++) { + vInvTx.push_back(it); + } + CAmount filterrate = 0; + { + LOCK(pto->cs_feeFilter); + filterrate = pto->minFeeFilter; + } + // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. + // A heap is used so that not all items need sorting if only a few are being sent. + CompareInvMempoolOrder compareInvMempoolOrder(&mempool); + std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); + // No reason to drain out at many times the network's capacity, + // especially since we have many peers and some will draw much shorter delays. + unsigned int nRelayedTransactions = 0; + LOCK(pto->cs_filter); + while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { + // Fetch the top element from the heap + std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); + std::set::iterator it = vInvTx.back(); + vInvTx.pop_back(); + uint256 hash = *it; + // Remove it from the to-be-sent set + pto->setInventoryTxToSend.erase(it); + // Check if not in the filter already + if (pto->filterInventoryKnown.contains(hash)) { + continue; } - CFeeRate filterrate; - { - LOCK(pto->m_tx_relay->cs_feeFilter); - filterrate = CFeeRate(pto->m_tx_relay->minFeeFilter); + // Not in the mempool anymore? don't bother sending it. + auto txinfo = mempool.info(hash); + if (!txinfo.tx) { + continue; } - // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. - // A heap is used so that not all items need sorting if only a few are being sent. - CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay); - std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); - // No reason to drain out at many times the network's capacity, - // especially since we have many peers and some will draw much shorter delays. - unsigned int nRelayedTransactions = 0; - LOCK(pto->m_tx_relay->cs_filter); - while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { - // Fetch the top element from the heap - std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); - std::set::iterator it = vInvTx.back(); - vInvTx.pop_back(); - uint256 hash = *it; - CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash); - // Remove it from the to-be-sent set - pto->m_tx_relay->setInventoryTxToSend.erase(it); - // Check if not in the filter already - if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) { - continue; - } - // Not in the mempool anymore? don't bother sending it. - auto txinfo = m_mempool.info(ToGenTxid(inv)); - if (!txinfo.tx) { - continue; - } - auto txid = txinfo.tx->GetHash(); - auto wtxid = txinfo.tx->GetWitnessHash(); - // Peer told you to not send transactions at that feerate? Don't bother sending it. - if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) { - continue; - } - if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; - // Send - State(pto->GetId())->m_recently_announced_invs.insert(hash); - vInv.push_back(inv); - nRelayedTransactions++; + if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) { + continue; + } + if (pto->pfilter && !pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + // Send + vInv.push_back(CInv(MSG_TX, hash)); + nRelayedTransactions++; + { + // Expire old relay messages + while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow) { - // Expire old relay messages - while (!vRelayExpiration.empty() && vRelayExpiration.front().first < count_microseconds(current_time)) - { - mapRelay.erase(vRelayExpiration.front().second); - vRelayExpiration.pop_front(); - } - - auto ret = mapRelay.emplace(txid, std::move(txinfo.tx)); - if (ret.second) { - vRelayExpiration.emplace_back(count_microseconds(current_time + std::chrono::microseconds{RELAY_TX_CACHE_TIME}), ret.first); - } - // Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid - auto ret2 = mapRelay.emplace(wtxid, ret.first->second); - if (ret2.second) { - vRelayExpiration.emplace_back(count_microseconds(current_time + std::chrono::microseconds{RELAY_TX_CACHE_TIME}), ret2.first); - } + mapRelay.erase(vRelayExpiration.front().second); + vRelayExpiration.pop_front(); } - if (vInv.size() == MAX_INV_SZ) { - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); - vInv.clear(); - } - pto->m_tx_relay->filterInventoryKnown.insert(hash); - if (hash != txid) { - // Insert txid into filterInventoryKnown, even for - // wtxidrelay peers. This prevents re-adding of - // unconfirmed parents to the recently_announced - // filter, when a child tx is requested. See - // ProcessGetData(). - pto->m_tx_relay->filterInventoryKnown.insert(txid); + + auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx))); + if (ret.second) { + vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first)); } } + if (vInv.size() == MAX_INV_SZ) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + vInv.clear(); + } + pto->filterInventoryKnown.insert(hash); } } } if (!vInv.empty()) - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); // Detect whether we're stalling - current_time = GetTime(); - if (state.nStallingSince && state.nStallingSince < count_microseconds(current_time) - 1000000 * BLOCK_STALLING_TIMEOUT) { + nNow = GetTimeMicros(); + if (state.nStallingSince && state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) { // Stalling only triggers when the block download window cannot move. During normal steady state, // the download window should be much larger than the to-be-downloaded set of blocks, so disconnection // should only happen during initial block download. @@ -4500,7 +3770,7 @@ bool PeerManager::SendMessages(CNode* pto) if (state.vBlocksInFlight.size() > 0) { QueuedBlock &queuedBlock = state.vBlocksInFlight.front(); int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0); - if (count_microseconds(current_time) > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { + if (nNow > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->GetId()); pto->fDisconnect = true; return true; @@ -4509,19 +3779,19 @@ bool PeerManager::SendMessages(CNode* pto) // Check for headers sync timeouts if (state.fSyncStarted && state.nHeadersSyncTimeout < std::numeric_limits::max()) { // Detect whether this is a stalling initial-headers-sync peer - if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { - if (count_microseconds(current_time) > state.nHeadersSyncTimeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { - // Disconnect a peer (without the noban permission) if it is our only sync peer, + if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24*60*60) { + if (nNow > state.nHeadersSyncTimeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { + // Disconnect a (non-whitelisted) peer if it is our only sync peer, // and we have others we could be using instead. // Note: If all our peers are inbound, then we won't // disconnect our sync peer for stalling; we have bigger // problems if we can't get any outbound peers. - if (!pto->HasPermission(PF_NOBAN)) { + if (!pto->fWhitelisted) { LogPrintf("Timeout downloading headers from peer=%d, disconnecting\n", pto->GetId()); pto->fDisconnect = true; return true; } else { - LogPrintf("Timeout downloading headers from noban peer=%d, not disconnecting\n", pto->GetId()); + LogPrintf("Timeout downloading headers from whitelisted peer=%d, not disconnecting\n", pto->GetId()); // Reset the headers sync state so that we have a // chance to try downloading from a different peer. // Note: this will also result in at least one more @@ -4541,26 +3811,26 @@ bool PeerManager::SendMessages(CNode* pto) // Check that outbound peers have reasonable chains // GetTime() is used by this anti-DoS logic so we can test this using mocktime - ConsiderEviction(*pto, GetTime()); + ConsiderEviction(pto, GetTime()); // // Message: getdata (blocks) // std::vector vGetData; - if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !::ChainstateActive().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { std::vector vToDownload; NodeId staller = -1; FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, consensusParams); for (const CBlockIndex *pindex : vToDownload) { - uint32_t nFetchFlags = GetFetchFlags(*pto); + uint32_t nFetchFlags = GetFetchFlags(pto); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - MarkBlockAsInFlight(m_mempool, pto->GetId(), pindex->GetBlockHash(), pindex); + MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex); LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->nHeight, pto->GetId()); } if (state.nBlocksInFlight == 0 && staller != -1) { if (State(staller)->nStallingSince == 0) { - State(staller)->nStallingSince = count_microseconds(current_time); + State(staller)->nStallingSince = nNow; LogPrint(BCLog::NET, "Stall started peer=%d\n", staller); } } @@ -4569,71 +3839,55 @@ bool PeerManager::SendMessages(CNode* pto) // // Message: getdata (non-blocks) // - std::vector> expired; - auto requestable = m_txrequest.GetRequestable(pto->GetId(), current_time, &expired); - for (const auto& entry : expired) { - LogPrint(BCLog::NET, "timeout of inflight %s %s from peer=%d\n", entry.second.IsWtxid() ? "wtx" : "tx", - entry.second.GetHash().ToString(), entry.first); - } - for (const GenTxid& gtxid : requestable) { - if (!AlreadyHaveTx(gtxid, m_mempool)) { - LogPrint(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx", - gtxid.GetHash().ToString(), pto->GetId()); - vGetData.emplace_back(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), gtxid.GetHash()); - if (vGetData.size() >= MAX_GETDATA_SZ) { - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); + while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) + { + const CInv& inv = (*pto->mapAskFor.begin()).second; + if (!AlreadyHave(inv)) + { + LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId()); + vGetData.push_back(inv); + if (vGetData.size() >= 1000) + { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); vGetData.clear(); } - m_txrequest.RequestedTx(pto->GetId(), gtxid.GetHash(), current_time + GETDATA_TX_INTERVAL); } else { - // We have already seen this transaction, no need to download. This is just a belt-and-suspenders, as - // this should already be called whenever a transaction becomes AlreadyHaveTx(). - m_txrequest.ForgetTxHash(gtxid.GetHash()); + //If we're not going to ask, don't expect a response. + pto->setAskFor.erase(inv.hash); } + pto->mapAskFor.erase(pto->mapAskFor.begin()); } - - if (!vGetData.empty()) - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); + connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); // // Message: feefilter // - if (pto->m_tx_relay != nullptr && pto->GetCommonVersion() >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && - !pto->HasPermission(PF_FORCERELAY) // peers with the forcerelay permission should not filter txs to us - ) { - CAmount currentFilter = m_mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); - static FeeFilterRounder g_filter_rounder{CFeeRate{DEFAULT_MIN_RELAY_TX_FEE}}; - if (m_chainman.ActiveChainstate().IsInitialBlockDownload()) { - // Received tx-inv messages are discarded when the active - // chainstate is in IBD, so tell the peer to not send them. - currentFilter = MAX_MONEY; - } else { - static const CAmount MAX_FILTER{g_filter_rounder.round(MAX_MONEY)}; - if (pto->m_tx_relay->lastSentFeeFilter == MAX_FILTER) { - // Send the current filter if we sent MAX_FILTER previously - // and made it out of IBD. - pto->m_tx_relay->nextSendTimeFeeFilter = count_microseconds(current_time) - 1; - } - } - if (count_microseconds(current_time) > pto->m_tx_relay->nextSendTimeFeeFilter) { - CAmount filterToSend = g_filter_rounder.round(currentFilter); + // We don't want white listed peers to filter txs to us if we have -whitelistforcerelay + if (pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && + !(pto->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) { + CAmount currentFilter = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); + int64_t timeNow = GetTimeMicros(); + if (timeNow > pto->nextSendTimeFeeFilter) { + static CFeeRate default_feerate(DEFAULT_MIN_RELAY_TX_FEE); + static FeeFilterRounder filterRounder(default_feerate); + CAmount filterToSend = filterRounder.round(currentFilter); // We always have a fee filter of at least minRelayTxFee filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK()); - if (filterToSend != pto->m_tx_relay->lastSentFeeFilter) { - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend)); - pto->m_tx_relay->lastSentFeeFilter = filterToSend; + if (filterToSend != pto->lastSentFeeFilter) { + connman->PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend)); + pto->lastSentFeeFilter = filterToSend; } - pto->m_tx_relay->nextSendTimeFeeFilter = PoissonNextSend(count_microseconds(current_time), AVG_FEEFILTER_BROADCAST_INTERVAL); + pto->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (count_microseconds(current_time) + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->m_tx_relay->nextSendTimeFeeFilter && - (currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) { - pto->m_tx_relay->nextSendTimeFeeFilter = count_microseconds(current_time) + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; + else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->nextSendTimeFeeFilter && + (currentFilter < 3 * pto->lastSentFeeFilter / 4 || currentFilter > 4 * pto->lastSentFeeFilter / 3)) { + pto->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; } } - } // release cs_main + } return true; } @@ -4645,7 +3899,5 @@ class CNetProcessingCleanup // orphan transactions mapOrphanTransactions.clear(); mapOrphanTransactionsByPrev.clear(); - g_orphans_by_wtxid.clear(); } -}; -static CNetProcessingCleanup instance_of_cnetprocessingcleanup; +} instance_of_cnetprocessingcleanup; diff --git a/src/validation.cpp b/src/validation.cpp index 5d21593b5..c088a13d0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -8,23 +8,18 @@ #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 @@ -32,129 +27,247 @@ #include #include