Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_pruning.py
Show First 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | def create_big_chain(self): | ||||
mine_big_block(self.nodes[0], self.utxo_cache_0) | mine_big_block(self.nodes[0], self.utxo_cache_0) | ||||
sync_blocks(self.nodes[0:5]) | sync_blocks(self.nodes[0:5]) | ||||
def test_height_min(self): | def test_height_min(self): | ||||
if not os.path.isfile(self.prunedir + "blk00000.dat"): | if not os.path.isfile(self.prunedir + "blk00000.dat"): | ||||
raise AssertionError("blk00000.dat is missing, pruning too early") | raise AssertionError("blk00000.dat is missing, pruning too early") | ||||
self.log.info("Success") | self.log.info("Success") | ||||
self.log.info("Though we're already using more than 550MiB, current usage: %d" % | self.log.info("Though we're already using more than 550MiB, current usage: {}".format( | ||||
calc_usage(self.prunedir)) | calc_usage(self.prunedir))) | ||||
self.log.info( | self.log.info( | ||||
"Mining 25 more blocks should cause the first block file to be pruned") | "Mining 25 more blocks should cause the first block file to be pruned") | ||||
# Pruning doesn't run until we're allocating another chunk, 20 full | # Pruning doesn't run until we're allocating another chunk, 20 full | ||||
# blocks past the height cutoff will ensure this | # blocks past the height cutoff will ensure this | ||||
for i in range(25): | for i in range(25): | ||||
mine_big_block(self.nodes[0], self.utxo_cache_0) | mine_big_block(self.nodes[0], self.utxo_cache_0) | ||||
waitstart = time.time() | waitstart = time.time() | ||||
while os.path.isfile(self.prunedir + "blk00000.dat"): | while os.path.isfile(self.prunedir + "blk00000.dat"): | ||||
time.sleep(0.1) | time.sleep(0.1) | ||||
if time.time() - waitstart > 30: | if time.time() - waitstart > 30: | ||||
raise AssertionError( | raise AssertionError( | ||||
"blk00000.dat not pruned when it should be") | "blk00000.dat not pruned when it should be") | ||||
self.log.info("Success") | self.log.info("Success") | ||||
usage = calc_usage(self.prunedir) | usage = calc_usage(self.prunedir) | ||||
self.log.info("Usage should be below target: %d" % usage) | self.log.info("Usage should be below target: {}".format(usage)) | ||||
if (usage > 550): | if (usage > 550): | ||||
raise AssertionError("Pruning target not being met") | raise AssertionError("Pruning target not being met") | ||||
def create_chain_with_staleblocks(self): | def create_chain_with_staleblocks(self): | ||||
# Create stale blocks in manageable sized chunks | # Create stale blocks in manageable sized chunks | ||||
self.log.info( | self.log.info( | ||||
"Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds") | "Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds") | ||||
Show All 20 Lines | def create_chain_with_staleblocks(self): | ||||
mine_big_block(self.nodes[0], self.utxo_cache_0) | mine_big_block(self.nodes[0], self.utxo_cache_0) | ||||
# Create connections in the order so both nodes can see the reorg | # Create connections in the order so both nodes can see the reorg | ||||
# at the same time | # at the same time | ||||
connect_nodes(self.nodes[1], self.nodes[0]) | connect_nodes(self.nodes[1], self.nodes[0]) | ||||
connect_nodes(self.nodes[2], self.nodes[0]) | connect_nodes(self.nodes[2], self.nodes[0]) | ||||
sync_blocks(self.nodes[0:3]) | sync_blocks(self.nodes[0:3]) | ||||
self.log.info("Usage can be over target because of high stale rate: %d" % | self.log.info("Usage can be over target because of high stale rate: {}".format( | ||||
calc_usage(self.prunedir)) | calc_usage(self.prunedir))) | ||||
def reorg_test(self): | def reorg_test(self): | ||||
# Node 1 will mine a 300 block chain starting 287 blocks back from Node | # Node 1 will mine a 300 block chain starting 287 blocks back from Node | ||||
# 0 and Node 2's tip. This will cause Node 2 to do a reorg requiring | # 0 and Node 2's tip. This will cause Node 2 to do a reorg requiring | ||||
# 288 blocks of undo data to the reorg_test chain. Reboot node 1 to | # 288 blocks of undo data to the reorg_test chain. Reboot node 1 to | ||||
# clear its mempool (hopefully make the invalidate faster). Lower the | # clear its mempool (hopefully make the invalidate faster). Lower the | ||||
# block max size so we don't keep mining all our big mempool | # block max size so we don't keep mining all our big mempool | ||||
# transactions (from disconnected blocks) | # transactions (from disconnected blocks) | ||||
self.stop_node(1) | self.stop_node(1) | ||||
self.start_node(1, extra_args=[ | self.start_node(1, extra_args=[ | ||||
"-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", | "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", | ||||
"-disablesafemode", "-noparkdeepreorg", "-maxreorgdepth=-1"]) | "-disablesafemode", "-noparkdeepreorg", "-maxreorgdepth=-1"]) | ||||
height = self.nodes[1].getblockcount() | height = self.nodes[1].getblockcount() | ||||
self.log.info("Current block height: %d" % height) | self.log.info("Current block height: {}".format(height)) | ||||
invalidheight = height - 287 | invalidheight = height - 287 | ||||
badhash = self.nodes[1].getblockhash(invalidheight) | badhash = self.nodes[1].getblockhash(invalidheight) | ||||
self.log.info("Invalidating block %s at height %d" % | self.log.info("Invalidating block {} at height {}".format( | ||||
(badhash, invalidheight)) | badhash, invalidheight)) | ||||
self.nodes[1].invalidateblock(badhash) | self.nodes[1].invalidateblock(badhash) | ||||
# We've now switched to our previously mined-24 block fork on node 1, but thats not what we want. | # We've now switched to our previously mined-24 block fork on node 1, but thats not what we want. | ||||
# So invalidate that fork as well, until we're on the same chain as | # So invalidate that fork as well, until we're on the same chain as | ||||
# node 0/2 (but at an ancestor 288 blocks ago) | # node 0/2 (but at an ancestor 288 blocks ago) | ||||
mainchainhash = self.nodes[0].getblockhash(invalidheight - 1) | mainchainhash = self.nodes[0].getblockhash(invalidheight - 1) | ||||
curhash = self.nodes[1].getblockhash(invalidheight - 1) | curhash = self.nodes[1].getblockhash(invalidheight - 1) | ||||
while curhash != mainchainhash: | while curhash != mainchainhash: | ||||
self.nodes[1].invalidateblock(curhash) | self.nodes[1].invalidateblock(curhash) | ||||
curhash = self.nodes[1].getblockhash(invalidheight - 1) | curhash = self.nodes[1].getblockhash(invalidheight - 1) | ||||
assert(self.nodes[1].getblockcount() == invalidheight - 1) | assert(self.nodes[1].getblockcount() == invalidheight - 1) | ||||
self.log.info("New best height: %d" % self.nodes[1].getblockcount()) | self.log.info("New best height: {}".format( | ||||
self.nodes[1].getblockcount())) | |||||
# Reboot node1 to clear those giant tx's from mempool | # Reboot node1 to clear those giant tx's from mempool | ||||
self.stop_node(1) | self.stop_node(1) | ||||
self.start_node(1, extra_args=[ | self.start_node(1, extra_args=[ | ||||
"-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", | "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", | ||||
"-disablesafemode", "-noparkdeepreorg", "-maxreorgdepth=-1"]) | "-disablesafemode", "-noparkdeepreorg", "-maxreorgdepth=-1"]) | ||||
self.log.info("Generating new longer chain of 300 more blocks") | self.log.info("Generating new longer chain of 300 more blocks") | ||||
self.nodes[1].generate(300) | self.nodes[1].generate(300) | ||||
self.log.info("Reconnect nodes") | self.log.info("Reconnect nodes") | ||||
connect_nodes(self.nodes[0], self.nodes[1]) | connect_nodes(self.nodes[0], self.nodes[1]) | ||||
connect_nodes(self.nodes[2], self.nodes[1]) | connect_nodes(self.nodes[2], self.nodes[1]) | ||||
sync_blocks(self.nodes[0:3], timeout=120) | sync_blocks(self.nodes[0:3], timeout=120) | ||||
self.log.info("Verify height on node 2: %d" % | self.log.info("Verify height on node 2: {}".format( | ||||
self.nodes[2].getblockcount()) | self.nodes[2].getblockcount())) | ||||
self.log.info("Usage possibly still high bc of stale blocks in block files: %d" % | self.log.info("Usage possibly still high bc of stale blocks in block files: {}".format( | ||||
calc_usage(self.prunedir)) | calc_usage(self.prunedir))) | ||||
self.log.info( | self.log.info( | ||||
"Mine 220 more blocks so we have requisite history (some blocks will be big and cause pruning of previous chain)") | "Mine 220 more blocks so we have requisite history (some blocks will be big and cause pruning of previous chain)") | ||||
# Get node0's wallet transactions back in its mempool, to avoid the | # Get node0's wallet transactions back in its mempool, to avoid the | ||||
# mined blocks from being too small. | # mined blocks from being too small. | ||||
self.nodes[0].resendwallettransactions() | self.nodes[0].resendwallettransactions() | ||||
for i in range(22): | for i in range(22): | ||||
# This can be slow, so do this in multiple RPC calls to avoid | # This can be slow, so do this in multiple RPC calls to avoid | ||||
# RPC timeouts. | # RPC timeouts. | ||||
# node 0 has many large tx's in its mempool from the disconnects | # node 0 has many large tx's in its mempool from the disconnects | ||||
self.nodes[0].generate(10) | self.nodes[0].generate(10) | ||||
sync_blocks(self.nodes[0:3], timeout=300) | sync_blocks(self.nodes[0:3], timeout=300) | ||||
usage = calc_usage(self.prunedir) | usage = calc_usage(self.prunedir) | ||||
self.log.info("Usage should be below target: %d" % usage) | self.log.info("Usage should be below target: {}".format(usage)) | ||||
if (usage > 550): | if (usage > 550): | ||||
raise AssertionError("Pruning target not being met") | raise AssertionError("Pruning target not being met") | ||||
return invalidheight, badhash | return invalidheight, badhash | ||||
def reorg_back(self): | def reorg_back(self): | ||||
# Verify that a block on the old main chain fork has been pruned away | # Verify that a block on the old main chain fork has been pruned away | ||||
assert_raises_rpc_error( | assert_raises_rpc_error( | ||||
-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash) | -1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash) | ||||
self.log.info("Will need to redownload block %d" % self.forkheight) | self.log.info( | ||||
"Will need to redownload block {}".format(self.forkheight)) | |||||
# Verify that we have enough history to reorg back to the fork point. | # Verify that we have enough history to reorg back to the fork point. | ||||
# Although this is more than 288 blocks, because this chain was written | # Although this is more than 288 blocks, because this chain was written | ||||
# more recently and only its other 299 small and 220 large block are in | # more recently and only its other 299 small and 220 large block are in | ||||
# the block files after it, its expected to still be retained. | # the block files after it, its expected to still be retained. | ||||
self.nodes[2].getblock(self.nodes[2].getblockhash(self.forkheight)) | self.nodes[2].getblock(self.nodes[2].getblockhash(self.forkheight)) | ||||
first_reorg_height = self.nodes[2].getblockcount() | first_reorg_height = self.nodes[2].getblockcount() | ||||
Show All 10 Lines | def reorg_back(self): | ||||
# because it has all the block data. However it must mine enough blocks | # because it has all the block data. However it must mine enough blocks | ||||
# to have a more work chain than the reorg_test chain in order to | # to have a more work chain than the reorg_test chain in order to | ||||
# trigger node 2's block download logic. At this point node 2 is within | # trigger node 2's block download logic. At this point node 2 is within | ||||
# 288 blocks of the fork point so it will preserve its ability to | # 288 blocks of the fork point so it will preserve its ability to | ||||
# reorg. | # reorg. | ||||
if self.nodes[2].getblockcount() < self.mainchainheight: | if self.nodes[2].getblockcount() < self.mainchainheight: | ||||
blocks_to_mine = first_reorg_height + 1 - self.mainchainheight | blocks_to_mine = first_reorg_height + 1 - self.mainchainheight | ||||
self.log.info( | self.log.info( | ||||
"Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: %d" % blocks_to_mine) | "Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: {}".format( | ||||
blocks_to_mine)) | |||||
self.nodes[0].invalidateblock(curchainhash) | self.nodes[0].invalidateblock(curchainhash) | ||||
assert(self.nodes[0].getblockcount() == self.mainchainheight) | assert(self.nodes[0].getblockcount() == self.mainchainheight) | ||||
assert(self.nodes[0].getbestblockhash() == self.mainchainhash2) | assert(self.nodes[0].getbestblockhash() == self.mainchainhash2) | ||||
goalbesthash = self.nodes[0].generate(blocks_to_mine)[-1] | goalbesthash = self.nodes[0].generate(blocks_to_mine)[-1] | ||||
goalbestheight = first_reorg_height + 1 | goalbestheight = first_reorg_height + 1 | ||||
self.log.info( | self.log.info( | ||||
"Verify node 2 reorged back to the main chain, some blocks of which it had to redownload") | "Verify node 2 reorged back to the main chain, some blocks of which it had to redownload") | ||||
▲ Show 20 Lines • Show All 257 Lines • Show Last 20 Lines |