Skip to content

Commit

Permalink
Merge bitcoin/bitcoin#31468: test: Avoid intermittent error in assert…
Browse files Browse the repository at this point in the history
…_equal(pruneheight_new, 248)

fa0998f test: Avoid intermittent error in assert_equal(pruneheight_new, 248) (MarcoFalke)

Pull request description:

  Fixes bitcoin/bitcoin#31446

  The test uses the P2P network to sync blocks, which has no inherent guarantee that the blocks are sent and received in the right order, assuming the headers are received first.

  This can mean that the first block file is flushed with block at height 249 and block at height 248 is added to the second file. In the log it looks like: `Leaving block file 0: CBlockFileInfo(blocks=249, size=65319, heights=0...249, time=2011-02-02...2024-12-03) (onto 1) (height 248)`. The test assumes that the height of the last pruned block in the first file is 248, expecting it to look like: `Leaving block file 0: CBlockFileInfo(blocks=249, size=65319, heights=0...248, time=2011-02-02...2024-12-09) (onto 1) (height 249) `.

  Fix the issue by using a linear dumb sync.

ACKs for top commit:
  achow101:
    ACK fa0998f
  mzumsande:
    Code Review ACK fa0998f
  i-am-yuvi:
    Code Review ACK fa0998f
  fjahr:
    Code review ACK fa0998f

Tree-SHA512: 59cb4317be6cf9012c9bf7a3e9f5ba96b8b114b30bd2ac42af4fe742cd26a634d685b075f04a84bd782b2a43a342d75bb20a042bd82ad2831dbf844d39517ca2
  • Loading branch information
achow101 committed Dec 30, 2024
2 parents 69e35f5 + fa0998f commit ba0cb7d
Showing 1 changed file with 25 additions and 24 deletions.
49 changes: 25 additions & 24 deletions test/functional/feature_index_prune.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2022 The Bitcoin Core developers
# Copyright (c) 2020-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test indices in conjunction with prune."""
import concurrent.futures
import os
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
Expand All @@ -19,9 +20,25 @@ def set_test_params(self):
["-fastprune", "-prune=1", "-blockfilterindex=1"],
["-fastprune", "-prune=1", "-coinstatsindex=1"],
["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"],
[]
[],
]

def setup_network(self):
self.setup_nodes() # No P2P connection, so that linear_sync works

def linear_sync(self, node_from, *, height_from=None):
# Linear sync over RPC, because P2P sync may not be linear
to_height = node_from.getblockcount()
if height_from is None:
height_from = min([n.getblockcount() for n in self.nodes]) + 1
with concurrent.futures.ThreadPoolExecutor(max_workers=self.num_nodes) as rpc_threads:
for i in range(height_from, to_height + 1):
b = node_from.getblock(blockhash=node_from.getblockhash(i), verbosity=0)
list(rpc_threads.map(lambda n: n.submitblock(b), self.nodes))

def generate(self, node, num_blocks, sync_fun=None):
return super().generate(node, num_blocks, sync_fun=sync_fun or (lambda: self.linear_sync(node)))

def sync_index(self, height):
expected_filter = {
'basic block filter index': {'synced': True, 'best_block_height': height},
Expand All @@ -36,22 +53,9 @@ def sync_index(self, height):
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):
filter_nodes = [self.nodes[0], self.nodes[2]]
Expand All @@ -65,7 +69,7 @@ def run_test(self):
for node in stats_nodes:
assert node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']

self.mine_batches(500)
self.generate(self.nodes[0], 500)
self.sync_index(height=700)

self.log.info("prune some blocks")
Expand Down Expand Up @@ -104,7 +108,7 @@ def run_test(self):
msg = "Querying specific block heights requires coinstatsindex"
assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", height_hash)

self.mine_batches(749)
self.generate(self.nodes[0], 749)

self.log.info("prune exactly up to the indices best blocks while the indices are disabled")
for i in range(3):
Expand All @@ -118,7 +122,7 @@ def run_test(self):

self.log.info("prune further than the indices best blocks while the indices are disabled")
self.restart_without_indices()
self.mine_batches(1000)
self.generate(self.nodes[0], 1000)

for i in range(3):
pruneheight_3 = self.nodes[i].pruneblockchain(2000)
Expand All @@ -134,12 +138,10 @@ def run_test(self):

self.log.info("make sure the nodes start again with the indices and an additional -reindex arg")
for i in range(3):
restart_args = self.extra_args[i]+["-reindex"]
restart_args = self.extra_args[i] + ["-reindex"]
self.restart_node(i, extra_args=restart_args)
# The nodes need to be reconnected to the non-pruning node upon restart, otherwise they will be stuck
self.connect_nodes(i, 3)

self.sync_blocks(timeout=300)
self.linear_sync(self.nodes[3])
self.sync_index(height=2500)

for node in self.nodes[:2]:
Expand All @@ -150,8 +152,7 @@ def run_test(self):
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()
self.generate(self.nodes[3], 30, sync_fun=lambda: self.linear_sync(self.nodes[3], height_from=2480))


if __name__ == '__main__':
Expand Down

0 comments on commit ba0cb7d

Please sign in to comment.