Skip to content

Commit 6cb63db

Browse files
committed
test: Add bitcoin-chainstate test for assumeutxo functionality
Adds functional test coverage for bitcoin-chainstate tool loading a datadir initialized with an assumeutxo snapshot
1 parent f13ca13 commit 6cb63db

File tree

2 files changed

+77
-20
lines changed

2 files changed

+77
-20
lines changed

src/kernel/chainparams.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ class CRegTestParams : public CChainParams
638638
.blockhash = consteval_ctor(uint256{"385901ccbd69dff6bbd00065d01fb8a9e464dede7cfe0372443884f9b1dcf6b9"}),
639639
},
640640
{
641-
// For use by test/functional/feature_assumeutxo.py
641+
// For use by test/functional/feature_assumeutxo.py and test/functional/tool_bitcoin_chainstate.py
642642
.height = 299,
643643
.hash_serialized = AssumeutxoHash{uint256{"d2b051ff5e8eef46520350776f4100dd710a63447a8e01d917e92e79751a63e2"}},
644644
.m_chain_tx_count = 334,

test/functional/tool_bitcoin_chainstate.py

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,58 @@
22
# Copyright (c) 2022-present 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 bitcoin-chainstate tool functionality
6+
7+
Test basic block processing via bitcoin-chainstate tool, including detecting
8+
duplicates and malformed input.
9+
10+
Test that bitcoin-chainstate can load a datadir initialized with an assumeutxo
11+
snapshot and extend the snapshot chain with new blocks.
12+
"""
513

614
import subprocess
715

816
from test_framework.test_framework import BitcoinTestFramework
17+
from test_framework.util import assert_equal
18+
from test_framework.wallet import MiniWallet
19+
20+
START_HEIGHT = 199
21+
# Hardcoded in regtest chainparams
22+
SNAPSHOT_BASE_BLOCK_HEIGHT = 299
23+
SNAPSHOT_BASE_BLOCK_HASH = "7cc695046fec709f8c9394b6f928f81e81fd3ac20977bb68760fa1faa7916ea2"
24+
925

1026
class BitcoinChainstateTest(BitcoinTestFramework):
1127
def skip_test_if_missing_module(self):
1228
self.skip_if_no_bitcoin_chainstate()
1329

1430
def set_test_params(self):
15-
self.setup_clean_chain = True
16-
self.chain = ""
17-
self.num_nodes = 1
18-
# Set prune to avoid disk space warning.
19-
self.extra_args = [["-prune=550"]]
31+
"""Use the pregenerated, deterministic chain up to height 199."""
32+
self.num_nodes = 2
2033

21-
def add_block(self, datadir, input, expected_stderr):
34+
def setup_network(self):
35+
"""Start with the nodes disconnected so that one can generate a snapshot
36+
including blocks the other hasn't yet seen."""
37+
self.add_nodes(2)
38+
self.start_nodes()
39+
40+
def generate_snapshot_chain(self):
41+
self.log.info(f"Generate deterministic chain up to block {SNAPSHOT_BASE_BLOCK_HEIGHT} for node0 while node1 disconnected")
42+
n0 = self.nodes[0]
43+
assert_equal(n0.getblockcount(), START_HEIGHT)
44+
n0.setmocktime(n0.getblockheader(n0.getbestblockhash())['time'])
45+
mini_wallet = MiniWallet(n0)
46+
for i in range(SNAPSHOT_BASE_BLOCK_HEIGHT - n0.getblockchaininfo()["blocks"]):
47+
if i % 3 == 0:
48+
mini_wallet.send_self_transfer(from_node=n0)
49+
self.generate(n0, nblocks=1, sync_fun=self.no_op)
50+
assert_equal(n0.getblockcount(), SNAPSHOT_BASE_BLOCK_HEIGHT)
51+
assert_equal(n0.getbestblockhash(), SNAPSHOT_BASE_BLOCK_HASH)
52+
return n0.dumptxoutset('utxos.dat', "latest")
53+
54+
def add_block(self, datadir, input, expected_stderr=None, expected_stdout=None):
2255
proc = subprocess.Popen(
23-
self.get_binaries().chainstate_argv() + [datadir],
56+
self.get_binaries().chainstate_argv() + ["-regtest", datadir],
2457
stdin=subprocess.PIPE,
2558
stdout=subprocess.PIPE,
2659
stderr=subprocess.PIPE,
@@ -30,20 +63,44 @@ def add_block(self, datadir, input, expected_stderr):
3063
self.log.debug("STDOUT: {0}".format(stdout.strip("\n")))
3164
self.log.info("STDERR: {0}".format(stderr.strip("\n")))
3265

33-
if expected_stderr not in stderr:
34-
raise AssertionError(f"Expected stderr output {expected_stderr} does not partially match stderr:\n{stderr}")
66+
if expected_stderr is not None and expected_stderr not in stderr:
67+
raise AssertionError(f"Expected stderr output '{expected_stderr}' does not partially match stderr:\n{stderr}")
68+
if expected_stdout is not None and expected_stdout not in stdout:
69+
raise AssertionError(f"Expected stdout output '{expected_stdout}' does not partially match stdout:\n{stdout}")
70+
71+
def basic_test(self):
72+
n0 = self.nodes[0]
73+
n1 = self.nodes[1]
74+
datadir = n1.chain_path
75+
n1.stop_node()
76+
block = n0.getblock(n0.getblockhash(START_HEIGHT+1), 0)
77+
self.log.info(f"Test bitcoin-chainstate {self.get_binaries().chainstate_argv()} with datadir: {datadir}")
78+
self.add_block(datadir, block, expected_stderr="Block has not yet been rejected")
79+
self.add_block(datadir, block, expected_stderr="duplicate")
80+
self.add_block(datadir, "00", expected_stderr="Block decode failed")
81+
self.add_block(datadir, "", expected_stderr="Empty line found")
82+
83+
def assumeutxo_test(self, dump_output_path):
84+
n0 = self.nodes[0]
85+
n1 = self.nodes[1]
86+
self.start_node(1)
87+
self.log.info("Submit headers for new blocks to node1, then load the snapshot so it activates")
88+
for height in range(START_HEIGHT+2, SNAPSHOT_BASE_BLOCK_HEIGHT+1):
89+
block = n0.getblock(n0.getblockhash(height), 0)
90+
n1.submitheader(block)
91+
assert_equal(n1.getblockcount(), START_HEIGHT+1)
92+
loaded = n1.loadtxoutset(dump_output_path)
93+
assert_equal(loaded['base_height'], SNAPSHOT_BASE_BLOCK_HEIGHT)
94+
datadir = n1.chain_path
95+
n1.stop_node()
96+
self.log.info(f"Test bitcoin-chainstate {self.get_binaries().chainstate_argv()} with an assumeutxo datadir: {datadir}")
97+
new_tip_hash = self.generate(n0, nblocks=1, sync_fun=self.no_op)[0]
98+
self.add_block(datadir, n0.getblock(new_tip_hash, 0), expected_stdout="Block tip changed")
3599

36100
def run_test(self):
37-
node = self.nodes[0]
38-
datadir = node.cli.datadir
39-
node.stop_node()
40-
41-
self.log.info(f"Testing bitcoin-chainstate {self.get_binaries().chainstate_argv()} with datadir: {datadir}")
42-
block_one = "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"
43-
self.add_block(datadir, block_one, "Block has not yet been rejected")
44-
self.add_block(datadir, block_one, "duplicate")
45-
self.add_block(datadir, "00", "Block decode failed")
46-
self.add_block(datadir, "", "Empty line found")
101+
dump_output = self.generate_snapshot_chain()
102+
self.basic_test()
103+
self.assumeutxo_test(dump_output['path'])
47104

48105
if __name__ == "__main__":
49106
BitcoinChainstateTest(__file__).main()

0 commit comments

Comments
 (0)