From 1fc4ec7bf224748d3d6271bffa23d121f015cbf3 Mon Sep 17 00:00:00 2001 From: mrbandrews Date: Tue, 29 Nov 2016 12:39:19 -0500 Subject: Add pruneblockchain RPC to enable manual block file pruning. --- qa/rpc-tests/pruning.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) (limited to 'qa') diff --git a/qa/rpc-tests/pruning.py b/qa/rpc-tests/pruning.py index 78b8938e4a..062bc8dda4 100755 --- a/qa/rpc-tests/pruning.py +++ b/qa/rpc-tests/pruning.py @@ -25,7 +25,7 @@ class PruneTest(BitcoinTestFramework): def __init__(self): super().__init__() self.setup_clean_chain = True - self.num_nodes = 3 + self.num_nodes = 5 # Cache for utxos, as the listunspent may take a long time later in the test self.utxo_cache_0 = [] @@ -43,10 +43,21 @@ class PruneTest(BitcoinTestFramework): self.nodes.append(start_node(2, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-prune=550"], timewait=900)) self.prunedir = self.options.tmpdir+"/node2/regtest/blocks/" + # Create node 3 to test manual pruning (it will be re-started with manual pruning later) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug=0","-maxreceivebuffer=20000","-blockmaxsize=999000"], timewait=900)) + self.manualdir = self.options.tmpdir+"/node3/regtest/blocks/" + + # Create node 4 to test wallet in prune mode, but do not connect + self.nodes.append(start_node(4, self.options.tmpdir, ["-debug=0", "-prune=550"])) + + # Determine default relay fee + self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"] + connect_nodes(self.nodes[0], 1) connect_nodes(self.nodes[1], 2) connect_nodes(self.nodes[2], 0) - sync_blocks(self.nodes[0:3]) + connect_nodes(self.nodes[0], 3) + sync_blocks(self.nodes[0:4]) def create_big_chain(self): # Start by creating some coinbases we can spend later @@ -57,7 +68,7 @@ class PruneTest(BitcoinTestFramework): for i in range(645): mine_large_block(self.nodes[0], self.utxo_cache_0) - sync_blocks(self.nodes[0:3]) + sync_blocks(self.nodes[0:4]) def test_height_min(self): if not os.path.isfile(self.prunedir+"blk00000.dat"): @@ -212,6 +223,93 @@ class PruneTest(BitcoinTestFramework): # Verify we can now have the data for a block previously pruned assert(self.nodes[2].getblock(self.forkhash)["height"] == self.forkheight) + def manual_test(self): + # at this point, node3 has 995 blocks and has not yet run in prune mode + self.nodes[3] = start_node(3, self.options.tmpdir, ["-debug=0"], timewait=900) + assert_raises_message(JSONRPCException, "not in prune mode", self.nodes[3].pruneblockchain, 500) + stop_node(self.nodes[3],3) + + # now re-start in manual pruning mode + self.nodes[3] = start_node(3, self.options.tmpdir, ["-debug=0","-prune=1"], timewait=900) + assert_equal(self.nodes[3].getblockcount(), 995) + + # should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000) + assert_raises_message(JSONRPCException, "Blockchain is too short for pruning", self.nodes[3].pruneblockchain, 500) + + # mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight) + self.nodes[3].generate(6) + + # negative and zero inputs should raise an exception + try: + self.nodes[3].pruneblockchain(-10) + raise AssertionError("pruneblockchain(-10) should have failed.") + except: + pass + + try: + self.nodes[3].pruneblockchain(0) + raise AssertionError("pruneblockchain(0) should have failed.") + except: + pass + + # height=100 too low to prune first block file so this is a no-op + self.nodes[3].pruneblockchain(100) + if not os.path.isfile(self.manualdir+"blk00000.dat"): + raise AssertionError("blk00000.dat is missing when should still be there") + + # height=500 should prune first file + self.nodes[3].pruneblockchain(500) + if os.path.isfile(self.manualdir+"blk00000.dat"): + raise AssertionError("blk00000.dat is still there, should be pruned by now") + if not os.path.isfile(self.manualdir+"blk00001.dat"): + raise AssertionError("blk00001.dat is missing when should still be there") + + # height=650 should prune second file + self.nodes[3].pruneblockchain(650) + if os.path.isfile(self.manualdir+"blk00001.dat"): + raise AssertionError("blk00001.dat is still there, should be pruned by now") + + # height=1000 should not prune anything more, because tip-288 is in blk00002.dat. + self.nodes[3].pruneblockchain(1000) + if not os.path.isfile(self.manualdir+"blk00002.dat"): + raise AssertionError("blk00002.dat is still there, should be pruned by now") + + # advance the tip so blk00002.dat and blk00003.dat can be pruned (the last 288 blocks should now be in blk00004.dat) + self.nodes[3].generate(288) + self.nodes[3].pruneblockchain(1000) + if os.path.isfile(self.manualdir+"blk00002.dat"): + raise AssertionError("blk00002.dat is still there, should be pruned by now") + if os.path.isfile(self.manualdir+"blk00003.dat"): + raise AssertionError("blk00003.dat is still there, should be pruned by now") + + # stop node, start back up with auto-prune at 550MB, make sure still runs + stop_node(self.nodes[3],3) + self.nodes[3] = start_node(3, self.options.tmpdir, ["-debug=0","-prune=550"], timewait=900) + + print("Success") + + def wallet_test(self): + # check that the pruning node's wallet is still in good shape + print("Stop and start pruning node to trigger wallet rescan") + try: + stop_node(self.nodes[2], 2) + start_node(2, self.options.tmpdir, ["-debug=1","-prune=550"]) + print("Success") + except Exception as detail: + raise AssertionError("Wallet test: unable to re-start the pruning node") + + # check that wallet loads loads successfully when restarting a pruned node after IBD. + # this was reported to fail in #7494. + print ("Syncing node 4 to test wallet") + connect_nodes(self.nodes[0], 4) + nds = [self.nodes[0], self.nodes[4]] + sync_blocks(nds) + try: + stop_node(self.nodes[4],4) #stop and start to trigger rescan + start_node(4, self.options.tmpdir, ["-debug=1","-prune=550"]) + print ("Success") + except Exception as detail: + raise AssertionError("Wallet test: unable to re-start node4") def run_test(self): print("Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)") @@ -226,6 +324,9 @@ class PruneTest(BitcoinTestFramework): # Start by mining a simple chain that all nodes have # N0=N1=N2 **...*(995) + # stop manual-pruning node with 995 blocks + stop_node(self.nodes[3],3) + print("Check that we haven't started pruning yet because we're below PruneAfterHeight") self.test_height_min() # Extend this chain past the PruneAfterHeight @@ -308,6 +409,12 @@ class PruneTest(BitcoinTestFramework): # # N1 doesn't change because 1033 on main chain (*) is invalid + print("Test manual pruning") + self.manual_test() + + print("Test wallet re-scan") + self.wallet_test() + print("Done") if __name__ == '__main__': -- cgit v1.2.3