Merge bitcoin/bitcoin#29575: net_processing: make any misbehavior trigger immediate discouragement

6eecba475e net_processing: make MaybePunishNodeFor{Block,Tx} return void (Pieter Wuille)
ae60d485da net_processing: remove Misbehavior score and increments (Pieter Wuille)
6457c31197 net_processing: make all Misbehaving increments = 100 (Pieter Wuille)
5120ab1478 net_processing: drop 8 headers threshold for incoming BIP130 (Pieter Wuille)
944c54290d net_processing: drop Misbehavior for unconnecting headers (Pieter Wuille)
9f66ac7cf1 net_processing: do not treat non-connecting headers as response (Pieter Wuille)

Pull request description:

  So far, discouragement of peers triggers when their misbehavior score exceeds 100 points. Most types of misbehavior increment the score by 100, triggering immediate discouragement, but some types do not. This PR makes all increments equal to either 100 (meaning any misbehavior will immediately cause disconnection and discouragement) or 0 (making the behavior effectively unconditionally allowed), and then removes the logic for score accumulation.

  This simplifies the code a bit, but also makes protocol expectations clearer: if a peer misbehaves, they get disconnected. There is no good reason why certain types of protocol violations should be permitted 4 times (howmuch=20) or 9 times (howmuch=10), while many others are never allowed. Furthermore, the distinction between these looks arbitrary.

  The specific types of misbehavior that are changed to 100 are:
  * Sending us a `block` which does not connect to our header tree (which necessarily must have been unsollicited). [used to be score 10]
  * Sending us a `headers` with a non-continuous headers sequence. [used to be score 20]
  * Sending us more than 1000 addresses in a single `addr` or `addrv2` message [used to be score 20]
  * Sending us more than 50000 invs in a single `inv` message [used to be score 20]
  * Sending us more than 2000 headers in a single `headers` message [used to be score 20]

  The specific types of misbehavior that are changed to 0 are:
  * Sending us 10 (*) separate BIP130 headers announcements that do not connect to our block tree [used to be score 20]
  * Sending us more than 8 headers in a single `headers` message (which thus does not get treated as a BIP130 announcement) that does not connect to our block tree. [used to be score 10]

  I believe that none of these behaviors are unavoidable, except for the one marked (*) which can in theory happen still due to interaction between BIP130 and variations in system clocks (the max 2 hour in the future rule). This one has been removed entirely. In order to remove the impact of the bug it was designed to deal with, without relying on misbehavior, a separate improvement is included that makes `getheaders`-tracking more accurate.

  In another unrelated improvement, this also gets rid of the 8 header limit heuristic to determine whether an incoming non-connecting `headers` is a potential BIP130 announcement, as this rule is no longer needed to prevent spurious Misbehavior. Instead, any non-connecting `headers` is now treated as a potential announcement.

ACKs for top commit:
  sr-gi:
    ACK [6eecba4](6eecba475e)
  achow101:
    ACK 6eecba475e
  mzumsande:
    Code Review ACK 6eecba475e
  glozow:
    light code review / concept ACK 6eecba475e

Tree-SHA512: e11e8a652c4ec048d8961086110a3594feefbb821e13f45c14ef81016377be0db44b5311751ef635d6e026def1960aff33f644e78ece11cfb54f2b7daa96f946
This commit is contained in:
Ava Chow
2024-06-20 13:28:38 -04:00
10 changed files with 100 additions and 170 deletions

View File

@@ -34,7 +34,7 @@ class CSubNet;
// disk on shutdown and reloaded on startup. Banning can be used to // disk on shutdown and reloaded on startup. Banning can be used to
// prevent connections with spy nodes or other griefers. // prevent connections with spy nodes or other griefers.
// //
// 2. Discouragement. If a peer misbehaves enough (see Misbehaving() in // 2. Discouragement. If a peer misbehaves (see Misbehaving() in
// net_processing.cpp), we'll mark that address as discouraged. We still allow // net_processing.cpp), we'll mark that address as discouraged. We still allow
// incoming connections from them, but they're preferred for eviction when // incoming connections from them, but they're preferred for eviction when
// we receive new incoming connections. We never make outgoing connections to // we receive new incoming connections. We never make outgoing connections to

View File

@@ -131,8 +131,6 @@ static constexpr double BLOCK_DOWNLOAD_TIMEOUT_BASE = 1;
static constexpr double BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 0.5; static constexpr double BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 0.5;
/** Maximum number of headers to announce when relaying blocks with headers message.*/ /** Maximum number of headers to announce when relaying blocks with headers message.*/
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
/** Maximum number of unconnecting headers announcements before DoS score */
static const int MAX_NUM_UNCONNECTING_HEADERS_MSGS = 10;
/** Minimum blocks required to signal NODE_NETWORK_LIMITED */ /** Minimum blocks required to signal NODE_NETWORK_LIMITED */
static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288;
/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */ /** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */
@@ -226,8 +224,6 @@ struct Peer {
/** Protects misbehavior data members */ /** Protects misbehavior data members */
Mutex m_misbehavior_mutex; 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 NetPermissionFlags::NoBan permission). */ /** Whether this peer should be disconnected and marked as discouraged (unless it has NetPermissionFlags::NoBan permission). */
bool m_should_discourage GUARDED_BY(m_misbehavior_mutex){false}; bool m_should_discourage GUARDED_BY(m_misbehavior_mutex){false};
@@ -383,9 +379,6 @@ struct Peer {
/** Whether we've sent our peer a sendheaders message. **/ /** Whether we've sent our peer a sendheaders message. **/
std::atomic<bool> m_sent_sendheaders{false}; std::atomic<bool> m_sent_sendheaders{false};
/** Length of current-streak of unconnecting headers announcements */
int m_num_unconnecting_headers_msgs GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0};
/** When to potentially disconnect peer for stalling headers download */ /** When to potentially disconnect peer for stalling headers download */
std::chrono::microseconds m_headers_sync_timeout GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0us}; std::chrono::microseconds m_headers_sync_timeout GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0us};
@@ -527,7 +520,7 @@ public:
m_best_height = height; m_best_height = height;
m_best_block_time = time; m_best_block_time = time;
}; };
void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); }; void UnitTestMisbehaving(NodeId peer_id) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), ""); };
void ProcessMessage(CNode& pfrom, const std::string& msg_type, DataStream& vRecv, void ProcessMessage(CNode& pfrom, const std::string& msg_type, DataStream& vRecv,
const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex); EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex);
@@ -552,11 +545,9 @@ private:
* May return an empty shared_ptr if the Peer object can't be found. */ * May return an empty shared_ptr if the Peer object can't be found. */
PeerRef RemovePeer(NodeId id) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); PeerRef RemovePeer(NodeId id) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
/** /** Mark a peer as misbehaving, which will cause it to be disconnected and its
* Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node * address discouraged. */
* to be discouraged, meaning the peer might be disconnected and added to the discouragement filter. void Misbehaving(Peer& peer, const std::string& message);
*/
void Misbehaving(Peer& peer, int howmuch, const std::string& message);
/** /**
* Potentially mark a node discouraged based on the contents of a BlockValidationState object * Potentially mark a node discouraged based on the contents of a BlockValidationState object
@@ -565,19 +556,15 @@ private:
* punish peers differently depending on whether the data was provided in a compact * punish peers differently depending on whether the data was provided in a compact
* block message or not. If the compact block had a valid header, but contained invalid * block message or not. If the compact block had a valid header, but contained invalid
* txs, the peer should not be punished. See BIP 152. * txs, the peer should not be punished. See BIP 152.
*
* @return Returns true if the peer was punished (probably disconnected)
*/ */
bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, void MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state,
bool via_compact_block, const std::string& message = "") bool via_compact_block, const std::string& message = "")
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
/** /**
* Potentially disconnect and discourage a node based on the contents of a TxValidationState object * Potentially disconnect and discourage a node based on the contents of a TxValidationState object
*
* @return Returns true if the peer was punished (probably disconnected)
*/ */
bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state) void MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state)
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
/** Maybe disconnect a peer and discourage future connections from its address. /** Maybe disconnect a peer and discourage future connections from its address.
@@ -668,10 +655,10 @@ private:
bool CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer); bool CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer);
/** Calculate an anti-DoS work threshold for headers chains */ /** Calculate an anti-DoS work threshold for headers chains */
arith_uint256 GetAntiDoSWorkThreshold(); arith_uint256 GetAntiDoSWorkThreshold();
/** Deal with state tracking and headers sync for peers that send the /** Deal with state tracking and headers sync for peers that send
* occasional non-connecting header (this can happen due to BIP 130 headers * non-connecting headers (this can happen due to BIP 130 headers
* announcements for blocks interacting with the 2hr (MAX_FUTURE_BLOCK_TIME) rule). */ * announcements for blocks interacting with the 2hr (MAX_FUTURE_BLOCK_TIME) rule). */
void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); void HandleUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex);
/** Return true if the headers connect to each other, false otherwise */ /** Return true if the headers connect to each other, false otherwise */
bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const; bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const;
/** Try to continue a low-work headers sync that has already begun. /** Try to continue a low-work headers sync that has already begun.
@@ -1718,7 +1705,6 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler)
void PeerManagerImpl::FinalizeNode(const CNode& node) void PeerManagerImpl::FinalizeNode(const CNode& node)
{ {
NodeId nodeid = node.GetId(); NodeId nodeid = node.GetId();
int misbehavior{0};
{ {
LOCK(cs_main); LOCK(cs_main);
{ {
@@ -1729,7 +1715,6 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
// destructed. // destructed.
PeerRef peer = RemovePeer(nodeid); PeerRef peer = RemovePeer(nodeid);
assert(peer != nullptr); assert(peer != nullptr);
misbehavior = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score);
m_wtxid_relay_peers -= peer->m_wtxid_relay; m_wtxid_relay_peers -= peer->m_wtxid_relay;
assert(m_wtxid_relay_peers >= 0); assert(m_wtxid_relay_peers >= 0);
} }
@@ -1772,7 +1757,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
assert(m_orphanage.Size() == 0); assert(m_orphanage.Size() == 0);
} }
} // cs_main } // cs_main
if (node.fSuccessfullyConnected && misbehavior == 0 && if (node.fSuccessfullyConnected &&
!node.IsBlockOnlyConn() && !node.IsInboundConn()) { !node.IsBlockOnlyConn() && !node.IsInboundConn()) {
// Only change visible addrman state for full outbound peers. We don't // Only change visible addrman state for full outbound peers. We don't
// call Connected() for feeler connections since they don't have // call Connected() for feeler connections since they don't have
@@ -1893,28 +1878,16 @@ void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx)
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % m_opts.max_extra_txs; vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % m_opts.max_extra_txs;
} }
void PeerManagerImpl::Misbehaving(Peer& peer, int howmuch, const std::string& message) void PeerManagerImpl::Misbehaving(Peer& peer, const std::string& message)
{ {
assert(howmuch > 0);
LOCK(peer.m_misbehavior_mutex); LOCK(peer.m_misbehavior_mutex);
const int score_before{peer.m_misbehavior_score};
peer.m_misbehavior_score += howmuch;
const int score_now{peer.m_misbehavior_score};
const std::string message_prefixed = message.empty() ? "" : (": " + message); const std::string message_prefixed = message.empty() ? "" : (": " + message);
std::string warning; peer.m_should_discourage = true;
LogPrint(BCLog::NET, "Misbehaving: peer=%d%s\n", peer.m_id, message_prefixed);
if (score_now >= DISCOURAGEMENT_THRESHOLD && score_before < DISCOURAGEMENT_THRESHOLD) {
warning = " DISCOURAGE THRESHOLD EXCEEDED";
peer.m_should_discourage = true;
}
LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s%s\n",
peer.m_id, score_before, score_now, warning, message_prefixed);
} }
bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, void PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state,
bool via_compact_block, const std::string& message) bool via_compact_block, const std::string& message)
{ {
PeerRef peer{GetPeerRef(nodeid)}; PeerRef peer{GetPeerRef(nodeid)};
@@ -1929,8 +1902,8 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
case BlockValidationResult::BLOCK_CONSENSUS: case BlockValidationResult::BLOCK_CONSENSUS:
case BlockValidationResult::BLOCK_MUTATED: case BlockValidationResult::BLOCK_MUTATED:
if (!via_compact_block) { if (!via_compact_block) {
if (peer) Misbehaving(*peer, 100, message); if (peer) Misbehaving(*peer, message);
return true; return;
} }
break; break;
case BlockValidationResult::BLOCK_CACHED_INVALID: case BlockValidationResult::BLOCK_CACHED_INVALID:
@@ -1944,21 +1917,20 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
// Discourage outbound (but not inbound) peers if on an invalid chain. // Discourage outbound (but not inbound) peers if on an invalid chain.
// Exempt HB compact block peers. Manual connections are always protected from discouragement. // Exempt HB compact block peers. Manual connections are always protected from discouragement.
if (!via_compact_block && !node_state->m_is_inbound) { if (!via_compact_block && !node_state->m_is_inbound) {
if (peer) Misbehaving(*peer, 100, message); if (peer) Misbehaving(*peer, message);
return true; return;
} }
break; break;
} }
case BlockValidationResult::BLOCK_INVALID_HEADER: case BlockValidationResult::BLOCK_INVALID_HEADER:
case BlockValidationResult::BLOCK_CHECKPOINT: case BlockValidationResult::BLOCK_CHECKPOINT:
case BlockValidationResult::BLOCK_INVALID_PREV: case BlockValidationResult::BLOCK_INVALID_PREV:
if (peer) Misbehaving(*peer, 100, message); if (peer) Misbehaving(*peer, message);
return true; return;
// Conflicting (but not necessarily invalid) data or different policy: // Conflicting (but not necessarily invalid) data or different policy:
case BlockValidationResult::BLOCK_MISSING_PREV: case BlockValidationResult::BLOCK_MISSING_PREV:
// TODO: Handle this much more gracefully (10 DoS points is super arbitrary) if (peer) Misbehaving(*peer, message);
if (peer) Misbehaving(*peer, 10, message); return;
return true;
case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE:
case BlockValidationResult::BLOCK_TIME_FUTURE: case BlockValidationResult::BLOCK_TIME_FUTURE:
break; break;
@@ -1966,10 +1938,9 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
if (message != "") { if (message != "") {
LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message);
} }
return false;
} }
bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state) void PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state)
{ {
PeerRef peer{GetPeerRef(nodeid)}; PeerRef peer{GetPeerRef(nodeid)};
switch (state.GetResult()) { switch (state.GetResult()) {
@@ -1977,8 +1948,8 @@ bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat
break; break;
// The node is providing invalid data: // The node is providing invalid data:
case TxValidationResult::TX_CONSENSUS: case TxValidationResult::TX_CONSENSUS:
if (peer) Misbehaving(*peer, 100, ""); if (peer) Misbehaving(*peer, "");
return true; return;
// Conflicting (but not necessarily invalid) data or different policy: // Conflicting (but not necessarily invalid) data or different policy:
case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE: case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE:
case TxValidationResult::TX_INPUTS_NOT_STANDARD: case TxValidationResult::TX_INPUTS_NOT_STANDARD:
@@ -1994,7 +1965,6 @@ bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat
case TxValidationResult::TX_UNKNOWN: case TxValidationResult::TX_UNKNOWN:
break; break;
} }
return false;
} }
bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex) bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
@@ -2679,7 +2649,7 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlo
BlockTransactions resp(req); BlockTransactions resp(req);
for (size_t i = 0; i < req.indexes.size(); i++) { for (size_t i = 0; i < req.indexes.size(); i++) {
if (req.indexes[i] >= block.vtx.size()) { if (req.indexes[i] >= block.vtx.size()) {
Misbehaving(peer, 100, "getblocktxn with out-of-bounds tx indices"); Misbehaving(peer, "getblocktxn with out-of-bounds tx indices");
return; return;
} }
resp.txn[i] = block.vtx[req.indexes[i]]; resp.txn[i] = block.vtx[req.indexes[i]];
@@ -2692,13 +2662,13 @@ bool PeerManagerImpl::CheckHeadersPoW(const std::vector<CBlockHeader>& headers,
{ {
// Do these headers have proof-of-work matching what's claimed? // Do these headers have proof-of-work matching what's claimed?
if (!HasValidProofOfWork(headers, consensusParams)) { if (!HasValidProofOfWork(headers, consensusParams)) {
Misbehaving(peer, 100, "header with invalid proof of work"); Misbehaving(peer, "header with invalid proof of work");
return false; return false;
} }
// Are these headers connected to each other? // Are these headers connected to each other?
if (!CheckHeadersAreContinuous(headers)) { if (!CheckHeadersAreContinuous(headers)) {
Misbehaving(peer, 20, "non-continuous headers sequence"); Misbehaving(peer, "non-continuous headers sequence");
return false; return false;
} }
return true; return true;
@@ -2722,37 +2692,24 @@ arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold()
* announcement. * announcement.
* *
* We'll send a getheaders message in response to try to connect the chain. * We'll send a getheaders message in response to try to connect the chain.
*
* The peer can send up to MAX_NUM_UNCONNECTING_HEADERS_MSGS in a row that
* don't connect before given DoS points.
*
* Once a headers message is received that is valid and does connect,
* m_num_unconnecting_headers_msgs gets reset back to 0.
*/ */
void PeerManagerImpl::HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, void PeerManagerImpl::HandleUnconnectingHeaders(CNode& pfrom, Peer& peer,
const std::vector<CBlockHeader>& headers) const std::vector<CBlockHeader>& headers)
{ {
peer.m_num_unconnecting_headers_msgs++;
// Try to fill in the missing headers. // Try to fill in the missing headers.
const CBlockIndex* best_header{WITH_LOCK(cs_main, return m_chainman.m_best_header)}; const CBlockIndex* best_header{WITH_LOCK(cs_main, return m_chainman.m_best_header)};
if (MaybeSendGetHeaders(pfrom, GetLocator(best_header), peer)) { if (MaybeSendGetHeaders(pfrom, GetLocator(best_header), peer)) {
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, m_num_unconnecting_headers_msgs=%d)\n", LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d)\n",
headers[0].GetHash().ToString(), headers[0].GetHash().ToString(),
headers[0].hashPrevBlock.ToString(), headers[0].hashPrevBlock.ToString(),
best_header->nHeight, best_header->nHeight,
pfrom.GetId(), peer.m_num_unconnecting_headers_msgs); pfrom.GetId());
} }
// Set hashLastUnknownBlock for this peer, so that if we // Set hashLastUnknownBlock for this peer, so that if we
// eventually get the headers - even from a different peer - // eventually get the headers - even from a different peer -
// we can use this peer to download. // we can use this peer to download.
WITH_LOCK(cs_main, UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash())); WITH_LOCK(cs_main, UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()));
// The peer may just be broken, so periodically assign DoS points if this
// condition persists.
if (peer.m_num_unconnecting_headers_msgs % MAX_NUM_UNCONNECTING_HEADERS_MSGS == 0) {
Misbehaving(peer, 20, strprintf("%d non-connecting headers", peer.m_num_unconnecting_headers_msgs));
}
} }
bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const
@@ -2771,25 +2728,21 @@ bool PeerManagerImpl::IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfro
{ {
if (peer.m_headers_sync) { if (peer.m_headers_sync) {
auto result = peer.m_headers_sync->ProcessNextHeaders(headers, headers.size() == MAX_HEADERS_RESULTS); auto result = peer.m_headers_sync->ProcessNextHeaders(headers, headers.size() == MAX_HEADERS_RESULTS);
// If it is a valid continuation, we should treat the existing getheaders request as responded to.
if (result.success) peer.m_last_getheaders_timestamp = {};
if (result.request_more) { if (result.request_more) {
auto locator = peer.m_headers_sync->NextHeadersRequestLocator(); auto locator = peer.m_headers_sync->NextHeadersRequestLocator();
// If we were instructed to ask for a locator, it should not be empty. // If we were instructed to ask for a locator, it should not be empty.
Assume(!locator.vHave.empty()); Assume(!locator.vHave.empty());
// We can only be instructed to request more if processing was successful.
Assume(result.success);
if (!locator.vHave.empty()) { if (!locator.vHave.empty()) {
// It should be impossible for the getheaders request to fail, // It should be impossible for the getheaders request to fail,
// because we should have cleared the last getheaders timestamp // because we just cleared the last getheaders timestamp.
// when processing the headers that triggered this call. But
// it may be possible to bypass this via compactblock
// processing, so check the result before logging just to be
// safe.
bool sent_getheaders = MaybeSendGetHeaders(pfrom, locator, peer); bool sent_getheaders = MaybeSendGetHeaders(pfrom, locator, peer);
if (sent_getheaders) { Assume(sent_getheaders);
LogPrint(BCLog::NET, "more getheaders (from %s) to peer=%d\n", LogPrint(BCLog::NET, "more getheaders (from %s) to peer=%d\n",
locator.vHave.front().ToString(), pfrom.GetId()); locator.vHave.front().ToString(), pfrom.GetId());
} else {
LogPrint(BCLog::NET, "error sending next getheaders (from %s) to continue sync with peer=%d\n",
locator.vHave.front().ToString(), pfrom.GetId());
}
} }
} }
@@ -2998,11 +2951,6 @@ void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, c
void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom, Peer& peer, void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom, Peer& peer,
const CBlockIndex& last_header, bool received_new_header, bool may_have_more_headers) const CBlockIndex& last_header, bool received_new_header, bool may_have_more_headers)
{ {
if (peer.m_num_unconnecting_headers_msgs > 0) {
LogPrint(BCLog::NET, "peer=%d: resetting m_num_unconnecting_headers_msgs (%d -> 0)\n", pfrom.GetId(), peer.m_num_unconnecting_headers_msgs);
}
peer.m_num_unconnecting_headers_msgs = 0;
LOCK(cs_main); LOCK(cs_main);
CNodeState *nodestate = State(pfrom.GetId()); CNodeState *nodestate = State(pfrom.GetId());
@@ -3068,6 +3016,9 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
LOCK(m_headers_presync_mutex); LOCK(m_headers_presync_mutex);
m_headers_presync_stats.erase(pfrom.GetId()); m_headers_presync_stats.erase(pfrom.GetId());
} }
// A headers message with no headers cannot be an announcement, so assume
// it is a response to our last getheaders request, if there is one.
peer.m_last_getheaders_timestamp = {};
return; return;
} }
@@ -3121,17 +3072,18 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
bool headers_connect_blockindex{chain_start_header != nullptr}; bool headers_connect_blockindex{chain_start_header != nullptr};
if (!headers_connect_blockindex) { if (!headers_connect_blockindex) {
if (nCount <= MAX_BLOCKS_TO_ANNOUNCE) { // This could be a BIP 130 block announcement, use
// If this looks like it could be a BIP 130 block announcement, use // special logic for handling headers that don't connect, as this
// special logic for handling headers that don't connect, as this // could be benign.
// could be benign. HandleUnconnectingHeaders(pfrom, peer, headers);
HandleFewUnconnectingHeaders(pfrom, peer, headers);
} else {
Misbehaving(peer, 10, "invalid header received");
}
return; return;
} }
// If headers connect, assume that this is in response to any outstanding getheaders
// request we may have sent, and clear out the time of our last request. Non-connecting
// headers cannot be a response to a getheaders request.
peer.m_last_getheaders_timestamp = {};
// If the headers we received are already in memory and an ancestor of // If the headers we received are already in memory and an ancestor of
// m_best_header or our tip, skip anti-DoS checks. These headers will not // m_best_header or our tip, skip anti-DoS checks. These headers will not
// use any more memory (and we are not leaking information that could be // use any more memory (and we are not leaking information that could be
@@ -3649,7 +3601,7 @@ void PeerManagerImpl::ProcessCompactBlockTxns(CNode& pfrom, Peer& peer, const Bl
ReadStatus status = partialBlock.FillBlock(*pblock, block_transactions.txn); ReadStatus status = partialBlock.FillBlock(*pblock, block_transactions.txn);
if (status == READ_STATUS_INVALID) { if (status == READ_STATUS_INVALID) {
RemoveBlockRequest(block_transactions.blockhash, pfrom.GetId()); // Reset in-flight state in case Misbehaving does not result in a disconnect RemoveBlockRequest(block_transactions.blockhash, pfrom.GetId()); // Reset in-flight state in case Misbehaving does not result in a disconnect
Misbehaving(peer, 100, "invalid compact block/non-matching block transactions"); Misbehaving(peer, "invalid compact block/non-matching block transactions");
return; return;
} else if (status == READ_STATUS_FAILED) { } else if (status == READ_STATUS_FAILED) {
if (first_in_flight) { if (first_in_flight) {
@@ -4133,7 +4085,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (vAddr.size() > MAX_ADDR_TO_SEND) if (vAddr.size() > MAX_ADDR_TO_SEND)
{ {
Misbehaving(*peer, 20, strprintf("%s message size = %u", msg_type, vAddr.size())); Misbehaving(*peer, strprintf("%s message size = %u", msg_type, vAddr.size()));
return; return;
} }
@@ -4215,7 +4167,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
vRecv >> vInv; vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) if (vInv.size() > MAX_INV_SZ)
{ {
Misbehaving(*peer, 20, strprintf("inv message size = %u", vInv.size())); Misbehaving(*peer, strprintf("inv message size = %u", vInv.size()));
return; return;
} }
@@ -4307,7 +4259,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
vRecv >> vInv; vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) if (vInv.size() > MAX_INV_SZ)
{ {
Misbehaving(*peer, 20, strprintf("getdata message size = %u", vInv.size())); Misbehaving(*peer, strprintf("getdata message size = %u", vInv.size()));
return; return;
} }
@@ -4847,7 +4799,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact); ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact);
if (status == READ_STATUS_INVALID) { if (status == READ_STATUS_INVALID) {
RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId()); // Reset in-flight state in case Misbehaving does not result in a disconnect RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId()); // Reset in-flight state in case Misbehaving does not result in a disconnect
Misbehaving(*peer, 100, "invalid compact block"); Misbehaving(*peer, "invalid compact block");
return; return;
} else if (status == READ_STATUS_FAILED) { } else if (status == READ_STATUS_FAILED) {
if (first_in_flight) { if (first_in_flight) {
@@ -4987,16 +4939,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return; return;
} }
// Assume that this is in response to any outstanding getheaders
// request we may have sent, and clear out the time of our last request
peer->m_last_getheaders_timestamp = {};
std::vector<CBlockHeader> headers; std::vector<CBlockHeader> headers;
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks. // Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
unsigned int nCount = ReadCompactSize(vRecv); unsigned int nCount = ReadCompactSize(vRecv);
if (nCount > MAX_HEADERS_RESULTS) { if (nCount > MAX_HEADERS_RESULTS) {
Misbehaving(*peer, 20, strprintf("headers message size = %u", nCount)); Misbehaving(*peer, strprintf("headers message size = %u", nCount));
return; return;
} }
headers.resize(nCount); headers.resize(nCount);
@@ -5043,7 +4991,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (prev_block && IsBlockMutated(/*block=*/*pblock, if (prev_block && IsBlockMutated(/*block=*/*pblock,
/*check_witness_root=*/DeploymentActiveAfter(prev_block, m_chainman, Consensus::DEPLOYMENT_SEGWIT))) { /*check_witness_root=*/DeploymentActiveAfter(prev_block, m_chainman, Consensus::DEPLOYMENT_SEGWIT))) {
LogDebug(BCLog::NET, "Received mutated block from peer=%d\n", peer->m_id); LogDebug(BCLog::NET, "Received mutated block from peer=%d\n", peer->m_id);
Misbehaving(*peer, 100, "mutated block"); Misbehaving(*peer, "mutated block");
WITH_LOCK(cs_main, RemoveBlockRequest(pblock->GetHash(), peer->m_id)); WITH_LOCK(cs_main, RemoveBlockRequest(pblock->GetHash(), peer->m_id));
return; return;
} }
@@ -5224,7 +5172,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (!filter.IsWithinSizeConstraints()) if (!filter.IsWithinSizeConstraints())
{ {
// There is no excuse for sending a too-large filter // There is no excuse for sending a too-large filter
Misbehaving(*peer, 100, "too-large bloom filter"); Misbehaving(*peer, "too-large bloom filter");
} else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) {
{ {
LOCK(tx_relay->m_bloom_filter_mutex); LOCK(tx_relay->m_bloom_filter_mutex);
@@ -5260,7 +5208,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
} }
} }
if (bad) { if (bad) {
Misbehaving(*peer, 100, "bad filteradd message"); Misbehaving(*peer, "bad filteradd message");
} }
return; return;
} }

View File

@@ -29,8 +29,6 @@ static const uint32_t DEFAULT_MAX_ORPHAN_TRANSACTIONS{100};
static const uint32_t DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN{100}; static const uint32_t DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN{100};
static const bool DEFAULT_PEERBLOOMFILTERS = false; static const bool DEFAULT_PEERBLOOMFILTERS = false;
static const bool DEFAULT_PEERBLOCKFILTERS = false; static const bool DEFAULT_PEERBLOCKFILTERS = false;
/** Threshold for marking a node to be discouraged, e.g. disconnected and added to the discouragement filter. */
static const int DISCOURAGEMENT_THRESHOLD{100};
/** Maximum number of outstanding CMPCTBLOCK requests for the same block. */ /** Maximum number of outstanding CMPCTBLOCK requests for the same block. */
static const unsigned int MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK = 3; static const unsigned int MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK = 3;
@@ -108,7 +106,7 @@ public:
virtual void SetBestBlock(int height, std::chrono::seconds time) = 0; virtual void SetBestBlock(int height, std::chrono::seconds time) = 0;
/* Public for unit testing. */ /* Public for unit testing. */
virtual void UnitTestMisbehaving(NodeId peer_id, int howmuch) = 0; virtual void UnitTestMisbehaving(NodeId peer_id) = 0;
/** /**
* Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound. * Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound.

View File

@@ -330,7 +330,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
peerLogic->InitializeNode(*nodes[0], NODE_NETWORK); peerLogic->InitializeNode(*nodes[0], NODE_NETWORK);
nodes[0]->fSuccessfullyConnected = true; nodes[0]->fSuccessfullyConnected = true;
connman->AddTestNode(*nodes[0]); connman->AddTestNode(*nodes[0]);
peerLogic->UnitTestMisbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged peerLogic->UnitTestMisbehaving(nodes[0]->GetId()); // Should be discouraged
BOOST_CHECK(peerLogic->SendMessages(nodes[0])); BOOST_CHECK(peerLogic->SendMessages(nodes[0]));
BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(banman->IsDiscouraged(addr[0]));
@@ -350,7 +350,6 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
peerLogic->InitializeNode(*nodes[1], NODE_NETWORK); peerLogic->InitializeNode(*nodes[1], NODE_NETWORK);
nodes[1]->fSuccessfullyConnected = true; nodes[1]->fSuccessfullyConnected = true;
connman->AddTestNode(*nodes[1]); connman->AddTestNode(*nodes[1]);
peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1);
BOOST_CHECK(peerLogic->SendMessages(nodes[1])); BOOST_CHECK(peerLogic->SendMessages(nodes[1]));
// [0] is still discouraged/disconnected. // [0] is still discouraged/disconnected.
BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(banman->IsDiscouraged(addr[0]));
@@ -358,7 +357,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
// [1] is not discouraged/disconnected yet. // [1] is not discouraged/disconnected yet.
BOOST_CHECK(!banman->IsDiscouraged(addr[1])); BOOST_CHECK(!banman->IsDiscouraged(addr[1]));
BOOST_CHECK(!nodes[1]->fDisconnect); BOOST_CHECK(!nodes[1]->fDisconnect);
peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), 1); // [1] reaches discouragement threshold peerLogic->UnitTestMisbehaving(nodes[1]->GetId());
BOOST_CHECK(peerLogic->SendMessages(nodes[1])); BOOST_CHECK(peerLogic->SendMessages(nodes[1]));
// Expect both [0] and [1] to be discouraged/disconnected now. // Expect both [0] and [1] to be discouraged/disconnected now.
BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(banman->IsDiscouraged(addr[0]));
@@ -381,7 +380,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
peerLogic->InitializeNode(*nodes[2], NODE_NETWORK); peerLogic->InitializeNode(*nodes[2], NODE_NETWORK);
nodes[2]->fSuccessfullyConnected = true; nodes[2]->fSuccessfullyConnected = true;
connman->AddTestNode(*nodes[2]); connman->AddTestNode(*nodes[2]);
peerLogic->UnitTestMisbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD); peerLogic->UnitTestMisbehaving(nodes[2]->GetId());
BOOST_CHECK(peerLogic->SendMessages(nodes[2])); BOOST_CHECK(peerLogic->SendMessages(nodes[2]));
BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(banman->IsDiscouraged(addr[0]));
BOOST_CHECK(banman->IsDiscouraged(addr[1])); BOOST_CHECK(banman->IsDiscouraged(addr[1]));
@@ -423,7 +422,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
peerLogic->InitializeNode(dummyNode, NODE_NETWORK); peerLogic->InitializeNode(dummyNode, NODE_NETWORK);
dummyNode.fSuccessfullyConnected = true; dummyNode.fSuccessfullyConnected = true;
peerLogic->UnitTestMisbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD); peerLogic->UnitTestMisbehaving(dummyNode.GetId());
BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); BOOST_CHECK(peerLogic->SendMessages(&dummyNode));
BOOST_CHECK(banman->IsDiscouraged(addr)); BOOST_CHECK(banman->IsDiscouraged(addr));

View File

@@ -142,7 +142,8 @@ class AddrTest(BitcoinTestFramework):
msg = self.setup_addr_msg(1010) msg = self.setup_addr_msg(1010)
with self.nodes[0].assert_debug_log(['addr message size = 1010']): with self.nodes[0].assert_debug_log(['addr message size = 1010']):
addr_source.send_and_ping(msg) addr_source.send_message(msg)
addr_source.wait_for_disconnect()
self.nodes[0].disconnect_p2ps() self.nodes[0].disconnect_p2ps()

View File

@@ -86,11 +86,6 @@ class AddrTest(BitcoinTestFramework):
addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = msg_addrv2() msg = msg_addrv2()
self.log.info('Send too-large addrv2 message')
msg.addrs = ADDRS * 101
with self.nodes[0].assert_debug_log(['addrv2 message size = 1010']):
addr_source.send_and_ping(msg)
self.log.info('Check that addrv2 message content is relayed and added to addrman') self.log.info('Check that addrv2 message content is relayed and added to addrman')
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver()) addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
msg.addrs = ADDRS msg.addrs = ADDRS
@@ -106,6 +101,13 @@ class AddrTest(BitcoinTestFramework):
assert addr_receiver.addrv2_received_and_checked assert addr_receiver.addrv2_received_and_checked
assert_equal(len(self.nodes[0].getnodeaddresses(count=0, network="i2p")), 0) assert_equal(len(self.nodes[0].getnodeaddresses(count=0, network="i2p")), 0)
self.log.info('Send too-large addrv2 message')
msg.addrs = ADDRS * 101
with self.nodes[0].assert_debug_log(['addrv2 message size = 1010']):
addr_source.send_message(msg)
addr_source.wait_for_disconnect()
if __name__ == '__main__': if __name__ == '__main__':
AddrTest().main() AddrTest().main()

View File

@@ -260,7 +260,9 @@ class InvalidMessagesTest(BitcoinTestFramework):
msg_type = msg.msgtype.decode('ascii') msg_type = msg.msgtype.decode('ascii')
self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size)) self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size))
with self.nodes[0].assert_debug_log(['Misbehaving', '{} message size = {}'.format(msg_type, size)]): with self.nodes[0].assert_debug_log(['Misbehaving', '{} message size = {}'.format(msg_type, size)]):
self.nodes[0].add_p2p_connection(P2PInterface()).send_and_ping(msg) conn = self.nodes[0].add_p2p_connection(P2PInterface())
conn.send_message(msg)
conn.wait_for_disconnect()
self.nodes[0].disconnect_p2ps() self.nodes[0].disconnect_p2ps()
def test_oversized_inv_msg(self): def test_oversized_inv_msg(self):
@@ -321,7 +323,8 @@ class InvalidMessagesTest(BitcoinTestFramework):
# delete arbitrary block header somewhere in the middle to break link # delete arbitrary block header somewhere in the middle to break link
del block_headers[random.randrange(1, len(block_headers)-1)] del block_headers[random.randrange(1, len(block_headers)-1)]
with self.nodes[0].assert_debug_log(expected_msgs=MISBEHAVING_NONCONTINUOUS_HEADERS_MSGS): with self.nodes[0].assert_debug_log(expected_msgs=MISBEHAVING_NONCONTINUOUS_HEADERS_MSGS):
peer.send_and_ping(msg_headers(block_headers)) peer.send_message(msg_headers(block_headers))
peer.wait_for_disconnect()
self.nodes[0].disconnect_p2ps() self.nodes[0].disconnect_p2ps()
def test_resource_exhaustion(self): def test_resource_exhaustion(self):

View File

@@ -104,11 +104,10 @@ class MutatedBlocksTest(BitcoinTestFramework):
block_missing_prev.hashPrevBlock = 123 block_missing_prev.hashPrevBlock = 123
block_missing_prev.solve() block_missing_prev.solve()
# Attacker gets a DoS score of 10, not immediately disconnected, so we do it 10 times to get to 100 # Check that non-connecting block causes disconnect
for _ in range(10): assert_equal(len(self.nodes[0].getpeerinfo()), 2)
assert_equal(len(self.nodes[0].getpeerinfo()), 2) with self.nodes[0].assert_debug_log(expected_msgs=["AcceptBlock FAILED (prev-blk-not-found)"]):
with self.nodes[0].assert_debug_log(expected_msgs=["AcceptBlock FAILED (prev-blk-not-found)"]): attacker.send_message(msg_block(block_missing_prev))
attacker.send_message(msg_block(block_missing_prev))
attacker.wait_for_disconnect(timeout=5) attacker.wait_for_disconnect(timeout=5)

View File

@@ -71,19 +71,13 @@ f. Announce 1 more header that builds on that fork.
Expect: no response. Expect: no response.
Part 5: Test handling of headers that don't connect. Part 5: Test handling of headers that don't connect.
a. Repeat 10 times: a. Repeat 100 times:
1. Announce a header that doesn't connect. 1. Announce a header that doesn't connect.
Expect: getheaders message Expect: getheaders message
2. Send headers chain. 2. Send headers chain.
Expect: getdata for the missing blocks, tip update. Expect: getdata for the missing blocks, tip update.
b. Then send 9 more headers that don't connect. b. Then send 99 more headers that don't connect.
Expect: getheaders message each time. Expect: getheaders message each time.
c. Announce a header that does connect.
Expect: no response.
d. Announce 49 headers that don't connect.
Expect: getheaders message each time.
e. Announce one more that doesn't connect.
Expect: disconnect.
""" """
from test_framework.blocktools import create_block, create_coinbase from test_framework.blocktools import create_block, create_coinbase
from test_framework.messages import CInv from test_framework.messages import CInv
@@ -526,7 +520,8 @@ class SendHeadersTest(BitcoinTestFramework):
# First we test that receipt of an unconnecting header doesn't prevent # First we test that receipt of an unconnecting header doesn't prevent
# chain sync. # chain sync.
expected_hash = tip expected_hash = tip
for i in range(10): NUM_HEADERS = 100
for i in range(NUM_HEADERS):
self.log.debug("Part 5.{}: starting...".format(i)) self.log.debug("Part 5.{}: starting...".format(i))
test_node.last_message.pop("getdata", None) test_node.last_message.pop("getdata", None)
blocks = [] blocks = []
@@ -550,41 +545,24 @@ class SendHeadersTest(BitcoinTestFramework):
blocks = [] blocks = []
# Now we test that if we repeatedly don't send connecting headers, we # Now we test that if we repeatedly don't send connecting headers, we
# don't go into an infinite loop trying to get them to connect. # don't go into an infinite loop trying to get them to connect.
MAX_NUM_UNCONNECTING_HEADERS_MSGS = 10 for _ in range(NUM_HEADERS + 1):
for _ in range(MAX_NUM_UNCONNECTING_HEADERS_MSGS + 1):
blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks.append(create_block(tip, create_coinbase(height), block_time))
blocks[-1].solve() blocks[-1].solve()
tip = blocks[-1].sha256 tip = blocks[-1].sha256
block_time += 1 block_time += 1
height += 1 height += 1
for i in range(1, MAX_NUM_UNCONNECTING_HEADERS_MSGS): for i in range(1, NUM_HEADERS):
# Send a header that doesn't connect, check that we get a getheaders. with p2p_lock:
test_node.last_message.pop("getheaders", None)
# Send an empty header as a failed response to the received getheaders
# (from the previous iteration). Otherwise, the new headers will be
# treated as a response instead of as an announcement.
test_node.send_header_for_blocks([])
# Send the actual unconnecting header, which should trigger a new getheaders.
test_node.send_header_for_blocks([blocks[i]]) test_node.send_header_for_blocks([blocks[i]])
test_node.wait_for_getheaders(block_hash=expected_hash) test_node.wait_for_getheaders(block_hash=expected_hash)
# Next header will connect, should re-set our count:
test_node.send_header_for_blocks([blocks[0]])
expected_hash = blocks[0].sha256
# Remove the first two entries (blocks[1] would connect):
blocks = blocks[2:]
# Now try to see how many unconnecting headers we can send
# before we get disconnected. Should be 5*MAX_NUM_UNCONNECTING_HEADERS_MSGS
for i in range(5 * MAX_NUM_UNCONNECTING_HEADERS_MSGS - 1):
# Send a header that doesn't connect, check that we get a getheaders.
test_node.send_header_for_blocks([blocks[i % len(blocks)]])
test_node.wait_for_getheaders(block_hash=expected_hash)
# Eventually this stops working.
test_node.send_header_for_blocks([blocks[-1]])
# Should get disconnected
test_node.wait_for_disconnect()
self.log.info("Part 5: success!")
# Finally, check that the inv node never received a getdata request, # Finally, check that the inv node never received a getdata request,
# throughout the test # throughout the test
assert "getdata" not in inv_node.last_message assert "getdata" not in inv_node.last_message

View File

@@ -170,9 +170,11 @@ class AcceptBlockTest(BitcoinTestFramework):
tip = next_block tip = next_block
# Now send the block at height 5 and check that it wasn't accepted (missing header) # Now send the block at height 5 and check that it wasn't accepted (missing header)
test_node.send_and_ping(msg_block(all_blocks[1])) test_node.send_message(msg_block(all_blocks[1]))
test_node.wait_for_disconnect()
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblock, all_blocks[1].hash) assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblock, all_blocks[1].hash)
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockheader, all_blocks[1].hash) assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockheader, all_blocks[1].hash)
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
# The block at height 5 should be accepted if we provide the missing header, though # The block at height 5 should be accepted if we provide the missing header, though
headers_message = msg_headers() headers_message = msg_headers()