|
2 | 2 | # Copyright (c) 2020-2021 The Bitcoin Core developers
|
3 | 3 | # Distributed under the MIT software license, see the accompanying
|
4 | 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
5 |
| -"""Test blockfilterindex in conjunction with prune.""" |
| 5 | +"""Test indices in conjunction with prune.""" |
6 | 6 | from test_framework.test_framework import BitcoinTestFramework
|
7 | 7 | from test_framework.util import (
|
8 | 8 | assert_equal,
|
9 | 9 | assert_greater_than,
|
10 | 10 | assert_raises_rpc_error,
|
| 11 | + p2p_port, |
11 | 12 | )
|
12 | 13 |
|
13 | 14 |
|
14 |
| -class FeatureBlockfilterindexPruneTest(BitcoinTestFramework): |
| 15 | +class FeatureIndexPruneTest(BitcoinTestFramework): |
15 | 16 | def set_test_params(self):
|
16 |
| - self.num_nodes = 1 |
17 |
| - self.extra_args = [["-fastprune", "-prune=1", "-blockfilterindex=1"]] |
| 17 | + self.num_nodes = 4 |
| 18 | + self.extra_args = [ |
| 19 | + ["-fastprune", "-prune=1", "-blockfilterindex=1"], |
| 20 | + ["-fastprune", "-prune=1", "-coinstatsindex=1"], |
| 21 | + ["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"], |
| 22 | + [] |
| 23 | + ] |
18 | 24 |
|
19 | 25 | def sync_index(self, height):
|
20 |
| - expected = {'basic block filter index': {'synced': True, 'best_block_height': height}} |
21 |
| - self.wait_until(lambda: self.nodes[0].getindexinfo() == expected) |
| 26 | + expected_filter = { |
| 27 | + 'basic block filter index': {'synced': True, 'best_block_height': height}, |
| 28 | + } |
| 29 | + self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter) |
| 30 | + |
| 31 | + expected_stats = { |
| 32 | + 'coinstatsindex': {'synced': True, 'best_block_height': height} |
| 33 | + } |
| 34 | + self.wait_until(lambda: self.nodes[1].getindexinfo() == expected_stats) |
| 35 | + |
| 36 | + expected = {**expected_filter, **expected_stats} |
| 37 | + self.wait_until(lambda: self.nodes[2].getindexinfo() == expected) |
| 38 | + |
| 39 | + def reconnect_nodes(self): |
| 40 | + self.connect_nodes(0,1) |
| 41 | + self.connect_nodes(0,2) |
| 42 | + self.connect_nodes(0,3) |
| 43 | + |
| 44 | + def mine_batches(self, blocks): |
| 45 | + n = blocks // 250 |
| 46 | + for _ in range(n): |
| 47 | + self.generate(self.nodes[0], 250) |
| 48 | + self.generate(self.nodes[0], blocks % 250) |
| 49 | + self.sync_blocks() |
| 50 | + |
| 51 | + def restart_without_indices(self): |
| 52 | + for i in range(3): |
| 53 | + self.restart_node(i, extra_args=["-fastprune", "-prune=1"]) |
| 54 | + self.reconnect_nodes() |
22 | 55 |
|
23 | 56 | def run_test(self):
|
24 |
| - self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned") |
| 57 | + filter_nodes = [self.nodes[0], self.nodes[2]] |
| 58 | + stats_nodes = [self.nodes[1], self.nodes[2]] |
| 59 | + |
| 60 | + self.log.info("check if we can access blockfilters and coinstats when pruning is enabled but no blocks are actually pruned") |
25 | 61 | self.sync_index(height=200)
|
26 |
| - assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) |
27 |
| - self.generate(self.nodes[0], 500) |
| 62 | + tip = self.nodes[0].getbestblockhash() |
| 63 | + for node in filter_nodes: |
| 64 | + assert_greater_than(len(node.getblockfilter(tip)['filter']), 0) |
| 65 | + for node in stats_nodes: |
| 66 | + assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']) |
| 67 | + |
| 68 | + self.mine_batches(500) |
28 | 69 | self.sync_index(height=700)
|
29 | 70 |
|
30 | 71 | self.log.info("prune some blocks")
|
31 |
| - pruneheight = self.nodes[0].pruneblockchain(400) |
32 |
| - # the prune heights used here and below are magic numbers that are determined by the |
33 |
| - # thresholds at which block files wrap, so they depend on disk serialization and default block file size. |
34 |
| - assert_equal(pruneheight, 249) |
35 |
| - |
36 |
| - self.log.info("check if we can access the tips blockfilter when we have pruned some blocks") |
37 |
| - assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) |
38 |
| - |
39 |
| - self.log.info("check if we can access the blockfilter of a pruned block") |
40 |
| - assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0) |
| 72 | + for node in self.nodes[:2]: |
| 73 | + with node.assert_debug_log(['limited pruning to height 689']): |
| 74 | + pruneheight_new = node.pruneblockchain(400) |
| 75 | + # the prune heights used here and below are magic numbers that are determined by the |
| 76 | + # thresholds at which block files wrap, so they depend on disk serialization and default block file size. |
| 77 | + assert_equal(pruneheight_new, 249) |
| 78 | + |
| 79 | + self.log.info("check if we can access the tips blockfilter and coinstats when we have pruned some blocks") |
| 80 | + tip = self.nodes[0].getbestblockhash() |
| 81 | + for node in filter_nodes: |
| 82 | + assert_greater_than(len(node.getblockfilter(tip)['filter']), 0) |
| 83 | + for node in stats_nodes: |
| 84 | + assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']) |
| 85 | + |
| 86 | + self.log.info("check if we can access the blockfilter and coinstats of a pruned block") |
| 87 | + height_hash = self.nodes[0].getblockhash(2) |
| 88 | + for node in filter_nodes: |
| 89 | + assert_greater_than(len(node.getblockfilter(height_hash)['filter']), 0) |
| 90 | + for node in stats_nodes: |
| 91 | + assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash']) |
41 | 92 |
|
42 | 93 | # mine and sync index up to a height that will later be the pruneheight
|
43 | 94 | self.generate(self.nodes[0], 51)
|
44 | 95 | self.sync_index(height=751)
|
45 | 96 |
|
46 |
| - self.log.info("start node without blockfilterindex") |
47 |
| - self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) |
| 97 | + self.restart_without_indices() |
| 98 | + |
| 99 | + self.log.info("make sure trying to access the indices throws errors") |
| 100 | + for node in filter_nodes: |
| 101 | + msg = "Index is not enabled for filtertype basic" |
| 102 | + assert_raises_rpc_error(-1, msg, node.getblockfilter, height_hash) |
| 103 | + for node in stats_nodes: |
| 104 | + msg = "Querying specific block heights requires coinstatsindex" |
| 105 | + assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", height_hash) |
48 | 106 |
|
49 |
| - self.log.info("make sure accessing the blockfilters throws an error") |
50 |
| - assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2)) |
51 |
| - self.generate(self.nodes[0], 749) |
| 107 | + self.mine_batches(749) |
52 | 108 |
|
53 |
| - self.log.info("prune exactly up to the blockfilterindexes best block while blockfilters are disabled") |
54 |
| - pruneheight_2 = self.nodes[0].pruneblockchain(1000) |
55 |
| - assert_equal(pruneheight_2, 751) |
56 |
| - self.restart_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"]) |
57 |
| - self.log.info("make sure that we can continue with the partially synced index after having pruned up to the index height") |
| 109 | + self.log.info("prune exactly up to the indices best blocks while the indices are disabled") |
| 110 | + for i in range(3): |
| 111 | + pruneheight_2 = self.nodes[i].pruneblockchain(1000) |
| 112 | + assert_equal(pruneheight_2, 751) |
| 113 | + # Restart the nodes again with the indices activated |
| 114 | + self.restart_node(i, extra_args=self.extra_args[i]) |
| 115 | + |
| 116 | + self.log.info("make sure that we can continue with the partially synced indices after having pruned up to the index height") |
58 | 117 | self.sync_index(height=1500)
|
59 | 118 |
|
60 |
| - self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled") |
61 |
| - self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) |
62 |
| - self.generate(self.nodes[0], 1000) |
63 |
| - pruneheight_3 = self.nodes[0].pruneblockchain(2000) |
64 |
| - assert_greater_than(pruneheight_3, pruneheight_2) |
65 |
| - self.stop_node(0) |
| 119 | + self.log.info("prune further than the indices best blocks while the indices are disabled") |
| 120 | + self.restart_without_indices() |
| 121 | + self.mine_batches(1000) |
| 122 | + |
| 123 | + for i in range(3): |
| 124 | + pruneheight_3 = self.nodes[i].pruneblockchain(2000) |
| 125 | + assert_greater_than(pruneheight_3, pruneheight_2) |
| 126 | + self.stop_node(i) |
| 127 | + |
| 128 | + self.log.info("make sure we get an init error when starting the nodes again with the indices") |
| 129 | + filter_msg = "Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" |
| 130 | + stats_msg = "Error: coinstatsindex best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" |
| 131 | + for i, msg in enumerate([filter_msg, stats_msg, filter_msg]): |
| 132 | + self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg) |
| 133 | + |
| 134 | + self.log.info("make sure the nodes start again with the indices and an additional -reindex arg") |
| 135 | + ip_port = "127.0.0.1:" + str(p2p_port(3)) |
| 136 | + for i in range(3): |
| 137 | + # The nodes need to be reconnected to the non-pruning node upon restart, otherwise they will be stuck |
| 138 | + restart_args = self.extra_args[i]+["-reindex", f"-connect={ip_port}"] |
| 139 | + self.restart_node(i, extra_args=restart_args) |
| 140 | + |
| 141 | + self.sync_blocks(timeout=300) |
66 | 142 |
|
67 |
| - self.log.info("make sure we get an init error when starting the node again with block filters") |
68 |
| - self.nodes[0].assert_start_raises_init_error( |
69 |
| - extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"], |
70 |
| - expected_msg="Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)", |
71 |
| - ) |
| 143 | + for node in self.nodes[:2]: |
| 144 | + with node.assert_debug_log(['limited pruning to height 2489']): |
| 145 | + pruneheight_new = node.pruneblockchain(2500) |
| 146 | + assert_equal(pruneheight_new, 2006) |
72 | 147 |
|
73 |
| - self.log.info("make sure the node starts again with the -reindex arg") |
74 |
| - self.start_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"]) |
| 148 | + self.log.info("ensure that prune locks don't prevent indices from failing in a reorg scenario") |
| 149 | + with self.nodes[0].assert_debug_log(['basic block filter index prune lock moved back to 2480']): |
| 150 | + self.nodes[3].invalidateblock(self.nodes[0].getblockhash(2480)) |
| 151 | + self.generate(self.nodes[3], 30) |
| 152 | + self.sync_blocks() |
75 | 153 |
|
76 | 154 |
|
77 | 155 | if __name__ == '__main__':
|
78 |
| - FeatureBlockfilterindexPruneTest().main() |
| 156 | + FeatureIndexPruneTest().main() |
0 commit comments