test: Update test for indices on pruned nodes

This commit is contained in:
Fabian Jahr committed 2022-04-25 23:22:00 +02:00
1 parent 825d19839b
commit de08932efa
1 file changed
+119 -41
+119 -41
View File
@@ -2,77 +2,155 @@
# Copyright (c) 2020-2021 The Bitcoin Core developers # Copyright (c) 2020-2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test blockfilterindex in conjunction with prune.""" """Test indices in conjunction with prune."""
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
assert_greater_than, assert_greater_than,
assert_raises_rpc_error, assert_raises_rpc_error,
p2p_port,
) )
class FeatureBlockfilterindexPruneTest(BitcoinTestFramework): class FeatureIndexPruneTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 4
self.extra_args = [["-fastprune", "-prune=1", "-blockfilterindex=1"]] self.extra_args = [
["-fastprune", "-prune=1", "-blockfilterindex=1"],
["-fastprune", "-prune=1", "-coinstatsindex=1"],
["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"],
[]
]
def sync_index(self, height): def sync_index(self, height):
expected = {'basic block filter index': {'synced': True, 'best_block_height': height}} expected_filter = {
self.wait_until(lambda: self.nodes[0].getindexinfo() == expected) 'basic block filter index': {'synced': True, 'best_block_height': height},
}
self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter)
expected_stats = {
'coinstatsindex': {'synced': True, 'best_block_height': height}
}
self.wait_until(lambda: self.nodes[1].getindexinfo() == expected_stats)
expected = {**expected_filter, **expected_stats}
self.wait_until(lambda: self.nodes[2].getindexinfo() == expected)
def reconnect_nodes(self):
self.connect_nodes(0,1)
self.connect_nodes(0,2)
self.connect_nodes(0,3)
def mine_batches(self, blocks):
n = blocks // 250
for _ in range(n):
self.generate(self.nodes[0], 250)
self.generate(self.nodes[0], blocks % 250)
self.sync_blocks()
def restart_without_indices(self):
for i in range(3):
self.restart_node(i, extra_args=["-fastprune", "-prune=1"])
self.reconnect_nodes()
def run_test(self): def run_test(self):
self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned") filter_nodes = [self.nodes[0], self.nodes[2]]
stats_nodes = [self.nodes[1], self.nodes[2]]
self.log.info("check if we can access blockfilters and coinstats when pruning is enabled but no blocks are actually pruned")
self.sync_index(height=200) self.sync_index(height=200)
assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) tip = self.nodes[0].getbestblockhash()
self.generate(self.nodes[0], 500) for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
for node in stats_nodes:
assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash'])
self.mine_batches(500)
self.sync_index(height=700) self.sync_index(height=700)
self.log.info("prune some blocks") self.log.info("prune some blocks")
pruneheight = self.nodes[0].pruneblockchain(400) for node in self.nodes[:2]:
# the prune heights used here and below are magic numbers that are determined by the with node.assert_debug_log(['limited pruning to height 689']):
# thresholds at which block files wrap, so they depend on disk serialization and default block file size. pruneheight_new = node.pruneblockchain(400)
assert_equal(pruneheight, 249) # the prune heights used here and below are magic numbers that are determined by the
# thresholds at which block files wrap, so they depend on disk serialization and default block file size.
assert_equal(pruneheight_new, 249)
self.log.info("check if we can access the tips blockfilter when we have pruned some blocks") self.log.info("check if we can access the tips blockfilter and coinstats when we have pruned some blocks")
assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) tip = self.nodes[0].getbestblockhash()
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(tip)['filter']), 0)
for node in stats_nodes:
assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash'])
self.log.info("check if we can access the blockfilter of a pruned block") self.log.info("check if we can access the blockfilter and coinstats of a pruned block")
assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0) height_hash = self.nodes[0].getblockhash(2)
for node in filter_nodes:
assert_greater_than(len(node.getblockfilter(height_hash)['filter']), 0)
for node in stats_nodes:
assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash'])
# mine and sync index up to a height that will later be the pruneheight # mine and sync index up to a height that will later be the pruneheight
self.generate(self.nodes[0], 51) self.generate(self.nodes[0], 51)
self.sync_index(height=751) self.sync_index(height=751)
self.log.info("start node without blockfilterindex") self.restart_without_indices()
self.restart_node(0, extra_args=["-fastprune", "-prune=1"])
self.log.info("make sure accessing the blockfilters throws an error") self.log.info("make sure trying to access the indices throws errors")
assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2)) for node in filter_nodes:
self.generate(self.nodes[0], 749) msg = "Index is not enabled for filtertype basic"
assert_raises_rpc_error(-1, msg, node.getblockfilter, height_hash)
for node in stats_nodes:
msg = "Querying specific block heights requires coinstatsindex"
assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", height_hash)
self.log.info("prune exactly up to the blockfilterindexes best block while blockfilters are disabled") self.mine_batches(749)
pruneheight_2 = self.nodes[0].pruneblockchain(1000)
assert_equal(pruneheight_2, 751) self.log.info("prune exactly up to the indices best blocks while the indices are disabled")
self.restart_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"]) for i in range(3):
self.log.info("make sure that we can continue with the partially synced index after having pruned up to the index height") pruneheight_2 = self.nodes[i].pruneblockchain(1000)
assert_equal(pruneheight_2, 751)
# Restart the nodes again with the indices activated
self.restart_node(i, extra_args=self.extra_args[i])
self.log.info("make sure that we can continue with the partially synced indices after having pruned up to the index height")
self.sync_index(height=1500) self.sync_index(height=1500)
self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled") self.log.info("prune further than the indices best blocks while the indices are disabled")
self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) self.restart_without_indices()
self.generate(self.nodes[0], 1000) self.mine_batches(1000)
pruneheight_3 = self.nodes[0].pruneblockchain(2000)
assert_greater_than(pruneheight_3, pruneheight_2)
self.stop_node(0)
self.log.info("make sure we get an init error when starting the node again with block filters") for i in range(3):
self.nodes[0].assert_start_raises_init_error( pruneheight_3 = self.nodes[i].pruneblockchain(2000)
extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"], assert_greater_than(pruneheight_3, pruneheight_2)
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)", self.stop_node(i)
)
self.log.info("make sure the node starts again with the -reindex arg") self.log.info("make sure we get an init error when starting the nodes again with the indices")
self.start_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"]) 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)"
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)"
for i, msg in enumerate([filter_msg, stats_msg, filter_msg]):
self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg)
self.log.info("make sure the nodes start again with the indices and an additional -reindex arg")
ip_port = "127.0.0.1:" + str(p2p_port(3))
for i in range(3):
# The nodes need to be reconnected to the non-pruning node upon restart, otherwise they will be stuck
restart_args = self.extra_args[i]+["-reindex", f"-connect={ip_port}"]
self.restart_node(i, extra_args=restart_args)
self.sync_blocks(timeout=300)
for node in self.nodes[:2]:
with node.assert_debug_log(['limited pruning to height 2489']):
pruneheight_new = node.pruneblockchain(2500)
assert_equal(pruneheight_new, 2006)
self.log.info("ensure that prune locks don't prevent indices from failing in a reorg scenario")
with self.nodes[0].assert_debug_log(['basic block filter index prune lock moved back to 2480']):
self.nodes[3].invalidateblock(self.nodes[0].getblockhash(2480))
self.generate(self.nodes[3], 30)
self.sync_blocks()
if __name__ == '__main__': if __name__ == '__main__':
FeatureBlockfilterindexPruneTest().main() FeatureIndexPruneTest().main()