Skip to content

Commit 4a90374

Browse files
committed
Merge bitcoin/bitcoin#28120: p2p: make block download logic aware of limited peers threshold
c5b5843 test: avoid requesting blocks beyond limited peer threshold (furszy) 2f6a055 p2p: sync from limited peer, only request blocks below threshold (furszy) 7312772 refactor: Make FindNextBlocks friendlier (furszy) Pull request description: Even when the node believes it has IBD completed, need to avoid requesting historical blocks from network-limited peers. Otherwise, the limited peer will disconnect right away. The simplest scenario could be a node that gets synced, drops connections, and stays inactive for a while. Then, once it re-connects (IBD stays completed), the node tries to fetch all the missing blocks from any peer, getting disconnected by the limited ones. Note: Can verify the behavior by cherry-picking the test commit alone on master. It will fail there. ACKs for top commit: achow101: ACK c5b5843 vasild: ACK c5b5843 mzumsande: Code Review ACK c5b5843 pinheadmz: ACK c5b5843 Tree-SHA512: 9e550698bc6e63cc587b2b988a87d0ab555a8fa188c91c3f33287f8201d77c28b373331845356ad86f17bb21c15950b6466bc1cafd0ce8139d70364cb71c2ad2
2 parents 10d7b6e + c5b5843 commit 4a90374

File tree

2 files changed

+96
-16
lines changed

2 files changed

+96
-16
lines changed

src/net_processing.cpp

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,7 @@ void PeerManagerImpl::FindNextBlocks(std::vector<const CBlockIndex*>& vBlocks, c
14511451
{
14521452
std::vector<const CBlockIndex*> vToFetch;
14531453
int nMaxHeight = std::min<int>(state->pindexBestKnownBlock->nHeight, nWindowEnd + 1);
1454+
bool is_limited_peer = IsLimitedPeer(peer);
14541455
NodeId waitingfor = -1;
14551456
while (pindexWalk->nHeight < nMaxHeight) {
14561457
// Read up to 128 (or more, if more blocks than that are needed) successors of pindexWalk (towards
@@ -1473,30 +1474,46 @@ void PeerManagerImpl::FindNextBlocks(std::vector<const CBlockIndex*>& vBlocks, c
14731474
// We consider the chain that this peer is on invalid.
14741475
return;
14751476
}
1477+
14761478
if (!CanServeWitnesses(peer) && DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) {
14771479
// We wouldn't download this block or its descendants from this peer.
14781480
return;
14791481
}
1482+
14801483
if (pindex->nStatus & BLOCK_HAVE_DATA || (activeChain && activeChain->Contains(pindex))) {
1481-
if (activeChain && pindex->HaveNumChainTxs())
1484+
if (activeChain && pindex->HaveNumChainTxs()) {
14821485
state->pindexLastCommonBlock = pindex;
1483-
} else if (!IsBlockRequested(pindex->GetBlockHash())) {
1484-
// The block is not already downloaded, and not yet in flight.
1485-
if (pindex->nHeight > nWindowEnd) {
1486-
// We reached the end of the window.
1487-
if (vBlocks.size() == 0 && waitingfor != peer.m_id) {
1488-
// We aren't able to fetch anything, but we would be if the download window was one larger.
1489-
if (nodeStaller) *nodeStaller = waitingfor;
1490-
}
1491-
return;
14921486
}
1493-
vBlocks.push_back(pindex);
1494-
if (vBlocks.size() == count) {
1495-
return;
1487+
continue;
1488+
}
1489+
1490+
// Is block in-flight?
1491+
if (IsBlockRequested(pindex->GetBlockHash())) {
1492+
if (waitingfor == -1) {
1493+
// This is the first already-in-flight block.
1494+
waitingfor = mapBlocksInFlight.lower_bound(pindex->GetBlockHash())->second.first;
14961495
}
1497-
} else if (waitingfor == -1) {
1498-
// This is the first already-in-flight block.
1499-
waitingfor = mapBlocksInFlight.lower_bound(pindex->GetBlockHash())->second.first;
1496+
continue;
1497+
}
1498+
1499+
// The block is not already downloaded, and not yet in flight.
1500+
if (pindex->nHeight > nWindowEnd) {
1501+
// We reached the end of the window.
1502+
if (vBlocks.size() == 0 && waitingfor != peer.m_id) {
1503+
// We aren't able to fetch anything, but we would be if the download window was one larger.
1504+
if (nodeStaller) *nodeStaller = waitingfor;
1505+
}
1506+
return;
1507+
}
1508+
1509+
// Don't request blocks that go further than what limited peers can provide
1510+
if (is_limited_peer && (state->pindexBestKnownBlock->nHeight - pindex->nHeight >= static_cast<int>(NODE_NETWORK_LIMITED_MIN_BLOCKS) - 2 /* two blocks buffer for possible races */)) {
1511+
continue;
1512+
}
1513+
1514+
vBlocks.push_back(pindex);
1515+
if (vBlocks.size() == count) {
1516+
return;
15001517
}
15011518
}
15021519
}

test/functional/p2p_node_network_limited.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
from test_framework.test_framework import BitcoinTestFramework
2222
from test_framework.util import (
2323
assert_equal,
24+
assert_raises_rpc_error,
25+
try_rpc
2426
)
2527

28+
# Minimum blocks required to signal NODE_NETWORK_LIMITED #
29+
NODE_NETWORK_LIMITED_MIN_BLOCKS = 288
2630

2731
class P2PIgnoreInv(P2PInterface):
2832
firstAddrnServices = 0
@@ -54,6 +58,63 @@ def setup_network(self):
5458
self.add_nodes(self.num_nodes, self.extra_args)
5559
self.start_nodes()
5660

61+
def test_avoid_requesting_historical_blocks(self):
62+
self.log.info("Test full node does not request blocks beyond the limited peer threshold")
63+
pruned_node = self.nodes[0]
64+
miner = self.nodes[1]
65+
full_node = self.nodes[2]
66+
67+
# Connect and generate block to ensure IBD=false
68+
self.connect_nodes(1, 0)
69+
self.connect_nodes(1, 2)
70+
self.generate(miner, 1)
71+
72+
# Verify peers are out of IBD
73+
for node in self.nodes:
74+
assert not node.getblockchaininfo()['initialblockdownload']
75+
76+
# Isolate full_node (the node will remain out of IBD)
77+
full_node.setnetworkactive(False)
78+
self.wait_until(lambda: len(full_node.getpeerinfo()) == 0)
79+
80+
# Mine blocks and sync the pruned node. Surpass the NETWORK_NODE_LIMITED threshold.
81+
# Blocks deeper than the threshold are considered "historical blocks"
82+
num_historial_blocks = 12
83+
self.generate(miner, NODE_NETWORK_LIMITED_MIN_BLOCKS + num_historial_blocks, sync_fun=self.no_op)
84+
self.sync_blocks([miner, pruned_node])
85+
86+
# Connect full_node to prune_node and check peers don't disconnect right away.
87+
# (they will disconnect if full_node, which is chain-wise behind, request blocks
88+
# older than NODE_NETWORK_LIMITED_MIN_BLOCKS)
89+
start_height_full_node = full_node.getblockcount()
90+
full_node.setnetworkactive(True)
91+
self.connect_nodes(2, 0)
92+
assert_equal(len(full_node.getpeerinfo()), 1)
93+
94+
# Wait until the full_node is headers-wise sync
95+
best_block_hash = pruned_node.getbestblockhash()
96+
self.wait_until(lambda: next(filter(lambda x: x['hash'] == best_block_hash, full_node.getchaintips()))['status'] == "headers-only")
97+
98+
# Now, since the node aims to download a window of 1024 blocks,
99+
# ensure it requests the blocks below the threshold only (with a
100+
# 2-block buffer). And also, ensure it does not request any
101+
# historical block.
102+
tip_height = pruned_node.getblockcount()
103+
limit_buffer = 2
104+
# Prevent races by waiting for the tip to arrive first
105+
self.wait_until(lambda: not try_rpc(-1, "Block not found", full_node.getblock, pruned_node.getbestblockhash()))
106+
for height in range(start_height_full_node + 1, tip_height + 1):
107+
if height <= tip_height - (NODE_NETWORK_LIMITED_MIN_BLOCKS - limit_buffer):
108+
assert_raises_rpc_error(-1, "Block not found on disk", full_node.getblock, pruned_node.getblockhash(height))
109+
else:
110+
full_node.getblock(pruned_node.getblockhash(height)) # just assert it does not throw an exception
111+
112+
# Lastly, ensure the full_node is not sync and verify it can get synced by
113+
# establishing a connection with another full node capable of providing them.
114+
assert_equal(full_node.getblockcount(), start_height_full_node)
115+
self.connect_nodes(2, 1)
116+
self.sync_blocks([miner, full_node])
117+
57118
def run_test(self):
58119
node = self.nodes[0].add_p2p_connection(P2PIgnoreInv())
59120

@@ -118,5 +179,7 @@ def run_test(self):
118179
# sync must be possible, node 1 is no longer in IBD and should therefore connect to node 0 (NODE_NETWORK_LIMITED)
119180
self.sync_blocks([self.nodes[0], self.nodes[1]])
120181

182+
self.test_avoid_requesting_historical_blocks()
183+
121184
if __name__ == '__main__':
122185
NodeNetworkLimitedTest().main()

0 commit comments

Comments
 (0)