merge bitcoin#20799: Only support version 2 compact blocks

This commit is contained in:
Kittywhiskers Van Gogh 2024-06-08 23:03:52 +00:00
parent f4ce573538
commit 6274a571b7
No known key found for this signature in database
GPG Key ID: 30CD0C065E5C4AAD
2 changed files with 97 additions and 121 deletions

View File

@ -183,6 +183,8 @@ static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1};
* based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR
* is exempt from this limit). */
static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND};
/** The compactblocks version we support. See BIP 152. */
static constexpr uint64_t CMPCTBLOCKS_VERSION{1};
struct COrphanTx {
// When modifying, adapt the copy of this definition in tests/DoS_tests.
@ -777,15 +779,10 @@ struct CNodeState {
bool fPreferHeaders{false};
//! Whether this peer wants invs or compressed headers (when possible) for block announcements.
bool fPreferHeadersCompressed{false};
//! Whether this peer wants invs or cmpctblocks (when possible) for block announcements.
bool fPreferHeaderAndIDs{false};
//! Whether this peer will send us cmpctblocks if we request them
bool fProvidesHeaderAndIDs{false};
/**
* If we've announced last version to this peer: whether the peer sends last version in cmpctblocks/blocktxns,
* otherwise: whether this peer sends non-last version in cmpctblocks/blocktxns.
*/
bool fSupportsDesiredCmpctVersion{false};
/** Whether this peer wants invs or cmpctblocks (when possible) for block announcements. */
bool m_requested_hb_cmpctblocks{false};
/** Whether this peer will send us cmpctblocks if we request them. */
bool m_provides_cmpctblocks{false};
/** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic.
*
@ -1059,11 +1056,11 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid)
if (m_ignore_incoming_txs) return;
CNodeState* nodestate = State(nodeid);
if (!nodestate || !nodestate->fSupportsDesiredCmpctVersion) {
// Never ask from peers who can't provide desired version.
if (!nodestate || !nodestate->m_provides_cmpctblocks) {
// Don't request compact blocks if the peer has not signalled support
return;
}
if (nodestate->fProvidesHeaderAndIDs) {
int num_outbound_hb_peers = 0;
for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) {
if (*it == nodeid) {
@ -1086,28 +1083,26 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid)
}
}
}
m_connman.ForNode(nodeid, [this](CNode* pfrom){
LockAssertion lock(::cs_main);
uint64_t nCMPCTBLOCKVersion = 1;
m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
// As per BIP152, we only get 3 of our peers to announce
// blocks using compact encodings.
m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this, nCMPCTBLOCKVersion](CNode* pnodeStop){
m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion));
m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this](CNode* pnodeStop){
m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
// save BIP152 bandwidth state: we select peer to be low-bandwidth
pnodeStop->m_bip152_highbandwidth_to = false;
return true;
});
lNodesAnnouncingHeaderAndIDs.pop_front();
}
m_connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion));
m_connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/true, /*version=*/CMPCTBLOCKS_VERSION));
// save BIP152 bandwidth state: we select peer to be high-bandwidth
pfrom->m_bip152_highbandwidth_to = true;
lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId());
return true;
});
}
}
bool PeerManagerImpl::TipMayBeStale()
{
@ -2024,9 +2019,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
CNodeState &state = *State(pnode->GetId());
// If the peer has, or we announced to them the previous block already,
// but we don't think they have this one, go ahead and announce it
if (state.fPreferHeaderAndIDs &&
!PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) {
if (state.m_requested_hb_cmpctblocks && !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) {
LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerManager::NewPoWValidBlock",
hashBlock.ToString(), pnode->GetId());
m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock));
@ -2757,7 +2750,6 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, c
}
resp.txn[i] = block.vtx[req.indexes[i]];
}
LOCK(cs_main);
CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp));
}
@ -2904,7 +2896,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
}
if (vGetData.size() > 0) {
if (!m_ignore_incoming_txs &&
nodestate->fSupportsDesiredCmpctVersion &&
nodestate->m_provides_cmpctblocks &&
vGetData.size() == 1 &&
mapBlocksInFlight.size() == 1 &&
pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
@ -3533,14 +3525,12 @@ void PeerManagerImpl::ProcessMessage(
m_connman.PushMessage(&pfrom, msgMaker.Make((pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::SENDHEADERS2 : NetMsgType::SENDHEADERS));
if (pfrom.CanRelay()) {
// Tell our peer we are willing to provide version-1 cmpctblocks
// Tell our peer we are willing to provide version 1 cmpctblocks.
// However, we do not request new block announcements using
// cmpctblock messages.
// We send this to non-NODE NETWORK peers as well, because
// they may wish to request compact blocks from us
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 1;
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION));
}
if (!fBlocksOnly) {
@ -3570,18 +3560,19 @@ void PeerManagerImpl::ProcessMessage(
}
if (msg_type == NetMsgType::SENDCMPCT) {
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 1;
vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion;
if (nCMPCTBLOCKVersion == 1) {
bool sendcmpct_hb{false};
uint64_t sendcmpct_version{0};
vRecv >> sendcmpct_hb >> sendcmpct_version;
if (sendcmpct_version != CMPCTBLOCKS_VERSION) return;
LOCK(cs_main);
State(pfrom.GetId())->fProvidesHeaderAndIDs = true;
State(pfrom.GetId())->fPreferHeaderAndIDs = fAnnounceUsingCMPCTBLOCK;
State(pfrom.GetId())->fSupportsDesiredCmpctVersion = true;
CNodeState *nodestate = State(pfrom.GetId());
nodestate->m_provides_cmpctblocks = true;
nodestate->m_requested_hb_cmpctblocks = sendcmpct_hb;
// save whether peer selects us as BIP152 high-bandwidth peer
// (receiving sendcmpct(1) signals high-bandwidth, sendcmpct(0) low-bandwidth)
pfrom.m_bip152_highbandwidth_from = fAnnounceUsingCMPCTBLOCK;
}
pfrom.m_bip152_highbandwidth_from = sendcmpct_hb;
return;
}
@ -3976,9 +3967,7 @@ void PeerManagerImpl::ProcessMessage(
// 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 = MSG_BLOCK);
inv.hash = req.blockhash;
CInv inv{MSG_BLOCK, 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 (without cs_main)
return;
@ -5449,7 +5438,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
LOCK(peer->m_block_inv_mutex);
std::vector<CBlock> vHeaders;
bool fRevertToInv = ((!state.fPreferHeaders && !state.fPreferHeadersCompressed &&
(!state.fPreferHeaderAndIDs || peer->m_blocks_for_headers_relay.size() > 1)) ||
(!state.m_requested_hb_cmpctblocks || peer->m_blocks_for_headers_relay.size() > 1)) ||
peer->m_blocks_for_headers_relay.size() > MAX_BLOCKS_TO_ANNOUNCE);
const CBlockIndex *pBestIndex = nullptr; // last header queued for delivery
ProcessBlockAvailability(pto->GetId()); // ensure pindexBestKnownBlock is up-to-date
@ -5511,7 +5500,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
}
}
if (!fRevertToInv && !vHeaders.empty()) {
if (vHeaders.size() == 1 && state.fPreferHeaderAndIDs) {
if (vHeaders.size() == 1 && state.m_requested_hb_cmpctblocks) {
// We only send up to 1 block as header-and-ids, as otherwise
// probably means we're doing an initial-ish-sync or they're slow
LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", __func__,

View File

@ -60,7 +60,7 @@ from test_framework.util import (
# TestP2PConn: A peer we use to send messages to dashd, and store responses.
class TestP2PConn(P2PInterface):
def __init__(self, cmpct_version):
def __init__(self):
super().__init__()
self.last_sendcmpct = []
self.block_announced = False
@ -68,7 +68,6 @@ class TestP2PConn(P2PInterface):
# This is for synchronizing the p2p message traffic,
# so we can eg wait until a particular block is announced.
self.announced_blockhashes = set()
self.cmpct_version = cmpct_version
def on_sendcmpct(self, message):
self.last_sendcmpct.append(message)
@ -177,14 +176,13 @@ class CompactBlocksTest(BitcoinTestFramework):
# Test "sendcmpct" (between peers with the same version):
# - No compact block announcements unless sendcmpct is sent.
# - If sendcmpct is sent with version < 1, the message is ignored.
# - If sendcmpct is sent with version > 1, the message is ignored.
# - If sendcmpct is sent with boolean 0, then block announcements are not
# made with compact blocks.
# - If sendcmpct is then sent with boolean 1, then new block announcements
# are made with compact blocks.
# If old_node is passed in, request compact blocks with version=preferred-1
# and verify that it receives block announcements via compact block.
def test_sendcmpct(self, test_node, old_node=None):
preferred_version = test_node.cmpct_version
def test_sendcmpct(self, test_node):
node = self.nodes[0]
# Make sure we get a SENDCMPCT message from our peer
@ -192,10 +190,8 @@ class CompactBlocksTest(BitcoinTestFramework):
return (len(test_node.last_sendcmpct) > 0)
test_node.wait_until(received_sendcmpct, timeout=30)
with p2p_lock:
# Check that the first version received is the preferred one
assert_equal(test_node.last_sendcmpct[0].version, preferred_version)
# And that we receive versions down to 1.
assert_equal(test_node.last_sendcmpct[-1].version, 1)
# Check that version 1 is received.
assert_equal(test_node.last_sendcmpct[0].version, 1)
test_node.last_sendcmpct = []
tip = int(node.getbestblockhash(), 16)
@ -223,22 +219,29 @@ class CompactBlocksTest(BitcoinTestFramework):
# Before each test, sync the headers chain.
test_node.request_headers_and_sync(locator=[tip])
# Now try a SENDCMPCT message with too-low version
test_node.send_and_ping(msg_sendcmpct(announce=True, version=0))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Now try a SENDCMPCT message with too-high version
test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version+1))
test_node.send_and_ping(msg_sendcmpct(announce=True, version=2))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Now try a SENDCMPCT message with valid version, but announce=False
test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version))
test_node.send_and_ping(msg_sendcmpct(announce=False, version=1))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Finally, try a SENDCMPCT message with announce=True
test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version))
test_node.send_and_ping(msg_sendcmpct(announce=True, version=1))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Try one more time (no headers sync should be needed!)
@ -249,23 +252,13 @@ class CompactBlocksTest(BitcoinTestFramework):
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Try one more time, after sending a version-1, announce=false message.
test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version-1))
test_node.send_and_ping(msg_sendcmpct(announce=False, version=0))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Now turn off announcements
test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version))
test_node.send_and_ping(msg_sendcmpct(announce=False, version=1))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message)
# This code should be enabled after increasing cmctblk version
to_validate = False
if to_validate and old_node is not None:
# Verify that a peer using an older protocol version can receive
# announcements from this node.
old_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version-1))
# Header sync
old_node.request_headers_and_sync(locator=[tip])
check_announcement_of_new_block(node, old_node, lambda p: "cmpctblock" in p.last_message)
# This test actually causes dashd to (reasonably!) disconnect us, so do this last.
def test_invalid_cmpctblock_message(self):
self.nodes[0].generate(COINBASE_MATURITY + 1)
@ -283,7 +276,6 @@ class CompactBlocksTest(BitcoinTestFramework):
# Compare the generated shortids to what we expect based on BIP 152, given
# dashd's choice of nonce.
def test_compactblock_construction(self, test_node):
version = test_node.cmpct_version
node = self.nodes[0]
# Generate a bunch of transactions.
node.generate(COINBASE_MATURITY + 1)
@ -321,7 +313,7 @@ class CompactBlocksTest(BitcoinTestFramework):
assert "cmpctblock" in test_node.last_message
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids)
self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
self.check_compactblock_construction_from_block(header_and_shortids, block_hash, block)
# Now fetch the compact block using a normal non-announce getdata
test_node.clear_block_announcement()
@ -336,9 +328,9 @@ class CompactBlocksTest(BitcoinTestFramework):
assert "cmpctblock" in test_node.last_message
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids)
self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
self.check_compactblock_construction_from_block(header_and_shortids, block_hash, block)
def check_compactblock_construction_from_block(self, version, header_and_shortids, block_hash, block):
def check_compactblock_construction_from_block(self, header_and_shortids, block_hash, block):
# Check that we got the right block!
header_and_shortids.header.calc_sha256()
assert_equal(header_and_shortids.header.sha256, block_hash)
@ -606,7 +598,7 @@ class CompactBlocksTest(BitcoinTestFramework):
assert "blocktxn" not in test_node.last_message
# Request with out-of-bounds tx index results in disconnect
bad_peer = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1))
bad_peer = self.nodes[0].add_p2p_connection(TestP2PConn())
block_hash = node.getblockhash(chain_height)
block = from_hex(CBlock(), node.getblock(block_hash, False))
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [len(block.vtx)])
@ -717,7 +709,7 @@ class CompactBlocksTest(BitcoinTestFramework):
node = self.nodes[0]
tip = node.getbestblockhash()
peer.get_headers(locator=[int(tip, 16)], hashstop=0)
peer.send_and_ping(msg_sendcmpct(announce=True, version=peer.cmpct_version))
peer.send_and_ping(msg_sendcmpct(announce=True, version=1))
def test_compactblock_reconstruction_multiple_peers(self, stalling_peer, delivery_peer):
node = self.nodes[0]
@ -769,7 +761,7 @@ class CompactBlocksTest(BitcoinTestFramework):
def test_highbandwidth_mode_states_via_getpeerinfo(self):
# create new p2p connection for a fresh state w/o any prior sendcmpct messages sent
hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1))
hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn())
# assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}`
# match the given parameters for the last peer of a given node
@ -800,19 +792,17 @@ class CompactBlocksTest(BitcoinTestFramework):
self.nodes[0].generate(1)
# Setup the p2p connections
self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1))
self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK | NODE_HEADERS_COMPRESSED)
self.additional_test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK | NODE_HEADERS_COMPRESSED)
self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn())
self.additional_test_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK | NODE_HEADERS_COMPRESSED)
# We will need UTXOs to construct transactions in later tests.
self.make_utxos()
self.log.info("Testing SENDCMPCT p2p message... ")
self.test_sendcmpct(self.test_node, old_node=self.old_node)
self.test_sendcmpct(self.test_node)
self.test_sendcmpct(self.additional_test_node)
self.log.info("Testing compactblock construction...")
self.test_compactblock_construction(self.old_node)
self.test_compactblock_construction(self.test_node)
self.log.info("Testing compactblock requests... ")
@ -820,11 +810,9 @@ class CompactBlocksTest(BitcoinTestFramework):
self.log.info("Testing getblocktxn handler...")
self.test_getblocktxn_handler(self.test_node)
self.test_getblocktxn_handler(self.old_node)
self.log.info("Testing compactblock requests/announcements not at chain tip...")
self.test_compactblocks_not_at_tip(self.test_node)
self.test_compactblocks_not_at_tip(self.old_node)
self.log.info("Testing handling of incorrect blocktxn responses...")
self.test_incorrect_blocktxn_response(self.test_node)
@ -834,13 +822,12 @@ class CompactBlocksTest(BitcoinTestFramework):
# End-to-end block relay tests
self.log.info("Testing end-to-end block relay...")
self.request_cb_announcements(self.old_node)
self.request_cb_announcements(self.test_node)
self.test_end_to_end_block_relay([self.test_node, self.old_node])
self.request_cb_announcements(self.additional_test_node)
self.test_end_to_end_block_relay([self.test_node, self.additional_test_node])
self.log.info("Testing handling of invalid compact blocks...")
self.test_invalid_tx_in_compactblock(self.test_node)
self.test_invalid_tx_in_compactblock(self.old_node)
self.log.info("Testing invalid index in cmpctblock message...")
self.test_invalid_cmpctblock_message()