Skip to content

Commit 93b606a

Browse files
sdaftuarlaanwj
authored andcommitted
Be even stricter in processing unrequested blocks
Github-Pull: #6224 Rebased-From: bfc30b3 6b1066f 04b5d23 59b49cd
1 parent 5c27f12 commit 93b606a

File tree

2 files changed

+90
-14
lines changed

2 files changed

+90
-14
lines changed

qa/rpc-tests/p2p-acceptblock.py

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
it's missing an intermediate block.
4141
Node1 should reorg to this longer chain.
4242
43+
4b.Send 288 more blocks on the longer chain.
44+
Node0 should process all but the last block (too far ahead in height).
45+
Send all headers to Node1, and then send the last block in that chain.
46+
Node1 should accept the block because it's coming from a whitelisted peer.
47+
4348
5. Send a duplicate of the block in #3 to Node0.
4449
Node0 should not process the block because it is unrequested, and stay on
4550
the shorter chain.
@@ -59,6 +64,8 @@ def __init__(self):
5964
NodeConnCB.__init__(self)
6065
self.create_callback_map()
6166
self.connection = None
67+
self.ping_counter = 1
68+
self.last_pong = msg_pong()
6269

6370
def add_connection(self, conn):
6471
self.connection = conn
@@ -82,6 +89,24 @@ def wait_for_verack(self):
8289
def send_message(self, message):
8390
self.connection.send_message(message)
8491

92+
def on_pong(self, conn, message):
93+
self.last_pong = message
94+
95+
# Sync up with the node after delivery of a block
96+
def sync_with_ping(self, timeout=30):
97+
self.connection.send_message(msg_ping(nonce=self.ping_counter))
98+
received_pong = False
99+
sleep_time = 0.05
100+
while not received_pong and timeout > 0:
101+
time.sleep(sleep_time)
102+
timeout -= sleep_time
103+
with mininode_lock:
104+
if self.last_pong.nonce == self.ping_counter:
105+
received_pong = True
106+
self.ping_counter += 1
107+
return received_pong
108+
109+
85110
class AcceptBlockTest(BitcoinTestFramework):
86111
def add_options(self, parser):
87112
parser.add_option("--testbinary", dest="testbinary",
@@ -126,13 +151,15 @@ def run_test(self):
126151
# 2. Send one block that builds on each tip.
127152
# This should be accepted.
128153
blocks_h2 = [] # the height 2 blocks on each node's chain
154+
block_time = time.time() + 1
129155
for i in xrange(2):
130-
blocks_h2.append(create_block(tips[i], create_coinbase(), time.time()+1))
156+
blocks_h2.append(create_block(tips[i], create_coinbase(), block_time))
131157
blocks_h2[i].solve()
158+
block_time += 1
132159
test_node.send_message(msg_block(blocks_h2[0]))
133160
white_node.send_message(msg_block(blocks_h2[1]))
134161

135-
time.sleep(1)
162+
[ x.sync_with_ping() for x in [test_node, white_node] ]
136163
assert_equal(self.nodes[0].getblockcount(), 2)
137164
assert_equal(self.nodes[1].getblockcount(), 2)
138165
print "First height 2 block accepted by both nodes"
@@ -145,7 +172,7 @@ def run_test(self):
145172
test_node.send_message(msg_block(blocks_h2f[0]))
146173
white_node.send_message(msg_block(blocks_h2f[1]))
147174

148-
time.sleep(1) # Give time to process the block
175+
[ x.sync_with_ping() for x in [test_node, white_node] ]
149176
for x in self.nodes[0].getchaintips():
150177
if x['hash'] == blocks_h2f[0].hash:
151178
assert_equal(x['status'], "headers-only")
@@ -164,7 +191,7 @@ def run_test(self):
164191
test_node.send_message(msg_block(blocks_h3[0]))
165192
white_node.send_message(msg_block(blocks_h3[1]))
166193

167-
time.sleep(1)
194+
[ x.sync_with_ping() for x in [test_node, white_node] ]
168195
# Since the earlier block was not processed by node0, the new block
169196
# can't be fully validated.
170197
for x in self.nodes[0].getchaintips():
@@ -182,6 +209,45 @@ def run_test(self):
182209
assert_equal(self.nodes[1].getblockcount(), 3)
183210
print "Successfully reorged to length 3 chain from whitelisted peer"
184211

212+
# 4b. Now mine 288 more blocks and deliver; all should be processed but
213+
# the last (height-too-high) on node0. Node1 should process the tip if
214+
# we give it the headers chain leading to the tip.
215+
tips = blocks_h3
216+
headers_message = msg_headers()
217+
all_blocks = [] # node0's blocks
218+
for j in xrange(2):
219+
for i in xrange(288):
220+
next_block = create_block(tips[j].sha256, create_coinbase(), tips[j].nTime+1)
221+
next_block.solve()
222+
if j==0:
223+
test_node.send_message(msg_block(next_block))
224+
all_blocks.append(next_block)
225+
else:
226+
headers_message.headers.append(CBlockHeader(next_block))
227+
tips[j] = next_block
228+
229+
time.sleep(2)
230+
for x in all_blocks:
231+
try:
232+
self.nodes[0].getblock(x.hash)
233+
if x == all_blocks[287]:
234+
raise AssertionError("Unrequested block too far-ahead should have been ignored")
235+
except:
236+
if x == all_blocks[287]:
237+
print "Unrequested block too far-ahead not processed"
238+
else:
239+
raise AssertionError("Unrequested block with more work should have been accepted")
240+
241+
headers_message.headers.pop() # Ensure the last block is unrequested
242+
white_node.send_message(headers_message) # Send headers leading to tip
243+
white_node.send_message(msg_block(tips[1])) # Now deliver the tip
244+
try:
245+
white_node.sync_with_ping()
246+
self.nodes[1].getblock(tips[1].hash)
247+
print "Unrequested block far ahead of tip accepted from whitelisted peer"
248+
except:
249+
raise AssertionError("Unrequested block from whitelisted peer not accepted")
250+
185251
# 5. Test handling of unrequested block on the node that didn't process
186252
# Should still not be processed (even though it has a child that has more
187253
# work).
@@ -192,7 +258,7 @@ def run_test(self):
192258
# the node processes it and incorrectly advances the tip).
193259
# But this would be caught later on, when we verify that an inv triggers
194260
# a getdata request for this block.
195-
time.sleep(1)
261+
test_node.sync_with_ping()
196262
assert_equal(self.nodes[0].getblockcount(), 2)
197263
print "Unrequested block that would complete more-work chain was ignored"
198264

@@ -204,21 +270,20 @@ def run_test(self):
204270
test_node.last_getdata = None
205271
test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)]))
206272

207-
time.sleep(1)
273+
test_node.sync_with_ping()
208274
with mininode_lock:
209275
getdata = test_node.last_getdata
210276

211-
# Check that the getdata is for the right block
212-
assert_equal(len(getdata.inv), 1)
277+
# Check that the getdata includes the right block
213278
assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256)
214279
print "Inv at tip triggered getdata for unprocessed block"
215280

216281
# 7. Send the missing block for the third time (now it is requested)
217282
test_node.send_message(msg_block(blocks_h2f[0]))
218283

219-
time.sleep(1)
220-
assert_equal(self.nodes[0].getblockcount(), 3)
221-
print "Successfully reorged to length 3 chain from non-whitelisted peer"
284+
test_node.sync_with_ping()
285+
assert_equal(self.nodes[0].getblockcount(), 290)
286+
print "Successfully reorged to longer chain from non-whitelisted peer"
222287

223288
[ c.disconnect_node() for c in connections ]
224289

src/main.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,16 +2847,23 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex,
28472847

28482848
// Try to process all requested blocks that we don't have, but only
28492849
// process an unrequested block if it's new and has enough work to
2850-
// advance our tip.
2850+
// advance our tip, and isn't too many blocks ahead.
28512851
bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
28522852
bool fHasMoreWork = (chainActive.Tip() ? pindex->nChainWork > chainActive.Tip()->nChainWork : true);
2853+
// Blocks that are too out-of-order needlessly limit the effectiveness of
2854+
// pruning, because pruning will not delete block files that contain any
2855+
// blocks which are too close in height to the tip. Apply this test
2856+
// regardless of whether pruning is enabled; it should generally be safe to
2857+
// not process unrequested blocks.
2858+
bool fTooFarAhead = (pindex->nHeight > int(chainActive.Height() + MIN_BLOCKS_TO_KEEP));
28532859

28542860
// TODO: deal better with return value and error conditions for duplicate
28552861
// and unrequested blocks.
28562862
if (fAlreadyHave) return true;
28572863
if (!fRequested) { // If we didn't ask for it:
28582864
if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned
28592865
if (!fHasMoreWork) return true; // Don't process less-work chains
2866+
if (fTooFarAhead) return true; // Block height is too high
28602867
}
28612868

28622869
if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) {
@@ -4478,8 +4485,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
44784485
pfrom->AddInventoryKnown(inv);
44794486

44804487
CValidationState state;
4481-
// Process all blocks from whitelisted peers, even if not requested.
4482-
ProcessNewBlock(state, pfrom, &block, pfrom->fWhitelisted, NULL);
4488+
// Process all blocks from whitelisted peers, even if not requested,
4489+
// unless we're still syncing with the network.
4490+
// Such an unrequested block may still be processed, subject to the
4491+
// conditions in AcceptBlock().
4492+
bool forceProcessing = pfrom->fWhitelisted && !IsInitialBlockDownload();
4493+
ProcessNewBlock(state, pfrom, &block, forceProcessing, NULL);
44834494
int nDoS;
44844495
if (state.IsInvalid(nDoS)) {
44854496
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),

0 commit comments

Comments
 (0)