Skip to content

Commit de08932

Browse files
committed
test: Update test for indices on pruned nodes
1 parent 825d198 commit de08932

File tree

1 file changed

+121
-43
lines changed

1 file changed

+121
-43
lines changed

test/functional/feature_blockfilterindex_prune.py

Lines changed: 121 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,155 @@
22
# Copyright (c) 2020-2021 The Bitcoin Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# 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."""
66
from test_framework.test_framework import BitcoinTestFramework
77
from test_framework.util import (
88
assert_equal,
99
assert_greater_than,
1010
assert_raises_rpc_error,
11+
p2p_port,
1112
)
1213

1314

14-
class FeatureBlockfilterindexPruneTest(BitcoinTestFramework):
15+
class FeatureIndexPruneTest(BitcoinTestFramework):
1516
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+
]
1824

1925
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()
2255

2356
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")
2561
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)
2869
self.sync_index(height=700)
2970

3071
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'])
4192

4293
# mine and sync index up to a height that will later be the pruneheight
4394
self.generate(self.nodes[0], 51)
4495
self.sync_index(height=751)
4596

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)
48106

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)
52108

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")
58117
self.sync_index(height=1500)
59118

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)
66142

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)
72147

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()
75153

76154

77155
if __name__ == '__main__':
78-
FeatureBlockfilterindexPruneTest().main()
156+
FeatureIndexPruneTest().main()

0 commit comments

Comments
 (0)