Skip to content

Commit 675d2fe

Browse files
committed
Merge pull request #6224
59b49cd Eliminate signed/unsigned comparison warning (Suhas Daftuar) 04b5d23 Replace sleep with syncing using pings (Suhas Daftuar) 6b1066f Ignore whitelisting during IBD for unrequested blocks. (Suhas Daftuar) bfc30b3 Ignore unrequested blocks too far ahead of tip (Suhas Daftuar)
2 parents 7cbed7f + 59b49cd commit 675d2fe

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
@@ -2734,16 +2734,23 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex,
27342734

27352735
// Try to process all requested blocks that we don't have, but only
27362736
// process an unrequested block if it's new and has enough work to
2737-
// advance our tip.
2737+
// advance our tip, and isn't too many blocks ahead.
27382738
bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
27392739
bool fHasMoreWork = (chainActive.Tip() ? pindex->nChainWork > chainActive.Tip()->nChainWork : true);
2740+
// Blocks that are too out-of-order needlessly limit the effectiveness of
2741+
// pruning, because pruning will not delete block files that contain any
2742+
// blocks which are too close in height to the tip. Apply this test
2743+
// regardless of whether pruning is enabled; it should generally be safe to
2744+
// not process unrequested blocks.
2745+
bool fTooFarAhead = (pindex->nHeight > int(chainActive.Height() + MIN_BLOCKS_TO_KEEP));
27402746

27412747
// TODO: deal better with return value and error conditions for duplicate
27422748
// and unrequested blocks.
27432749
if (fAlreadyHave) return true;
27442750
if (!fRequested) { // If we didn't ask for it:
27452751
if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned
27462752
if (!fHasMoreWork) return true; // Don't process less-work chains
2753+
if (fTooFarAhead) return true; // Block height is too high
27472754
}
27482755

27492756
if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) {
@@ -4368,8 +4375,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
43684375
pfrom->AddInventoryKnown(inv);
43694376

43704377
CValidationState state;
4371-
// Process all blocks from whitelisted peers, even if not requested.
4372-
ProcessNewBlock(state, pfrom, &block, pfrom->fWhitelisted, NULL);
4378+
// Process all blocks from whitelisted peers, even if not requested,
4379+
// unless we're still syncing with the network.
4380+
// Such an unrequested block may still be processed, subject to the
4381+
// conditions in AcceptBlock().
4382+
bool forceProcessing = pfrom->fWhitelisted && !IsInitialBlockDownload();
4383+
ProcessNewBlock(state, pfrom, &block, forceProcessing, NULL);
43734384
int nDoS;
43744385
if (state.IsInvalid(nDoS)) {
43754386
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),

0 commit comments

Comments
 (0)