test: Indices can not start based on block data without undo data
What changed, and why it matters
This commit only adds a new test to Bitcoin Core. It checks that two optional index features (block filter index and coinstatsindex) cannot be started on a pruned node even if the missing raw blocks are fetched from a peer, because those indexes also need 'undo data' that getblockfrompeer does not provide. There is no product code change and no security fix.
No action required; this is a test-only commit documenting expected behavior. Reviewers may optionally verify the new test passes in CI.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds a functional test in test/functional/feature_index_prune.py. It introduces a helper to send JSON-RPC batch requests and a check_for_block helper, then extends run_test to restart pruned nodes, fetch missing block hashes via getblockfrompeer, wait for the blocks to arrive, and assert that the nodes still fail to start with the index-related init errors. This confirms an existing design limitation: block filter index and coinstatsindex require undo/rev data, which getblockfrompeer does not supply. No consensus, networking, or wallet code is modified.
Changed components
test/functional/feature_index_prune.pyInspect captured patch +48 / −0
diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py
index f552ee89..49520be0 100755
--- a/test/functional/feature_index_prune.py
+++ b/test/functional/feature_index_prune.py
@@ -5,13 +5,28 @@
"""Test indices in conjunction with prune."""
import concurrent.futures
import os
+from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.test_node import TestNode
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
)
+from typing import List, Any
+
+def send_batch_request(node: TestNode, method: str, params: List[Any]) -> List[Any]:
+ """Send batch request and parse all results"""
+ data = [{"method": method, "params": p} for p in params]
+ response = node.batch(data)
+ result = []
+ for item in response:
+ assert item["error"] is None, item["error"]
+ result.append(item["result"])
+
+ return result
+
class FeatureIndexPruneTest(BitcoinTestFramework):
def set_test_params(self):
@@ -57,6 +72,13 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
for i in range(3):
self.restart_node(i, extra_args=["-fastprune", "-prune=1"])
+ def check_for_block(self, node, hash):
+ try:
+ self.nodes[node].getblock(hash)
+ return True
+ except JSONRPCException:
+ return False
+
def run_test(self):
filter_nodes = [self.nodes[0], self.nodes[2]]
stats_nodes = [self.nodes[1], self.nodes[2]]
@@ -136,6 +158,32 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
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+end_msg)
+ self.log.info("fetching the missing blocks with getblockfrompeer doesn't work for block filter index and coinstatsindex")
+ # Only checking the first two nodes since this test takes a long time
+ # and the third node is kind of redundant in this context
+ for i, msg in enumerate([filter_msg, stats_msg]):
+ self.restart_node(i, extra_args=["-prune=1", "-fastprune"])
+ node = self.nodes[i]
+ prune_height = node.getblockchaininfo()["pruneheight"]
+ self.connect_nodes(i, 3)
+ peers = node.getpeerinfo()
+ assert_equal(len(peers), 1)
+ peer_id = peers[0]["id"]
+
+ # 1500 is the height to where the indices were able to sync previously
+ hashes = send_batch_request(node, "getblockhash", [[a] for a in range(1500, prune_height)])
+ send_batch_request(node, "getblockfrompeer", [[bh, peer_id] for bh in hashes])
+ # Ensure all necessary blocks have been fetched before proceeding
+ for bh in hashes:
+ self.wait_until(lambda: self.check_for_block(i, bh), timeout=10)
+
+ # Upon restart we expect the same errors as previously although all
+ # necessary blocks have been fetched. Both indices need the undo
+ # data of the blocks to be available as well and getblockfrompeer
+ # can not provide that.
+ self.stop_node(i)
+ node.assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg+end_msg)
+
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"]
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.