p2p: Ignore CMPCTBLOCK from peer that hasn't sent SENDCMPCT
What changed, and why it matters
This Bitcoin Core update tightens the rules for compact block messages. Before the change, a peer could send a compressed block (CMPCTBLOCK) even if it had never advertised support for compact blocks via the SENDCMPCT handshake. The fix makes the node ignore such unexpected CMPCTBLOCK messages. The commit also updates the test framework so that simulated peers use the current compact-block version 2 by default. This is a hardening change that reduces the attack surface for malformed or unexpected compact-block traffic, but the commit message does not describe it as a security fix and no exploit is demonstrated.
Treat as a defensive hardening patch. Review whether any downstream logic assumed CMPCTBLOCK implies a prior SENDCMPCT, and verify that the new check does not interfere with legitimate compact-block relay. No urgent security response is indicated by the commit materials alone.
Security signals we found
New input-validation gate before deserialization of peer message
Peer-state flag (m_provides_cmpctblocks) now enforced for CMPCTBLOCK
Functional tests extended to cover unsolicited and solicited CMPCTBLOCK from non-announcing peers
Default protocol version in test framework updated to current version 2
Evidence from the diff
In src/net_processing.cpp, ProcessMessage now checks the CNodeState flag m_provides_cmpctblocks under cs_main before deserializing a CMPCTBLOCK message. If the peer never sent SENDCMPCT, the message is logged at debug level and dropped. The functional tests are updated to send msg_sendcmpct() before exercising compact-block behavior, and the default version in msg_sendcmpct is changed from 1 to 2 to match the version nodes currently expect. The change is defensive: it prevents peers from triggering compact-block deserialization and downstream processing without first completing the SENDCMPCT handshake.
Changed components
src/net_processing.cpp (CMPCTBLOCK message handler)test/functional/p2p_compactblocks.pytest/functional/p2p_mutated_blocks.pytest/functional/test_framework/messages.pyInspect captured patch +25 / −1
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 41d39966..bf7e0b64 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -4476,6 +4476,15 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string
return;
}
+ {
+ LOCK(cs_main);
+ const CNodeState *nodestate = State(pfrom.GetId());
+ if (!nodestate->m_provides_cmpctblocks) {
+ LogDebug(BCLog::CMPCTBLOCK, "%s sent us a compact block despite never having sent us a SENDCMPCT!", pfrom.LogPeer());
+ return;
+ }
+ }
+
CBlockHeaderAndShortTxIDs cmpctblock;
vRecv >> cmpctblock;
diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py
index 135e96db..99f1e5fa 100755
--- a/test/functional/p2p_compactblocks.py
+++ b/test/functional/p2p_compactblocks.py
@@ -1002,6 +1002,17 @@ class CompactBlocksTest(BitcoinTestFramework):
unsolicited_peer = self.nodes[0].add_p2p_connection(TestP2PConn())
self.assert_highbandwidth_states(node, idx=-1, hb_to=False, hb_from=False)
+ self.log.info("Test that a node ignores unsolicited CMPCTBLOCK messages from peers that have not sent SENDCMPCT.")
+ assert ignores_compact_block(unsolicited_peer, solicited=False)
+
+ self.log.info("Test that a node ignores solicited CMPCTBLOCK messages from peers that have not sent SENDCMPCT.")
+ assert ignores_compact_block(unsolicited_peer, solicited=True)
+
+ # Unsolicited peer announces CMPCTBLOCK support with SENDCMPCT message,
+ # but still non-HB.
+ unsolicited_peer.send_and_ping(msg_sendcmpct())
+ self.assert_highbandwidth_states(node, idx=-1, hb_to=False, hb_from=False)
+
self.log.info("Test that a node ignores unsolicited CMPCTBLOCK messages from non-HB peers.")
assert ignores_compact_block(unsolicited_peer, solicited=False)
self.assert_highbandwidth_states(node, idx=-1, hb_to=False, hb_from=False)
@@ -1081,12 +1092,14 @@ class CompactBlocksTest(BitcoinTestFramework):
# The previous test will lead to a disconnection. Reconnect before continuing.
self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn())
+ self.segwit_node.send_and_ping(msg_sendcmpct())
self.log.info("Testing handling of multiple blocktxn responses...")
self.test_multiple_blocktxn_response(self.segwit_node)
# The previous test will lead to a disconnection. Reconnect before continuing.
self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn())
+ self.segwit_node.send_and_ping(msg_sendcmpct())
self.log.info("Testing invalid index in cmpctblock message...")
self.test_invalid_cmpctblock_message()
diff --git a/test/functional/p2p_mutated_blocks.py b/test/functional/p2p_mutated_blocks.py
index b9c266f8..51c06569 100755
--- a/test/functional/p2p_mutated_blocks.py
+++ b/test/functional/p2p_mutated_blocks.py
@@ -16,6 +16,7 @@ from test_framework.messages import (
msg_blocktxn,
msg_headers,
HeaderAndShortIDs,
+ msg_sendcmpct,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.blocktools import (
@@ -43,6 +44,7 @@ class MutatedBlocksTest(BitcoinTestFramework):
self.generate(self.wallet, COINBASE_MATURITY)
honest_relayer = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay")
+ honest_relayer.send_and_ping(msg_sendcmpct())
attacker = self.nodes[0].add_p2p_connection(P2PInterface())
# Create new block with two transactions (coinbase + 1 self-transfer).
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index a0f2a174..7dd86879 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -1672,7 +1672,7 @@ class msg_sendcmpct:
__slots__ = ("announce", "version")
msgtype = b"sendcmpct"
- def __init__(self, announce=False, version=1):
+ def __init__(self, announce=False, version=2):
self.announce = announce
self.version = version
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.