Merge 10593 via relax_invblk_punishment

This commit is contained in:
Luke Dashjr 2025-03-05 03:27:08 +00:00
commit 79e3ebd76a
8 changed files with 55 additions and 19 deletions

View File

@ -950,6 +950,25 @@ public:
void CopyStats(CNodeStats& stats) EXCLUSIVE_LOCKS_REQUIRED(!m_subver_mutex, !m_addr_local_mutex, !cs_vSend, !cs_vRecv);
bool PunishInvalidBlocks() const
{
if (HasPermission(NetPermissionFlags::NoBan)) {
return false;
}
switch (m_conn_type) {
case ConnectionType::INBOUND:
case ConnectionType::MANUAL:
case ConnectionType::FEELER:
return false;
case ConnectionType::OUTBOUND_FULL_RELAY:
case ConnectionType::BLOCK_RELAY:
case ConnectionType::ADDR_FETCH:
return true;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
std::string ConnectionTypeAsString() const { return ::ConnectionTypeAsString(m_conn_type); }
/** A ping-pong round trip has completed successfully. Update latest and minimum ping times. */

View File

@ -1968,10 +1968,23 @@ void PeerManagerImpl::Misbehaving(Peer& peer, const std::string& message)
LogPrint(BCLog::NET, "Misbehaving: peer=%d%s\n", peer.m_id, message_prefixed);
}
static void HandleDoSPunishment(CConnman& connman, NodeId node_id, const int nDoS, const char * const what_is_it) {
// We never actually DoS ban for invalid blocks, merely disconnect nodes if we're relying on them as a primary node
const std::string msg = strprintf("peer=%d got DoS score %d on invalid %s", node_id, nDoS, what_is_it);
connman.ForNode(node_id, [msg](CNode* node) {
if (node->PunishInvalidBlocks()) {
LogPrint(BCLog::NET, "%s; simply disconnecting\n", msg);
node->fDisconnect = true;
} else {
LogPrint(BCLog::NET, "%s; tolerating\n", msg);
}
return true;
});
}
void PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state,
bool via_compact_block, const std::string& message)
{
PeerRef peer{GetPeerRef(nodeid)};
switch (state.GetResult()) {
case BlockValidationResult::BLOCK_RESULT_UNSET:
break;
@ -1983,7 +1996,7 @@ void PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
case BlockValidationResult::BLOCK_CONSENSUS:
case BlockValidationResult::BLOCK_MUTATED:
if (!via_compact_block) {
if (peer) Misbehaving(*peer, message);
HandleDoSPunishment(m_connman, nodeid, 100, "block");
return;
}
break;
@ -1998,7 +2011,7 @@ void PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
// Discourage outbound (but not inbound) peers if on an invalid chain.
// Exempt HB compact block peers. Manual connections are always protected from discouragement.
if (!via_compact_block && !node_state->m_is_inbound) {
if (peer) Misbehaving(*peer, message);
HandleDoSPunishment(m_connman, nodeid, 100, "block");
return;
}
break;
@ -2006,11 +2019,11 @@ void PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
case BlockValidationResult::BLOCK_INVALID_HEADER:
case BlockValidationResult::BLOCK_CHECKPOINT:
case BlockValidationResult::BLOCK_INVALID_PREV:
if (peer) Misbehaving(*peer, message);
HandleDoSPunishment(m_connman, nodeid, 100, "block header");
return;
// Conflicting (but not necessarily invalid) data or different policy:
case BlockValidationResult::BLOCK_MISSING_PREV:
if (peer) Misbehaving(*peer, message);
HandleDoSPunishment(m_connman, nodeid, 100, "block header");
return;
case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE:
case BlockValidationResult::BLOCK_TIME_FUTURE:
@ -2029,7 +2042,7 @@ void PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat
break;
// The node is providing invalid data:
case TxValidationResult::TX_CONSENSUS:
if (peer) Misbehaving(*peer, "");
HandleDoSPunishment(m_connman, nodeid, 100, "transaction");
return;
// Conflicting (but not necessarily invalid) data or different policy:
case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE:
@ -2793,6 +2806,10 @@ void PeerManagerImpl::HandleUnconnectingHeaders(CNode& pfrom, Peer& peer,
// eventually get the headers - even from a different peer -
// we can use this peer to download.
WITH_LOCK(cs_main, UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()));
if (pfrom.PunishInvalidBlocks()) {
pfrom.fDisconnect = true;
}
}
bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const

View File

@ -1266,7 +1266,7 @@ class FullBlockTest(BitcoinTestFramework):
# Don't use v2transport for the large reorg, which is too slow with the unoptimized python ChaCha20 implementation
if self.options.v2transport:
self.nodes[0].disconnect_p2ps()
self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore(), supports_v2_p2p=False)
self.helper_peer = self.nodes[0].add_outbound_p2p_connection(P2PDataStore(), supports_v2_p2p=False, advertise_v2_p2p=False, p2p_idx=0)
self.log.info("Test a re-org of one week's worth of blocks (1088 blocks)")
self.move_tip(88)
@ -1413,7 +1413,7 @@ class FullBlockTest(BitcoinTestFramework):
"""Add a P2P connection to the node.
Helper to connect and wait for version handshake."""
self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore())
self.helper_peer = self.nodes[0].add_outbound_p2p_connection(P2PDataStore(), p2p_idx=0)
# We need to wait for the initial getheaders from the peer before we
# start populating our blockstore. If we don't, then we may run ahead
# to the next subtest before we receive the getheaders. We'd then send

View File

@ -47,7 +47,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.headers_fork = [from_hex(CBlockHeader(), h) for h in self.headers_fork]
self.log.info("Feed all non-fork headers, including and up to the first checkpoint")
peer_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface())
peer_checkpoint = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0)
peer_checkpoint.send_and_ping(msg_headers(self.headers))
assert {
'height': 546,
@ -64,7 +64,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
self.log.info("Feed all fork headers (succeeds without checkpoint)")
# On node 0 it succeeds because checkpoints are disabled
self.restart_node(0, extra_args=['-nocheckpoints', "-minimumchainwork=0x0", '-prune=550'])
peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface())
peer_no_checkpoint = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0)
peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork))
assert {
"height": 2,
@ -74,7 +74,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
} in self.nodes[0].getchaintips()
# On node 1 it succeeds because no checkpoint has been reached yet by a chain tip
peer_before_checkpoint = self.nodes[1].add_p2p_connection(P2PInterface())
peer_before_checkpoint = self.nodes[1].add_outbound_p2p_connection(P2PInterface(), p2p_idx=1)
peer_before_checkpoint.send_and_ping(msg_headers(self.headers_fork))
assert {
"height": 2,

View File

@ -33,8 +33,8 @@ class InvalidTxRequestTest(BitcoinTestFramework):
"""Add a P2P connection to the node.
Helper to connect and wait for version handshake."""
for _ in range(num_connections):
self.nodes[0].add_p2p_connection(P2PDataStore())
for i in range(num_connections):
self.nodes[0].add_outbound_p2p_connection(P2PDataStore(), p2p_idx=i)
def reconnect_p2p(self, **kwargs):
"""Tear down and bootstrap the P2P connection to the node.

View File

@ -99,7 +99,7 @@ class MutatedBlocksTest(BitcoinTestFramework):
# Check that unexpected-witness mutation check doesn't trigger on a header that doesn't connect to anything
assert_equal(len(self.nodes[0].getpeerinfo()), 1)
attacker = self.nodes[0].add_p2p_connection(P2PInterface())
attacker = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=1)
block_missing_prev = copy.deepcopy(block)
block_missing_prev.hashPrevBlock = 123
block_missing_prev.solve()

View File

@ -226,7 +226,7 @@ class PackageRelayTest(BitcoinTestFramework):
tx_orphan_bad_wit.wit.vtxinwit.append(CTxInWitness())
tx_orphan_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
bad_orphan_sender = node.add_p2p_connection(P2PInterface())
bad_orphan_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0)
parent_sender = node.add_p2p_connection(P2PInterface())
# 1. Child is received first. It is missing an input.
@ -266,7 +266,7 @@ class PackageRelayTest(BitcoinTestFramework):
tx_parent_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
package_sender = node.add_p2p_connection(P2PInterface())
fake_parent_sender = node.add_p2p_connection(P2PInterface())
fake_parent_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0)
# 1. Child is received first. It is missing an input.
child_wtxid_int = int(high_fee_child["tx"].getwtxid(), 16)

View File

@ -80,7 +80,7 @@ class AcceptBlockTest(BitcoinTestFramework):
return False
def run_test(self):
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
test_node = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0)
min_work_node = self.nodes[1].add_p2p_connection(P2PInterface())
# 1. Have nodes mine a block (leave IBD)
@ -203,7 +203,7 @@ class AcceptBlockTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
self.nodes[1].disconnect_p2ps()
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
test_node = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=2)
test_node.send_and_ping(msg_block(block_h1f))
assert_equal(self.nodes[0].getblockcount(), 2)
@ -275,7 +275,7 @@ class AcceptBlockTest(BitcoinTestFramework):
test_node.wait_for_disconnect()
self.nodes[0].disconnect_p2ps()
test_node = self.nodes[0].add_p2p_connection(P2PInterface())
test_node = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=3)
# We should have failed reorg and switched back to 290 (but have block 291)
assert_equal(self.nodes[0].getblockcount(), 290)