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
614import subprocess
715
816from 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
1026class 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
48105if __name__ == "__main__" :
49106 BitcoinChainstateTest (__file__ ).main ()
0 commit comments