Changeset View
Changeset View
Standalone View
Standalone View
test/functional/pruning.py
Show All 23 Lines | |||||
TIMESTAMP_WINDOW = 2 * 60 * 60 | TIMESTAMP_WINDOW = 2 * 60 * 60 | ||||
def calc_usage(blockdir): | def calc_usage(blockdir): | ||||
return sum(os.path.getsize(blockdir + f) for f in os.listdir(blockdir) if os.path.isfile(blockdir + f)) / (1024. * 1024.) | return sum(os.path.getsize(blockdir + f) for f in os.listdir(blockdir) if os.path.isfile(blockdir + f)) / (1024. * 1024.) | ||||
class PruneTest(BitcoinTestFramework): | class PruneTest(BitcoinTestFramework): | ||||
def set_test_params(self): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 6 | self.num_nodes = 6 | ||||
# Create nodes 0 and 1 to mine. | # Create nodes 0 and 1 to mine. | ||||
# Create node 2 to test pruning. | # Create node 2 to test pruning. | ||||
self.full_node_default_args = ["-maxreceivebuffer=20000", "-blockmaxsize=999000", "-checkblocks=5", | self.full_node_default_args = ["-maxreceivebuffer=20000", "-blockmaxsize=999000", "-checkblocks=5", | ||||
"-limitdescendantcount=100", "-limitdescendantsize=5000", "-limitancestorcount=100", "-limitancestorsize=5000"] | "-limitdescendantcount=100", "-limitdescendantsize=5000", "-limitancestorcount=100", "-limitancestorsize=5000"] | ||||
# Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later) | # Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later) | ||||
Show All 12 Lines | def setup_network(self): | ||||
connect_nodes(self.nodes[0], 1) | connect_nodes(self.nodes[0], 1) | ||||
connect_nodes(self.nodes[1], 2) | connect_nodes(self.nodes[1], 2) | ||||
connect_nodes(self.nodes[2], 0) | connect_nodes(self.nodes[2], 0) | ||||
connect_nodes(self.nodes[0], 3) | connect_nodes(self.nodes[0], 3) | ||||
connect_nodes(self.nodes[0], 4) | connect_nodes(self.nodes[0], 4) | ||||
sync_blocks(self.nodes[0:5]) | sync_blocks(self.nodes[0:5]) | ||||
def setup_nodes(self): | |||||
self.add_nodes(self.num_nodes, self.extra_args, timewait=900) | |||||
self.start_nodes() | |||||
def create_big_chain(self): | def create_big_chain(self): | ||||
# Start by creating some coinbases we can spend later | # Start by creating some coinbases we can spend later | ||||
self.nodes[1].generate(200) | self.nodes[1].generate(200) | ||||
sync_blocks(self.nodes[0:2]) | sync_blocks(self.nodes[0:2]) | ||||
self.nodes[0].generate(150) | self.nodes[0].generate(150) | ||||
# Then mine enough full blocks to create more than 550MiB of data | # Then mine enough full blocks to create more than 550MiB of data | ||||
for i in range(645): | for i in range(645): | ||||
mine_large_block(self.nodes[0], self.utxo_cache_0) | mine_large_block(self.nodes[0], self.utxo_cache_0) | ||||
Show All 32 Lines | def create_chain_with_staleblocks(self): | ||||
"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") | ||||
for j in range(12): | for j in range(12): | ||||
# Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain | # Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain | ||||
# Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects | # Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects | ||||
# Stopping node 0 also clears its mempool, so it doesn't have | # Stopping node 0 also clears its mempool, so it doesn't have | ||||
# node1's transactions to accidentally mine | # node1's transactions to accidentally mine | ||||
self.stop_node(0) | self.stop_node(0) | ||||
self.nodes[0] = self.start_node( | self.start_node(0, extra_args=self.full_node_default_args) | ||||
0, self.options.tmpdir, self.full_node_default_args, timewait=900) | |||||
# Mine 24 blocks in node 1 | # Mine 24 blocks in node 1 | ||||
for i in range(24): | for i in range(24): | ||||
if j == 0: | if j == 0: | ||||
mine_large_block(self.nodes[1], self.utxo_cache_1) | mine_large_block(self.nodes[1], self.utxo_cache_1) | ||||
else: | else: | ||||
# Add node1's wallet transactions back to the mempool, to | # Add node1's wallet transactions back to the mempool, to | ||||
# avoid the mined blocks from being too small. | # avoid the mined blocks from being too small. | ||||
self.nodes[1].resendwallettransactions() | self.nodes[1].resendwallettransactions() | ||||
Show All 16 Lines | class PruneTest(BitcoinTestFramework): | ||||
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.nodes[1] = self.start_node(1, self.options.tmpdir, [ | self.start_node(1, extra_args=[ | ||||
"-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900) | "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"]) | ||||
height = self.nodes[1].getblockcount() | height = self.nodes[1].getblockcount() | ||||
self.log.info("Current block height: %d" % height) | self.log.info("Current block height: %d" % 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 %s at height %d" % | ||||
(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: %d" % 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.nodes[1] = self.start_node(1, self.options.tmpdir, [ | self.start_node(1, extra_args=[ | ||||
"-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900) | "-maxreceivebuffer=20000", "-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"]) | ||||
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], 1) | connect_nodes(self.nodes[0], 1) | ||||
connect_nodes(self.nodes[2], 1) | connect_nodes(self.nodes[2], 1) | ||||
sync_blocks(self.nodes[0:3], timeout=120) | sync_blocks(self.nodes[0:3], timeout=120) | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | def reorg_back(self): | ||||
raise AssertionError("Node 2 didn't reorg to proper height") | raise AssertionError("Node 2 didn't reorg to proper height") | ||||
assert(self.nodes[2].getbestblockhash() == goalbesthash) | assert(self.nodes[2].getbestblockhash() == goalbesthash) | ||||
# Verify we can now have the data for a block previously pruned | # Verify we can now have the data for a block previously pruned | ||||
assert(self.nodes[2].getblock( | assert(self.nodes[2].getblock( | ||||
self.forkhash)["height"] == self.forkheight) | self.forkhash)["height"] == self.forkheight) | ||||
def manual_test(self, node_number, use_timestamp): | def manual_test(self, node_number, use_timestamp): | ||||
# at this point, node has 995 blocks and has not yet run in prune mode | # at this point, node has 995 blocks and has not yet run in prune mode | ||||
node = self.nodes[node_number] = self.start_node( | self.start_node(node_number) | ||||
node_number, self.options.tmpdir, timewait=900) | node = self.nodes[node_number] | ||||
assert_equal(node.getblockcount(), 995) | assert_equal(node.getblockcount(), 995) | ||||
assert_raises_jsonrpc( | assert_raises_jsonrpc(-1, "not in prune mode", | ||||
-1, "not in prune mode", node.pruneblockchain, 500) | node.pruneblockchain, 500) | ||||
self.stop_node(node_number) | |||||
# now re-start in manual pruning mode | # now re-start in manual pruning mode | ||||
node = self.nodes[node_number] = self.start_node( | self.stop_node(node_number) | ||||
node_number, self.options.tmpdir, ["-prune=1"], timewait=900) | self.start_node(node_number, extra_args=["-prune=1"]) | ||||
node = self.nodes[node_number] | |||||
assert_equal(node.getblockcount(), 995) | assert_equal(node.getblockcount(), 995) | ||||
def height(index): | def height(index): | ||||
if use_timestamp: | if use_timestamp: | ||||
return node.getblockheader(node.getblockhash(index))["time"] + TIMESTAMP_WINDOW | return node.getblockheader(node.getblockhash(index))["time"] + TIMESTAMP_WINDOW | ||||
else: | else: | ||||
return index | return index | ||||
▲ Show 20 Lines • Show All 70 Lines • ▼ Show 20 Lines | def manual_test(self, node_number, use_timestamp): | ||||
"blk00002.dat is still there, should be pruned by now") | "blk00002.dat is still there, should be pruned by now") | ||||
if has_block(3): | if has_block(3): | ||||
raise AssertionError( | raise AssertionError( | ||||
"blk00003.dat is still there, should be pruned by now") | "blk00003.dat is still there, should be pruned by now") | ||||
# stop node, start back up with auto-prune at 550MB, make sure still | # stop node, start back up with auto-prune at 550MB, make sure still | ||||
# runs | # runs | ||||
self.stop_node(node_number) | self.stop_node(node_number) | ||||
self.nodes[node_number] = self.start_node( | self.start_node(node_number, extra_args=["-prune=550"]) | ||||
node_number, self.options.tmpdir, ["-prune=550"], timewait=900) | |||||
self.log.info("Success") | self.log.info("Success") | ||||
def wallet_test(self): | def wallet_test(self): | ||||
# check that the pruning node's wallet is still in good shape | # check that the pruning node's wallet is still in good shape | ||||
self.log.info("Stop and start pruning node to trigger wallet rescan") | self.log.info("Stop and start pruning node to trigger wallet rescan") | ||||
self.stop_node(2) | self.stop_node(2) | ||||
self.nodes[2] = self.start_node(2, self.options.tmpdir, ["-prune=550"]) | self.start_node(2, extra_args=["-prune=550"]) | ||||
self.log.info("Success") | self.log.info("Success") | ||||
# check that wallet loads loads successfully when restarting a pruned node after IBD. | # check that wallet loads loads successfully when restarting a pruned node after IBD. | ||||
# this was reported to fail in #7494. | # this was reported to fail in #7494. | ||||
self.log.info("Syncing node 5 to test wallet") | self.log.info("Syncing node 5 to test wallet") | ||||
connect_nodes(self.nodes[0], 5) | connect_nodes(self.nodes[0], 5) | ||||
nds = [self.nodes[0], self.nodes[5]] | nds = [self.nodes[0], self.nodes[5]] | ||||
sync_blocks(nds, wait=5, timeout=300) | sync_blocks(nds, wait=5, timeout=300) | ||||
self.stop_node(5) # stop and start to trigger rescan | self.stop_node(5) # stop and start to trigger rescan | ||||
self.nodes[5] = self.start_node(5, self.options.tmpdir, ["-prune=550"]) | self.start_node(5, extra_args=["-prune=550"]) | ||||
self.log.info("Success") | self.log.info("Success") | ||||
def run_test(self): | def run_test(self): | ||||
self.log.info( | self.log.info( | ||||
"Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)") | "Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)") | ||||
self.log.info("Mining a big blockchain of 995 blocks") | self.log.info("Mining a big blockchain of 995 blocks") | ||||
# Determine default relay fee | # Determine default relay fee | ||||
▲ Show 20 Lines • Show All 120 Lines • Show Last 20 Lines |