Skip to content

Commit c0e5139

Browse files
committed
Merge #11458: Don't process unrequested, low-work blocks
01b52ce Add comment explaining forced processing of compact blocks (Suhas Daftuar) 08fd822 qa: add test for minchainwork use in acceptblock (Suhas Daftuar) ce8cd7a Don't process unrequested, low-work blocks (Suhas Daftuar) Pull request description: A peer could try to waste our resources by sending us unrequested blocks with low work (eg to fill up our disk). Since e265200 we no longer request blocks until we know we're on a chain with more than nMinimumChainWork (our anti-DoS threshold), but we would still process unrequested blocks that had more work than our tip (which generally has low-work during IBD), even though we may not yet have found a headers chain with sufficient work. Fix this and add a test. Tree-SHA512: 1a4fb0bbd78054b84683f995c8c3194dd44fa914dc351ae4379c7c1a6f83224f609f8b9c2d9dde28741426c6af008ffffea836d21aa31a5ebaa00f8e0f81229e
2 parents e668a6e + 01b52ce commit c0e5139

File tree

3 files changed

+54
-13
lines changed

3 files changed

+54
-13
lines changed

src/net_processing.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,7 +2146,16 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
21462146
mapBlockSource.emplace(pblock->GetHash(), std::make_pair(pfrom->GetId(), false));
21472147
}
21482148
bool fNewBlock = false;
2149-
ProcessNewBlock(chainparams, pblock, true, &fNewBlock);
2149+
// Setting fForceProcessing to true means that we bypass some of
2150+
// our anti-DoS protections in AcceptBlock, which filters
2151+
// unrequested blocks that might be trying to waste our resources
2152+
// (eg disk space). Because we only try to reconstruct blocks when
2153+
// we're close to caught up (via the CanDirectFetch() requirement
2154+
// above, combined with the behavior of not requesting blocks until
2155+
// we have a chain with at least nMinimumChainWork), and we ignore
2156+
// compact blocks with less work than our tip, it is safe to treat
2157+
// reconstructed compact blocks as having been requested.
2158+
ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
21502159
if (fNewBlock) {
21512160
pfrom->nLastBlockTime = GetTime();
21522161
} else {
@@ -2226,7 +2235,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
22262235
bool fNewBlock = false;
22272236
// Since we requested this block (it was in mapBlocksInFlight), force it to be processed,
22282237
// even if it would not be a candidate for new tip (missing previous block, chain not long enough, etc)
2229-
ProcessNewBlock(chainparams, pblock, true, &fNewBlock);
2238+
// This bypasses some anti-DoS logic in AcceptBlock (eg to prevent
2239+
// disk-space attacks), but this should be safe due to the
2240+
// protections in the compact block handler -- see related comment
2241+
// in compact block optimistic reconstruction handling.
2242+
ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
22302243
if (fNewBlock) {
22312244
pfrom->nLastBlockTime = GetTime();
22322245
} else {

src/validation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3135,6 +3135,12 @@ static bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidation
31353135
if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned
31363136
if (!fHasMoreWork) return true; // Don't process less-work chains
31373137
if (fTooFarAhead) return true; // Block height is too high
3138+
3139+
// Protect against DoS attacks from low-work chains.
3140+
// If our tip is behind, a peer could try to send us
3141+
// low-work blocks on a fake chain that we would never
3142+
// request; don't process these.
3143+
if (pindex->nChainWork < nMinimumChainWork) return true;
31383144
}
31393145
if (fNewBlock) *fNewBlock = true;
31403146

test/functional/p2p-acceptblock.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@
88
versus non-whitelisted peers, this tests the behavior of both (effectively two
99
separate tests running in parallel).
1010
11-
Setup: two nodes, node0 and node1, not connected to each other. Node0 does not
11+
Setup: three nodes, node0+node1+node2, not connected to each other. Node0 does not
1212
whitelist localhost, but node1 does. They will each be on their own chain for
13-
this test.
13+
this test. Node2 will have nMinimumChainWork set to 0x10, so it won't process
14+
low-work unrequested blocks.
1415
15-
We have one NodeConn connection to each, test_node and white_node respectively.
16+
We have one NodeConn connection to each, test_node, white_node, and min_work_node,
17+
respectively.
1618
1719
The test:
1820
1. Generate one block on each node, to leave IBD.
1921
2022
2. Mine a new block on each tip, and deliver to each node from node's peer.
21-
The tip should advance.
23+
The tip should advance for node0 and node1, but node2 should skip processing
24+
due to nMinimumChainWork.
25+
26+
Node2 is unused in tests 3-7:
2227
2328
3. Mine a block that forks the previous block, and deliver to each node from
2429
corresponding peer.
@@ -46,6 +51,10 @@
4651
4752
7. Send Node0 the missing block again.
4853
Node0 should process and the tip should advance.
54+
55+
8. Test Node2 is able to sync when connected to node0 (which should have sufficient
56+
work on its chain).
57+
4958
"""
5059

5160
from test_framework.mininode import *
@@ -62,52 +71,60 @@ def add_options(self, parser):
6271

6372
def set_test_params(self):
6473
self.setup_clean_chain = True
65-
self.num_nodes = 2
66-
self.extra_args = [[], ["-whitelist=127.0.0.1"]]
74+
self.num_nodes = 3
75+
self.extra_args = [[], ["-whitelist=127.0.0.1"], ["-minimumchainwork=0x10"]]
6776

6877
def setup_network(self):
6978
# Node0 will be used to test behavior of processing unrequested blocks
7079
# from peers which are not whitelisted, while Node1 will be used for
7180
# the whitelisted case.
81+
# Node2 will be used for non-whitelisted peers to test the interaction
82+
# with nMinimumChainWork.
7283
self.setup_nodes()
7384

7485
def run_test(self):
7586
# Setup the p2p connections and start up the network thread.
7687
test_node = NodeConnCB() # connects to node0 (not whitelisted)
7788
white_node = NodeConnCB() # connects to node1 (whitelisted)
89+
min_work_node = NodeConnCB() # connects to node2 (not whitelisted)
7890

7991
connections = []
8092
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
8193
connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node))
94+
connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], min_work_node))
8295
test_node.add_connection(connections[0])
8396
white_node.add_connection(connections[1])
97+
min_work_node.add_connection(connections[2])
8498

8599
NetworkThread().start() # Start up network handling in another thread
86100

87101
# Test logic begins here
88102
test_node.wait_for_verack()
89103
white_node.wait_for_verack()
104+
min_work_node.wait_for_verack()
90105

91-
# 1. Have both nodes mine a block (leave IBD)
106+
# 1. Have nodes mine a block (nodes1/2 leave IBD)
92107
[ n.generate(1) for n in self.nodes ]
93108
tips = [ int("0x" + n.getbestblockhash(), 0) for n in self.nodes ]
94109

95110
# 2. Send one block that builds on each tip.
96-
# This should be accepted.
111+
# This should be accepted by nodes 1/2
97112
blocks_h2 = [] # the height 2 blocks on each node's chain
98113
block_time = int(time.time()) + 1
99-
for i in range(2):
114+
for i in range(3):
100115
blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time))
101116
blocks_h2[i].solve()
102117
block_time += 1
103118
test_node.send_message(msg_block(blocks_h2[0]))
104119
white_node.send_message(msg_block(blocks_h2[1]))
120+
min_work_node.send_message(msg_block(blocks_h2[2]))
105121

106-
for x in [test_node, white_node]:
122+
for x in [test_node, white_node, min_work_node]:
107123
x.sync_with_ping()
108124
assert_equal(self.nodes[0].getblockcount(), 2)
109125
assert_equal(self.nodes[1].getblockcount(), 2)
110-
self.log.info("First height 2 block accepted by both nodes")
126+
assert_equal(self.nodes[2].getblockcount(), 1)
127+
self.log.info("First height 2 block accepted by node0/node1; correctly rejected by node2")
111128

112129
# 3. Send another block that builds on the original tip.
113130
blocks_h2f = [] # Blocks at height 2 that fork off the main chain
@@ -220,6 +237,11 @@ def run_test(self):
220237
assert_equal(self.nodes[0].getblockcount(), 290)
221238
self.log.info("Successfully reorged to longer chain from non-whitelisted peer")
222239

240+
# 8. Connect node2 to node0 and ensure it is able to sync
241+
connect_nodes(self.nodes[0], 2)
242+
sync_blocks([self.nodes[0], self.nodes[2]])
243+
self.log.info("Successfully synced nodes 2 and 0")
244+
223245
[ c.disconnect_node() for c in connections ]
224246

225247
if __name__ == '__main__':

0 commit comments

Comments
 (0)