2021-06-17 16:09:38 -04:00
#!/usr/bin/env python3
2023-10-13 07:23:25 -03:00
# Copyright (c) 2021-present The Bitcoin Core developers
2021-06-17 16:09:38 -04:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
""" Test for assumeutxo, a means of quickly bootstrapping a node using
a serialized version of the UTXO set at a certain height , which corresponds
to a hash that has been compiled into bitcoind .
The assumeutxo value generated and used here is committed to in
2024-01-31 07:28:18 -03:00
` CRegTestParams : : m_assumeutxo_data ` in ` src / kernel / chainparams . cpp ` .
2021-06-17 16:09:38 -04:00
"""
2024-07-09 10:07:29 -04:00
import time
2023-10-25 00:04:13 -03:00
from shutil import rmtree
2024-02-05 18:16:14 -03:00
from dataclasses import dataclass
2024-06-20 16:14:22 -04:00
from test_framework . blocktools import (
create_block ,
create_coinbase
)
2024-07-09 10:07:29 -04:00
from test_framework . messages import (
CBlockHeader ,
from_hex ,
msg_headers ,
tx_from_hex
)
from test_framework . p2p import (
P2PInterface ,
)
2021-06-17 16:09:38 -04:00
from test_framework . test_framework import BitcoinTestFramework
2023-10-10 04:28:22 -03:00
from test_framework . util import (
2024-04-27 09:48:15 -04:00
assert_approx ,
2023-10-10 04:28:22 -03:00
assert_equal ,
assert_raises_rpc_error ,
2024-03-02 14:26:27 -03:00
sha256sum_file ,
2024-07-09 10:07:29 -04:00
try_rpc ,
2023-10-10 04:28:22 -03:00
)
2024-01-31 07:28:18 -03:00
from test_framework . wallet import (
getnewdestination ,
MiniWallet ,
)
2021-06-17 16:09:38 -04:00
START_HEIGHT = 199
SNAPSHOT_BASE_HEIGHT = 299
FINAL_HEIGHT = 399
COMPLETE_IDX = { ' synced ' : True , ' best_block_height ' : FINAL_HEIGHT }
class AssumeutxoTest ( BitcoinTestFramework ) :
def set_test_params ( self ) :
""" Use the pregenerated, deterministic chain up to height 199. """
2024-06-19 09:07:55 -04:00
self . num_nodes = 4
2021-06-17 16:09:38 -04:00
self . rpc_timeout = 120
self . extra_args = [
[ ] ,
[ " -fastprune " , " -prune=1 " , " -blockfilterindex=1 " , " -coinstatsindex=1 " ] ,
2024-02-05 16:57:45 -03:00
[ " -persistmempool=0 " , " -txindex=1 " , " -blockfilterindex=1 " , " -coinstatsindex=1 " ] ,
2024-06-19 09:07:55 -04:00
[ ]
2021-06-17 16:09:38 -04:00
]
def setup_network ( self ) :
""" Start with the nodes disconnected so that one can generate a snapshot
including blocks the other hasn ' t yet seen. " " "
2024-06-19 09:07:55 -04:00
self . add_nodes ( 4 )
2021-06-17 16:09:38 -04:00
self . start_nodes ( extra_args = self . extra_args )
2023-10-10 04:28:22 -03:00
def test_invalid_snapshot_scenarios ( self , valid_snapshot_path ) :
self . log . info ( " Test different scenarios of loading invalid snapshot files " )
with open ( valid_snapshot_path , ' rb ' ) as f :
valid_snapshot_contents = f . read ( )
2023-10-12 06:21:08 -03:00
bad_snapshot_path = valid_snapshot_path + ' .mod '
2024-06-18 08:24:55 -04:00
node = self . nodes [ 1 ]
2023-10-10 04:28:22 -03:00
2024-07-22 06:21:14 -04:00
def expected_error ( msg ) :
assert_raises_rpc_error ( - 32603 , f " Unable to load UTXO snapshot: Population failed: { msg } " , node . loadtxoutset , bad_snapshot_path )
2023-10-17 06:32:03 -03:00
2024-04-22 08:52:40 -04:00
self . log . info ( " - snapshot file with invalid file magic " )
parsing_error_code = - 22
bad_magic = 0xf00f00f000
with open ( bad_snapshot_path , ' wb ' ) as f :
f . write ( bad_magic . to_bytes ( 5 , " big " ) + valid_snapshot_contents [ 5 : ] )
2024-06-18 08:24:55 -04:00
assert_raises_rpc_error ( parsing_error_code , " Unable to parse metadata: Invalid UTXO set snapshot magic bytes. Please check if this is indeed a snapshot file or if you are using an outdated snapshot format. " , node . loadtxoutset , bad_snapshot_path )
2024-04-22 08:52:40 -04:00
self . log . info ( " - snapshot file with unsupported version " )
2024-08-06 18:21:36 -04:00
for version in [ 0 , 1 , 3 ] :
2024-04-22 08:52:40 -04:00
with open ( bad_snapshot_path , ' wb ' ) as f :
f . write ( valid_snapshot_contents [ : 5 ] + version . to_bytes ( 2 , " little " ) + valid_snapshot_contents [ 7 : ] )
2024-06-18 08:24:55 -04:00
assert_raises_rpc_error ( parsing_error_code , f " Unable to parse metadata: Version of snapshot { version } does not match any of the supported versions. " , node . loadtxoutset , bad_snapshot_path )
2024-04-22 08:52:40 -04:00
self . log . info ( " - snapshot file with mismatching network magic " )
invalid_magics = [
# magic, name, real
[ 0xf9beb4d9 , " main " , True ] ,
[ 0x0b110907 , " test " , True ] ,
[ 0x0a03cf40 , " signet " , True ] ,
[ 0x00000000 , " " , False ] ,
[ 0xffffffff , " " , False ] ,
]
for [ magic , name , real ] in invalid_magics :
with open ( bad_snapshot_path , ' wb ' ) as f :
f . write ( valid_snapshot_contents [ : 7 ] + magic . to_bytes ( 4 , ' big ' ) + valid_snapshot_contents [ 11 : ] )
if real :
2024-06-18 08:24:55 -04:00
assert_raises_rpc_error ( parsing_error_code , f " Unable to parse metadata: The network of the snapshot ( { name } ) does not match the network of this node (regtest). " , node . loadtxoutset , bad_snapshot_path )
2024-04-22 08:52:40 -04:00
else :
2024-06-18 08:24:55 -04:00
assert_raises_rpc_error ( parsing_error_code , " Unable to parse metadata: This snapshot has been created for an unrecognized network. This could be a custom signet, a new testnet or possibly caused by data corruption. " , node . loadtxoutset , bad_snapshot_path )
2024-04-22 08:52:40 -04:00
2023-11-06 22:21:51 -03:00
self . log . info ( " - snapshot file referring to a block that is not in the assumeutxo parameters " )
2023-10-17 11:57:07 -03:00
prev_block_hash = self . nodes [ 0 ] . getblockhash ( SNAPSHOT_BASE_HEIGHT - 1 )
bogus_block_hash = " 0 " * 64 # Represents any unknown block hash
for bad_block_hash in [ bogus_block_hash , prev_block_hash ] :
with open ( bad_snapshot_path , ' wb ' ) as f :
2024-08-06 18:21:36 -04:00
f . write ( valid_snapshot_contents [ : 11 ] + bytes . fromhex ( bad_block_hash ) [ : : - 1 ] + valid_snapshot_contents [ 43 : ] )
2024-06-18 08:24:55 -04:00
2024-08-06 18:21:36 -04:00
msg = f " Unable to load UTXO snapshot: assumeutxo block hash in snapshot metadata not recognized (hash: { bad_block_hash } ). The following snapshot heights are available: 110, 200, 299. "
2024-06-18 08:24:55 -04:00
assert_raises_rpc_error ( - 32603 , msg , node . loadtxoutset , bad_snapshot_path )
2023-10-10 04:28:22 -03:00
2023-10-12 06:21:08 -03:00
self . log . info ( " - snapshot file with wrong number of coins " )
2024-08-06 18:21:36 -04:00
valid_num_coins = int . from_bytes ( valid_snapshot_contents [ 43 : 43 + 8 ] , " little " )
2023-10-12 06:21:08 -03:00
for off in [ - 1 , + 1 ] :
with open ( bad_snapshot_path , ' wb ' ) as f :
2024-08-06 18:21:36 -04:00
f . write ( valid_snapshot_contents [ : 43 ] )
2023-10-13 07:23:25 -03:00
f . write ( ( valid_num_coins + off ) . to_bytes ( 8 , " little " ) )
2024-08-06 18:21:36 -04:00
f . write ( valid_snapshot_contents [ 43 + 8 : ] )
2024-07-22 06:21:14 -04:00
expected_error ( msg = " Bad snapshot - coins left over after deserializing 298 coins. " if off == - 1 else " Bad snapshot format or truncated snapshot after deserializing 299 coins. " )
2023-10-12 06:21:08 -03:00
2024-03-09 21:19:04 -03:00
self . log . info ( " - snapshot file with alternated but parsable UTXO data results in different hash " )
2023-10-17 13:35:10 -03:00
cases = [
2024-03-10 23:47:03 -03:00
# (content, offset, wrong_hash, custom_message)
[ b " \xff " * 32 , 0 , " 7d52155c9a9fdc4525b637ef6170568e5dad6fabd0b1fdbb9432010b8453095b " , None ] , # wrong outpoint hash
2024-07-22 06:21:14 -04:00
[ ( 2 ) . to_bytes ( 1 , " little " ) , 32 , None , " Bad snapshot data after deserializing 1 coins. " ] , # wrong txid coins count
[ b " \xfd \xff \xff " , 32 , None , " Mismatch in coins count in snapshot metadata and actual snapshot data " ] , # txid coins count exceeds coins left
2024-03-09 21:19:04 -03:00
[ b " \x01 " , 33 , " 9f4d897031ab8547665b4153317ae2fdbf0130c7840b66427ebc48b881cb80ad " , None ] , # wrong outpoint index
[ b " \x81 " , 34 , " 3da966ba9826fb6d2604260e01607b55ba44e1a5de298606b08704bc62570ea8 " , None ] , # wrong coin code VARINT
[ b " \x80 " , 34 , " 091e893b3ccb4334378709578025356c8bcb0a623f37c7c4e493133c988648e5 " , None ] , # another wrong coin code
2024-07-22 06:21:14 -04:00
[ b " \x84 \x58 " , 34 , None , " Bad snapshot data after deserializing 0 coins " ] , # wrong coin case with height 364 and coinbase 0
[ b " \xCA \xD2 \x8F \x5A " , 39 , None , " Bad snapshot data after deserializing 0 coins - bad tx out value " ] , # Amount exceeds MAX_MONEY
2023-10-17 13:35:10 -03:00
]
2024-03-10 23:47:03 -03:00
for content , offset , wrong_hash , custom_message in cases :
2023-10-17 13:35:10 -03:00
with open ( bad_snapshot_path , " wb " ) as f :
2024-08-06 18:21:36 -04:00
# Prior to offset: Snapshot magic, snapshot version, network magic, hash, coins count
f . write ( valid_snapshot_contents [ : ( 5 + 2 + 4 + 32 + 8 + offset ) ] )
2023-10-17 13:35:10 -03:00
f . write ( content )
2024-08-06 18:21:36 -04:00
f . write ( valid_snapshot_contents [ ( 5 + 2 + 4 + 32 + 8 + offset + len ( content ) ) : ] )
2024-03-10 23:47:03 -03:00
2024-07-22 06:21:14 -04:00
msg = custom_message if custom_message is not None else f " Bad snapshot content hash: expected a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27, got { wrong_hash } . "
expected_error ( msg )
2023-10-13 07:23:25 -03:00
2024-02-19 13:50:06 -03:00
def test_headers_not_synced ( self , valid_snapshot_path ) :
for node in self . nodes [ 1 : ] :
2024-06-18 08:24:55 -04:00
msg = " Unable to load UTXO snapshot: The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again. "
assert_raises_rpc_error ( - 32603 , msg , node . loadtxoutset , valid_snapshot_path )
2024-02-19 13:50:06 -03:00
2023-10-25 00:04:13 -03:00
def test_invalid_chainstate_scenarios ( self ) :
self . log . info ( " Test different scenarios of invalid snapshot chainstate in datadir " )
2023-11-06 22:21:51 -03:00
self . log . info ( " - snapshot chainstate referring to a block that is not in the assumeutxo parameters " )
2023-10-25 00:04:13 -03:00
self . stop_node ( 0 )
chainstate_snapshot_path = self . nodes [ 0 ] . chain_path / " chainstate_snapshot "
chainstate_snapshot_path . mkdir ( )
with open ( chainstate_snapshot_path / " base_blockhash " , ' wb ' ) as f :
f . write ( b ' z ' * 32 )
2023-11-09 18:54:27 -03:00
def expected_error ( log_msg = " " , error_msg = " " ) :
with self . nodes [ 0 ] . assert_debug_log ( [ log_msg ] ) :
self . nodes [ 0 ] . assert_start_raises_init_error ( expected_msg = error_msg )
2024-03-15 17:42:44 -03:00
expected_error_msg = f " Error: A fatal internal error occurred, see debug.log for details: Assumeutxo data not found for the given blockhash ' 7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a ' . "
2023-11-09 18:54:27 -03:00
error_details = f " Assumeutxo data not found for the given blockhash "
expected_error ( log_msg = error_details , error_msg = expected_error_msg )
2023-10-25 00:04:13 -03:00
# resurrect node again
rmtree ( chainstate_snapshot_path )
self . start_node ( 0 )
2024-02-05 16:57:45 -03:00
def test_invalid_mempool_state ( self , dump_output_path ) :
self . log . info ( " Test bitcoind should fail when mempool not empty. " )
node = self . nodes [ 2 ]
tx = MiniWallet ( node ) . send_self_transfer ( from_node = node )
assert tx [ ' txid ' ] in node . getrawmempool ( )
# Attempt to load the snapshot on Node 2 and expect it to fail
2024-06-18 08:24:55 -04:00
msg = " Unable to load UTXO snapshot: Can ' t activate a snapshot when mempool not empty "
assert_raises_rpc_error ( - 32603 , msg , node . loadtxoutset , dump_output_path )
2024-02-05 16:57:45 -03:00
self . restart_node ( 2 , extra_args = self . extra_args [ 2 ] )
2024-05-06 17:24:40 -04:00
def test_invalid_file_path ( self ) :
self . log . info ( " Test bitcoind should fail when file path is invalid. " )
node = self . nodes [ 0 ]
path = node . datadir_path / node . chain / " invalid " / " path "
assert_raises_rpc_error ( - 8 , " Couldn ' t open file {} for reading. " . format ( path ) , node . loadtxoutset , path )
2024-02-12 22:24:57 -03:00
def test_snapshot_with_less_work ( self , dump_output_path ) :
self . log . info ( " Test bitcoind should fail when snapshot has less accumulated work than this node. " )
node = self . nodes [ 0 ]
2024-07-22 06:21:14 -04:00
msg = " Unable to load UTXO snapshot: Population failed: Work does not exceed active chainstate. "
assert_raises_rpc_error ( - 32603 , msg , node . loadtxoutset , dump_output_path )
2024-02-12 22:24:57 -03:00
2024-06-18 08:34:50 -04:00
def test_snapshot_block_invalidated ( self , dump_output_path ) :
self . log . info ( " Test snapshot is not loaded when base block is invalid. " )
node = self . nodes [ 0 ]
# We are testing the case where the base block is invalidated itself
# and also the case where one of its parents is invalidated.
for height in [ SNAPSHOT_BASE_HEIGHT , SNAPSHOT_BASE_HEIGHT - 1 ] :
block_hash = node . getblockhash ( height )
node . invalidateblock ( block_hash )
assert_equal ( node . getblockcount ( ) , height - 1 )
msg = " Unable to load UTXO snapshot: The base block header (3bb7ce5eba0be48939b7a521ac1ba9316afee2c7bada3a0cca24188e6d7d96c0) is part of an invalid chain. "
assert_raises_rpc_error ( - 32603 , msg , node . loadtxoutset , dump_output_path )
node . reconsiderblock ( block_hash )
2024-06-19 09:07:55 -04:00
def test_snapshot_in_a_divergent_chain ( self , dump_output_path ) :
n0 = self . nodes [ 0 ]
n3 = self . nodes [ 3 ]
assert_equal ( n0 . getblockcount ( ) , FINAL_HEIGHT )
assert_equal ( n3 . getblockcount ( ) , START_HEIGHT )
self . log . info ( " Check importing a snapshot where current chain-tip is not an ancestor of the snapshot block but has less work " )
# Generate a divergent chain in n3 up to 298
self . generate ( n3 , nblocks = 99 , sync_fun = self . no_op )
assert_equal ( n3 . getblockcount ( ) , SNAPSHOT_BASE_HEIGHT - 1 )
# Try importing the snapshot and assert its success
loaded = n3 . loadtxoutset ( dump_output_path )
assert_equal ( loaded [ ' base_height ' ] , SNAPSHOT_BASE_HEIGHT )
normal , snapshot = n3 . getchainstates ( ) [ " chainstates " ]
assert_equal ( normal [ ' blocks ' ] , START_HEIGHT + 99 )
assert_equal ( snapshot [ ' blocks ' ] , SNAPSHOT_BASE_HEIGHT )
# Now lets sync the nodes and wait for the background validation to finish
self . connect_nodes ( 0 , 3 )
self . sync_blocks ( nodes = ( n0 , n3 ) )
self . wait_until ( lambda : len ( n3 . getchainstates ( ) [ ' chainstates ' ] ) == 1 )
2024-06-20 16:14:22 -04:00
def test_snapshot_not_on_most_work_chain ( self , dump_output_path ) :
self . log . info ( " Test snapshot is not loaded when the node knows the headers of another chain with more work. " )
node0 = self . nodes [ 0 ]
node1 = self . nodes [ 1 ]
# Create an alternative chain of 2 new blocks, forking off the main chain at the block before the snapshot block.
# This simulates a longer chain than the main chain when submitting these two block headers to node 1 because it is only aware of
# the main chain headers up to the snapshot height.
parent_block_hash = node0 . getblockhash ( SNAPSHOT_BASE_HEIGHT - 1 )
block_time = node0 . getblock ( node0 . getbestblockhash ( ) ) [ ' time ' ] + 1
fork_block1 = create_block ( int ( parent_block_hash , 16 ) , create_coinbase ( SNAPSHOT_BASE_HEIGHT ) , block_time )
fork_block1 . solve ( )
fork_block2 = create_block ( fork_block1 . sha256 , create_coinbase ( SNAPSHOT_BASE_HEIGHT + 1 ) , block_time + 1 )
fork_block2 . solve ( )
node1 . submitheader ( fork_block1 . serialize ( ) . hex ( ) )
node1 . submitheader ( fork_block2 . serialize ( ) . hex ( ) )
msg = " A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo. "
assert_raises_rpc_error ( - 32603 , msg , node1 . loadtxoutset , dump_output_path )
# Cleanup: submit two more headers of the snapshot chain to node 1, so that it is the most-work chain again and loading
# the snapshot in future subtests succeeds
main_block1 = node0 . getblock ( node0 . getblockhash ( SNAPSHOT_BASE_HEIGHT + 1 ) , 0 )
main_block2 = node0 . getblock ( node0 . getblockhash ( SNAPSHOT_BASE_HEIGHT + 2 ) , 0 )
node1 . submitheader ( main_block1 )
node1 . submitheader ( main_block2 )
2024-07-09 10:07:29 -04:00
def test_sync_from_assumeutxo_node ( self , snapshot ) :
"""
This test verifies that :
1. An IBD node can sync headers from an AssumeUTXO node at any time .
2. IBD nodes do not request historical blocks from AssumeUTXO nodes while they are syncing the background - chain .
3. The assumeUTXO node dynamically adjusts the network services it offers according to its state .
4. IBD nodes can fully sync from AssumeUTXO nodes after they finish the background - chain sync .
"""
self . log . info ( " Testing IBD-sync from assumeUTXO node " )
# Node2 starts clean and loads the snapshot.
# Node3 starts clean and seeks to sync-up from snapshot_node.
miner = self . nodes [ 0 ]
snapshot_node = self . nodes [ 2 ]
ibd_node = self . nodes [ 3 ]
# Start test fresh by cleaning up node directories
for node in ( snapshot_node , ibd_node ) :
self . stop_node ( node . index )
rmtree ( node . chain_path )
self . start_node ( node . index , extra_args = self . extra_args [ node . index ] )
# Sync-up headers chain on snapshot_node to load snapshot
headers_provider_conn = snapshot_node . add_p2p_connection ( P2PInterface ( ) )
headers_provider_conn . wait_for_getheaders ( )
msg = msg_headers ( )
for block_num in range ( 1 , miner . getblockcount ( ) + 1 ) :
msg . headers . append ( from_hex ( CBlockHeader ( ) , miner . getblockheader ( miner . getblockhash ( block_num ) , verbose = False ) ) )
headers_provider_conn . send_message ( msg )
# Ensure headers arrived
default_value = { ' status ' : ' ' } # No status
headers_tip_hash = miner . getbestblockhash ( )
self . wait_until ( lambda : next ( filter ( lambda x : x [ ' hash ' ] == headers_tip_hash , snapshot_node . getchaintips ( ) ) , default_value ) [ ' status ' ] == " headers-only " )
snapshot_node . disconnect_p2ps ( )
# Load snapshot
snapshot_node . loadtxoutset ( snapshot [ ' path ' ] )
# Connect nodes and verify the ibd_node can sync-up the headers-chain from the snapshot_node
self . connect_nodes ( ibd_node . index , snapshot_node . index )
snapshot_block_hash = snapshot [ ' base_hash ' ]
self . wait_until ( lambda : next ( filter ( lambda x : x [ ' hash ' ] == snapshot_block_hash , ibd_node . getchaintips ( ) ) , default_value ) [ ' status ' ] == " headers-only " )
# Once the headers-chain is synced, the ibd_node must avoid requesting historical blocks from the snapshot_node.
# If it does request such blocks, the snapshot_node will ignore requests it cannot fulfill, causing the ibd_node
# to stall. This stall could last for up to 10 min, ultimately resulting in an abrupt disconnection due to the
# ibd_node's perceived unresponsiveness.
time . sleep ( 3 ) # Sleep here because we can't detect when a node avoids requesting blocks from other peer.
assert_equal ( len ( ibd_node . getpeerinfo ( ) [ 0 ] [ ' inflight ' ] ) , 0 )
# Now disconnect nodes and finish background chain sync
self . disconnect_nodes ( ibd_node . index , snapshot_node . index )
self . connect_nodes ( snapshot_node . index , miner . index )
self . sync_blocks ( nodes = ( miner , snapshot_node ) )
# Check the base snapshot block was stored and ensure node signals full-node service support
self . wait_until ( lambda : not try_rpc ( - 1 , " Block not found " , snapshot_node . getblock , snapshot_block_hash ) )
assert ' NETWORK ' in snapshot_node . getnetworkinfo ( ) [ ' localservicesnames ' ]
# Now the snapshot_node is sync, verify the ibd_node can sync from it
self . connect_nodes ( snapshot_node . index , ibd_node . index )
assert ' NETWORK ' in ibd_node . getpeerinfo ( ) [ 0 ] [ ' servicesnames ' ]
self . sync_blocks ( nodes = ( ibd_node , snapshot_node ) )
2024-09-03 10:00:55 -04:00
def assert_only_network_limited_service ( self , node ) :
node_services = node . getnetworkinfo ( ) [ ' localservicesnames ' ]
assert ' NETWORK ' not in node_services
assert ' NETWORK_LIMITED ' in node_services
2021-06-17 16:09:38 -04:00
def run_test ( self ) :
"""
Bring up two ( disconnected ) nodes , mine some new blocks on the first ,
and generate a UTXO snapshot .
Load the snapshot into the second , ensure it syncs to tip and completes
background validation when connected to the first .
"""
n0 = self . nodes [ 0 ]
n1 = self . nodes [ 1 ]
n2 = self . nodes [ 2 ]
2024-06-19 09:07:55 -04:00
n3 = self . nodes [ 3 ]
2021-06-17 16:09:38 -04:00
2024-01-31 07:28:18 -03:00
self . mini_wallet = MiniWallet ( n0 )
2021-06-17 16:09:38 -04:00
# Mock time for a deterministic chain
for n in self . nodes :
n . setmocktime ( n . getblockheader ( n . getbestblockhash ( ) ) [ ' time ' ] )
# Generate a series of blocks that `n0` will have in the snapshot,
2024-02-19 13:50:06 -03:00
# but that n1 and n2 don't yet see.
2024-02-05 18:16:14 -03:00
assert n0 . getblockcount ( ) == START_HEIGHT
blocks = { START_HEIGHT : Block ( n0 . getbestblockhash ( ) , 1 , START_HEIGHT + 1 ) }
2021-06-17 16:09:38 -04:00
for i in range ( 100 ) :
2024-02-05 18:16:14 -03:00
block_tx = 1
2024-01-31 07:28:18 -03:00
if i % 3 == 0 :
self . mini_wallet . send_self_transfer ( from_node = n0 )
2024-02-05 18:16:14 -03:00
block_tx + = 1
2023-10-02 19:55:10 -03:00
self . generate ( n0 , nblocks = 1 , sync_fun = self . no_op )
2024-02-05 18:16:14 -03:00
height = n0 . getblockcount ( )
hash = n0 . getbestblockhash ( )
blocks [ height ] = Block ( hash , block_tx , blocks [ height - 1 ] . chain_tx + block_tx )
2024-02-02 12:59:24 -03:00
if i == 4 :
# Create a stale block that forks off the main chain before the snapshot.
temp_invalid = n0 . getbestblockhash ( )
n0 . invalidateblock ( temp_invalid )
stale_hash = self . generateblock ( n0 , output = " raw(aaaa) " , transactions = [ ] , sync_fun = self . no_op ) [ " hash " ]
n0 . invalidateblock ( stale_hash )
n0 . reconsiderblock ( temp_invalid )
stale_block = n0 . getblock ( stale_hash , 0 )
2024-02-05 18:16:14 -03:00
2021-06-17 16:09:38 -04:00
self . log . info ( " -- Testing assumeutxo + some indexes + pruning " )
assert_equal ( n0 . getblockcount ( ) , SNAPSHOT_BASE_HEIGHT )
assert_equal ( n1 . getblockcount ( ) , START_HEIGHT )
self . log . info ( f " Creating a UTXO snapshot at height { SNAPSHOT_BASE_HEIGHT } " )
2024-03-02 12:43:58 -03:00
dump_output = n0 . dumptxoutset ( ' utxos.dat ' , " latest " )
2021-06-17 16:09:38 -04:00
2024-07-08 07:12:33 -04:00
self . log . info ( " Test loading snapshot when the node tip is on the same block as the snapshot " )
assert_equal ( n0 . getblockcount ( ) , SNAPSHOT_BASE_HEIGHT )
assert_equal ( n0 . getblockchaininfo ( ) [ " blocks " ] , SNAPSHOT_BASE_HEIGHT )
self . test_snapshot_with_less_work ( dump_output [ ' path ' ] )
2024-02-19 13:50:06 -03:00
self . log . info ( " Test loading snapshot when headers are not synced " )
self . test_headers_not_synced ( dump_output [ ' path ' ] )
# In order for the snapshot to activate, we have to ferry over the new
# headers to n1 and n2 so that they see the header of the snapshot's
# base block while disconnected from n0.
for i in range ( 1 , 300 ) :
block = n0 . getblock ( n0 . getblockhash ( i ) , 0 )
# make n1 and n2 aware of the new header, but don't give them the
# block.
n1 . submitheader ( block )
n2 . submitheader ( block )
2024-06-19 09:07:55 -04:00
n3 . submitheader ( block )
2024-02-19 13:50:06 -03:00
# Ensure everyone is seeing the same headers.
for n in self . nodes :
assert_equal ( n . getblockchaininfo ( ) [ " headers " ] , SNAPSHOT_BASE_HEIGHT )
2021-06-17 16:09:38 -04:00
assert_equal ( n0 . getblockchaininfo ( ) [ " blocks " ] , SNAPSHOT_BASE_HEIGHT )
2024-03-02 14:26:27 -03:00
def check_dump_output ( output ) :
assert_equal (
output [ ' txoutset_hash ' ] ,
" a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27 " )
assert_equal ( output [ " nchaintx " ] , blocks [ SNAPSHOT_BASE_HEIGHT ] . chain_tx )
check_dump_output ( dump_output )
2021-06-17 16:09:38 -04:00
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
# will allow us to test n1's sync-to-tip on top of a snapshot.
2023-10-02 19:55:10 -03:00
self . generate ( n0 , nblocks = 100 , sync_fun = self . no_op )
2021-06-17 16:09:38 -04:00
assert_equal ( n0 . getblockcount ( ) , FINAL_HEIGHT )
assert_equal ( n1 . getblockcount ( ) , START_HEIGHT )
assert_equal ( n0 . getblockchaininfo ( ) [ " blocks " ] , FINAL_HEIGHT )
2024-03-02 14:26:27 -03:00
self . log . info ( f " Check that dumptxoutset works for past block heights " )
# rollback defaults to the snapshot base height
dump_output2 = n0 . dumptxoutset ( ' utxos2.dat ' , " rollback " )
check_dump_output ( dump_output2 )
assert_equal ( sha256sum_file ( dump_output [ ' path ' ] ) , sha256sum_file ( dump_output2 [ ' path ' ] ) )
# Rollback with specific height
dump_output3 = n0 . dumptxoutset ( ' utxos3.dat ' , rollback = SNAPSHOT_BASE_HEIGHT )
check_dump_output ( dump_output3 )
assert_equal ( sha256sum_file ( dump_output [ ' path ' ] ) , sha256sum_file ( dump_output3 [ ' path ' ] ) )
# Specified height that is not a snapshot height
prev_snap_height = SNAPSHOT_BASE_HEIGHT - 1
dump_output4 = n0 . dumptxoutset ( path = ' utxos4.dat ' , rollback = prev_snap_height )
assert_equal (
dump_output4 [ ' txoutset_hash ' ] ,
" 8a1db0d6e958ce0d7c963bc6fc91ead596c027129bacec68acc40351037b09d7 " )
assert sha256sum_file ( dump_output [ ' path ' ] ) != sha256sum_file ( dump_output4 [ ' path ' ] )
# Use a hash instead of a height
prev_snap_hash = n0 . getblockhash ( prev_snap_height )
dump_output5 = n0 . dumptxoutset ( ' utxos5.dat ' , rollback = prev_snap_hash )
assert_equal ( sha256sum_file ( dump_output4 [ ' path ' ] ) , sha256sum_file ( dump_output5 [ ' path ' ] ) )
# TODO: This is a hack to set m_best_header to the correct value after
# dumptxoutset/reconsiderblock. Otherwise the wrong error messages are
# returned in following tests. It can be removed once this bug is
# fixed. See also https://github.com/bitcoin/bitcoin/issues/26245
self . restart_node ( 0 , [ " -reindex " ] )
# Ensure n0 is back at the tip
assert_equal ( n0 . getblockchaininfo ( ) [ " blocks " ] , FINAL_HEIGHT )
2024-02-12 22:24:57 -03:00
self . test_snapshot_with_less_work ( dump_output [ ' path ' ] )
2024-02-05 16:57:45 -03:00
self . test_invalid_mempool_state ( dump_output [ ' path ' ] )
2023-10-10 04:28:22 -03:00
self . test_invalid_snapshot_scenarios ( dump_output [ ' path ' ] )
2023-10-25 00:04:13 -03:00
self . test_invalid_chainstate_scenarios ( )
2024-05-06 17:24:40 -04:00
self . test_invalid_file_path ( )
2024-06-18 08:34:50 -04:00
self . test_snapshot_block_invalidated ( dump_output [ ' path ' ] )
2024-06-20 16:14:22 -04:00
self . test_snapshot_not_on_most_work_chain ( dump_output [ ' path ' ] )
2023-10-10 04:28:22 -03:00
2024-09-03 10:00:55 -04:00
# Prune-node sanity check
assert ' NETWORK ' not in n1 . getnetworkinfo ( ) [ ' localservicesnames ' ]
2021-06-17 16:09:38 -04:00
self . log . info ( f " Loading snapshot into second node from { dump_output [ ' path ' ] } " )
2024-07-06 12:09:37 -04:00
# This node's tip is on an ancestor block of the snapshot, which should
# be the normal case
2021-06-17 16:09:38 -04:00
loaded = n1 . loadtxoutset ( dump_output [ ' path ' ] )
assert_equal ( loaded [ ' coins_loaded ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( loaded [ ' base_height ' ] , SNAPSHOT_BASE_HEIGHT )
2024-09-03 10:00:55 -04:00
self . log . info ( " Confirm that local services remain unchanged " )
# Since n1 is a pruned node, the 'NETWORK' service flag must always be unset.
self . assert_only_network_limited_service ( n1 )
2024-08-11 20:48:21 -04:00
self . log . info ( " Check that UTXO-querying RPCs operate on snapshot chainstate " )
snapshot_hash = loaded [ ' tip_hash ' ]
snapshot_num_coins = loaded [ ' coins_loaded ' ]
# coinstatsindex might be not caught up yet and is not relevant for this test, so don't use it
utxo_info = n1 . gettxoutsetinfo ( use_index = False )
assert_equal ( utxo_info [ ' txouts ' ] , snapshot_num_coins )
assert_equal ( utxo_info [ ' height ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( utxo_info [ ' bestblock ' ] , snapshot_hash )
# find coinbase output at snapshot height on node0 and scan for it on node1,
# where the block is not available, but the snapshot was loaded successfully
coinbase_tx = n0 . getblock ( snapshot_hash , verbosity = 2 ) [ ' tx ' ] [ 0 ]
assert_raises_rpc_error ( - 1 , " Block not found on disk " , n1 . getblock , snapshot_hash )
coinbase_output_descriptor = coinbase_tx [ ' vout ' ] [ 0 ] [ ' scriptPubKey ' ] [ ' desc ' ]
scan_result = n1 . scantxoutset ( ' start ' , [ coinbase_output_descriptor ] )
assert_equal ( scan_result [ ' success ' ] , True )
assert_equal ( scan_result [ ' txouts ' ] , snapshot_num_coins )
assert_equal ( scan_result [ ' height ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( scan_result [ ' bestblock ' ] , snapshot_hash )
scan_utxos = [ ( coin [ ' txid ' ] , coin [ ' vout ' ] ) for coin in scan_result [ ' unspents ' ] ]
assert ( coinbase_tx [ ' txid ' ] , 0 ) in scan_utxos
txout_result = n1 . gettxout ( coinbase_tx [ ' txid ' ] , 0 )
assert_equal ( txout_result [ ' scriptPubKey ' ] [ ' desc ' ] , coinbase_output_descriptor )
2024-02-05 18:16:14 -03:00
def check_tx_counts ( final : bool ) - > None :
""" Check nTx and nChainTx intermediate values right after loading
the snapshot , and final values after the snapshot is validated . """
for height , block in blocks . items ( ) :
tx = n1 . getblockheader ( block . hash ) [ " nTx " ]
2024-04-27 09:48:15 -04:00
stats = n1 . getchaintxstats ( nblocks = 1 , blockhash = block . hash )
chain_tx = stats . get ( " txcount " , None )
window_tx_count = stats . get ( " window_tx_count " , None )
tx_rate = stats . get ( " txrate " , None )
window_interval = stats . get ( " window_interval " )
2024-02-05 18:16:14 -03:00
2024-02-02 12:59:24 -03:00
# Intermediate nTx of the starting block should be set, but nTx of
# later blocks should be 0 before they are downloaded.
2024-04-27 09:48:15 -04:00
# The window_tx_count of one block is equal to the blocks tx count.
# If the window tx count is unknown, the value is missing.
# The tx_rate is calculated from window_tx_count and window_interval
# when possible.
2024-02-05 18:16:14 -03:00
if final or height == START_HEIGHT :
assert_equal ( tx , block . tx )
2024-04-27 09:48:15 -04:00
assert_equal ( window_tx_count , tx )
if window_interval > 0 :
assert_approx ( tx_rate , window_tx_count / window_interval , vspan = 0.1 )
else :
assert_equal ( tx_rate , None )
2024-02-05 18:16:14 -03:00
else :
2024-02-02 12:59:24 -03:00
assert_equal ( tx , 0 )
2024-04-27 09:48:15 -04:00
assert_equal ( window_tx_count , None )
2024-02-05 18:16:14 -03:00
# Intermediate nChainTx of the starting block and snapshot block
2024-03-25 07:33:07 -03:00
# should be set, but others should be None until they are downloaded.
2024-02-05 18:16:14 -03:00
if final or height in ( START_HEIGHT , SNAPSHOT_BASE_HEIGHT ) :
assert_equal ( chain_tx , block . chain_tx )
else :
2024-03-25 07:33:07 -03:00
assert_equal ( chain_tx , None )
2024-02-05 18:16:14 -03:00
check_tx_counts ( final = False )
2023-10-04 11:58:47 -03:00
normal , snapshot = n1 . getchainstates ( ) [ " chainstates " ]
assert_equal ( normal [ ' blocks ' ] , START_HEIGHT )
assert_equal ( normal . get ( ' snapshot_blockhash ' ) , None )
assert_equal ( normal [ ' validated ' ] , True )
assert_equal ( snapshot [ ' blocks ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( snapshot [ ' snapshot_blockhash ' ] , dump_output [ ' base_hash ' ] )
assert_equal ( snapshot [ ' validated ' ] , False )
2021-06-17 16:09:38 -04:00
assert_equal ( n1 . getblockchaininfo ( ) [ " blocks " ] , SNAPSHOT_BASE_HEIGHT )
2024-02-02 12:59:24 -03:00
self . log . info ( " Submit a stale block that forked off the chain before the snapshot " )
# Normally a block like this would not be downloaded, but if it is
# submitted early before the background chain catches up to the fork
# point, it winds up in m_blocks_unlinked and triggers a corner case
# that previously crashed CheckBlockIndex.
n1 . submitblock ( stale_block )
n1 . getchaintips ( )
n1 . getblock ( stale_hash )
2024-01-09 21:07:17 -03:00
self . log . info ( " Submit a spending transaction for a snapshot chainstate coin to the mempool " )
# spend the coinbase output of the first block that is not available on node1
spend_coin_blockhash = n1 . getblockhash ( START_HEIGHT + 1 )
assert_raises_rpc_error ( - 1 , " Block not found on disk " , n1 . getblock , spend_coin_blockhash )
prev_tx = n0 . getblock ( spend_coin_blockhash , 3 ) [ ' tx ' ] [ 0 ]
prevout = { " txid " : prev_tx [ ' txid ' ] , " vout " : 0 , " scriptPubKey " : prev_tx [ ' vout ' ] [ 0 ] [ ' scriptPubKey ' ] [ ' hex ' ] }
privkey = n0 . get_deterministic_priv_key ( ) . key
raw_tx = n1 . createrawtransaction ( [ prevout ] , { getnewdestination ( ) [ 2 ] : 24.99 } )
signed_tx = n1 . signrawtransactionwithkey ( raw_tx , [ privkey ] , [ prevout ] ) [ ' hex ' ]
signed_txid = tx_from_hex ( signed_tx ) . rehash ( )
assert n1 . gettxout ( prev_tx [ ' txid ' ] , 0 ) is not None
n1 . sendrawtransaction ( signed_tx )
assert signed_txid in n1 . getrawmempool ( )
assert not n1 . gettxout ( prev_tx [ ' txid ' ] , 0 )
2021-06-17 16:09:38 -04:00
PAUSE_HEIGHT = FINAL_HEIGHT - 40
self . log . info ( " Restarting node to stop at height %d " , PAUSE_HEIGHT )
self . restart_node ( 1 , extra_args = [
f " -stopatheight= { PAUSE_HEIGHT } " , * self . extra_args [ 1 ] ] )
2024-09-03 10:00:55 -04:00
# Upon restart during snapshot tip sync, the node must remain in 'limited' mode.
self . assert_only_network_limited_service ( n1 )
2021-06-17 16:09:38 -04:00
# Finally connect the nodes and let them sync.
2023-10-04 12:05:27 -03:00
#
# Set `wait_for_connect=False` to avoid a race between performing connection
# assertions and the -stopatheight tripping.
self . connect_nodes ( 0 , 1 , wait_for_connect = False )
2021-06-17 16:09:38 -04:00
n1 . wait_until_stopped ( timeout = 5 )
self . log . info ( " Checking that blocks are segmented on disk " )
assert self . has_blockfile ( n1 , " 00000 " ) , " normal blockfile missing "
assert self . has_blockfile ( n1 , " 00001 " ) , " assumed blockfile missing "
assert not self . has_blockfile ( n1 , " 00002 " ) , " too many blockfiles "
self . log . info ( " Restarted node before snapshot validation completed, reloading... " )
self . restart_node ( 1 , extra_args = self . extra_args [ 1 ] )
2024-02-02 12:59:24 -03:00
2024-09-03 10:00:55 -04:00
# Upon restart, the node must remain in 'limited' mode
self . assert_only_network_limited_service ( n1 )
2024-02-02 12:59:24 -03:00
# Send snapshot block to n1 out of order. This makes the test less
# realistic because normally the snapshot block is one of the last
# blocks downloaded, but its useful to test because it triggers more
# corner cases in ReceivedBlockTransactions() and CheckBlockIndex()
# setting and testing nChainTx values, and it exposed previous bugs.
snapshot_hash = n0 . getblockhash ( SNAPSHOT_BASE_HEIGHT )
snapshot_block = n0 . getblock ( snapshot_hash , 0 )
n1 . submitblock ( snapshot_block )
2021-06-17 16:09:38 -04:00
self . connect_nodes ( 0 , 1 )
self . log . info ( f " Ensuring snapshot chain syncs to tip. ( { FINAL_HEIGHT } ) " )
2023-10-02 19:55:10 -03:00
self . wait_until ( lambda : n1 . getchainstates ( ) [ ' chainstates ' ] [ - 1 ] [ ' blocks ' ] == FINAL_HEIGHT )
2021-06-17 16:09:38 -04:00
self . sync_blocks ( nodes = ( n0 , n1 ) )
self . log . info ( " Ensuring background validation completes " )
2023-10-02 19:55:10 -03:00
self . wait_until ( lambda : len ( n1 . getchainstates ( ) [ ' chainstates ' ] ) == 1 )
2021-06-17 16:09:38 -04:00
2024-09-03 10:00:55 -04:00
# Since n1 is a pruned node, it will not signal NODE_NETWORK after
# completing the background sync.
self . assert_only_network_limited_service ( n1 )
2021-06-17 16:09:38 -04:00
# Ensure indexes have synced.
completed_idx_state = {
' basic block filter index ' : COMPLETE_IDX ,
' coinstatsindex ' : COMPLETE_IDX ,
}
self . wait_until ( lambda : n1 . getindexinfo ( ) == completed_idx_state )
2024-02-05 18:16:14 -03:00
self . log . info ( " Re-check nTx and nChainTx values " )
check_tx_counts ( final = True )
2021-06-17 16:09:38 -04:00
for i in ( 0 , 1 ) :
n = self . nodes [ i ]
self . log . info ( f " Restarting node { i } to ensure (Check|Load)BlockIndex passes " )
self . restart_node ( i , extra_args = self . extra_args [ i ] )
assert_equal ( n . getblockchaininfo ( ) [ " blocks " ] , FINAL_HEIGHT )
2023-10-04 11:58:47 -03:00
chainstate , = n . getchainstates ( ) [ ' chainstates ' ]
assert_equal ( chainstate [ ' blocks ' ] , FINAL_HEIGHT )
2021-06-17 16:09:38 -04:00
if i != 0 :
# Ensure indexes have synced for the assumeutxo node
self . wait_until ( lambda : n . getindexinfo ( ) == completed_idx_state )
# Node 2: all indexes + reindex
# -----------------------------
self . log . info ( " -- Testing all indexes + reindex " )
assert_equal ( n2 . getblockcount ( ) , START_HEIGHT )
2024-09-03 10:00:55 -04:00
assert ' NETWORK ' in n2 . getnetworkinfo ( ) [ ' localservicesnames ' ] # sanity check
2021-06-17 16:09:38 -04:00
self . log . info ( f " Loading snapshot into third node from { dump_output [ ' path ' ] } " )
loaded = n2 . loadtxoutset ( dump_output [ ' path ' ] )
assert_equal ( loaded [ ' coins_loaded ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( loaded [ ' base_height ' ] , SNAPSHOT_BASE_HEIGHT )
2024-09-03 10:00:55 -04:00
# Even though n2 is a full node, it will unset the 'NETWORK' service flag during snapshot loading.
# This indicates other peers that the node will temporarily not provide historical blocks.
self . log . info ( " Check node2 updated the local services during snapshot load " )
self . assert_only_network_limited_service ( n2 )
2024-03-25 13:51:55 -03:00
for reindex_arg in [ ' -reindex=1 ' , ' -reindex-chainstate=1 ' ] :
self . log . info ( f " Check that restarting with { reindex_arg } will delete the snapshot chainstate " )
self . restart_node ( 2 , extra_args = [ reindex_arg , * self . extra_args [ 2 ] ] )
assert_equal ( 1 , len ( n2 . getchainstates ( ) [ " chainstates " ] ) )
for i in range ( 1 , 300 ) :
block = n0 . getblock ( n0 . getblockhash ( i ) , 0 )
n2 . submitheader ( block )
loaded = n2 . loadtxoutset ( dump_output [ ' path ' ] )
assert_equal ( loaded [ ' coins_loaded ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( loaded [ ' base_height ' ] , SNAPSHOT_BASE_HEIGHT )
2023-10-04 11:58:47 -03:00
normal , snapshot = n2 . getchainstates ( ) [ ' chainstates ' ]
assert_equal ( normal [ ' blocks ' ] , START_HEIGHT )
assert_equal ( normal . get ( ' snapshot_blockhash ' ) , None )
assert_equal ( normal [ ' validated ' ] , True )
assert_equal ( snapshot [ ' blocks ' ] , SNAPSHOT_BASE_HEIGHT )
assert_equal ( snapshot [ ' snapshot_blockhash ' ] , dump_output [ ' base_hash ' ] )
assert_equal ( snapshot [ ' validated ' ] , False )
2021-06-17 16:09:38 -04:00
2024-04-26 09:50:00 -04:00
self . log . info ( " Check that loading the snapshot again will fail because there is already an active snapshot. " )
2024-06-18 08:24:55 -04:00
msg = " Unable to load UTXO snapshot: Can ' t activate a snapshot-based chainstate more than once "
assert_raises_rpc_error ( - 32603 , msg , n2 . loadtxoutset , dump_output [ ' path ' ] )
2024-04-26 09:50:00 -04:00
2024-09-03 10:00:55 -04:00
# Upon restart, the node must stay in 'limited' mode until the background
# chain sync completes.
self . restart_node ( 2 , extra_args = self . extra_args [ 2 ] )
self . assert_only_network_limited_service ( n2 )
2021-06-17 16:09:38 -04:00
self . connect_nodes ( 0 , 2 )
2023-10-02 19:55:10 -03:00
self . wait_until ( lambda : n2 . getchainstates ( ) [ ' chainstates ' ] [ - 1 ] [ ' blocks ' ] == FINAL_HEIGHT )
2024-06-19 09:07:55 -04:00
self . sync_blocks ( nodes = ( n0 , n2 ) )
2021-06-17 16:09:38 -04:00
self . log . info ( " Ensuring background validation completes " )
2023-10-02 19:55:10 -03:00
self . wait_until ( lambda : len ( n2 . getchainstates ( ) [ ' chainstates ' ] ) == 1 )
2021-06-17 16:09:38 -04:00
2024-09-03 10:00:55 -04:00
# Once background chain sync completes, the full node must start offering historical blocks again.
assert { ' NETWORK ' , ' NETWORK_LIMITED ' } . issubset ( n2 . getnetworkinfo ( ) [ ' localservicesnames ' ] )
2021-06-17 16:09:38 -04:00
completed_idx_state = {
' basic block filter index ' : COMPLETE_IDX ,
' coinstatsindex ' : COMPLETE_IDX ,
' txindex ' : COMPLETE_IDX ,
}
self . wait_until ( lambda : n2 . getindexinfo ( ) == completed_idx_state )
for i in ( 0 , 2 ) :
n = self . nodes [ i ]
self . log . info ( f " Restarting node { i } to ensure (Check|Load)BlockIndex passes " )
self . restart_node ( i , extra_args = self . extra_args [ i ] )
assert_equal ( n . getblockchaininfo ( ) [ " blocks " ] , FINAL_HEIGHT )
2023-10-04 11:58:47 -03:00
chainstate , = n . getchainstates ( ) [ ' chainstates ' ]
assert_equal ( chainstate [ ' blocks ' ] , FINAL_HEIGHT )
2021-06-17 16:09:38 -04:00
if i != 0 :
# Ensure indexes have synced for the assumeutxo node
self . wait_until ( lambda : n . getindexinfo ( ) == completed_idx_state )
self . log . info ( " Test -reindex-chainstate of an assumeutxo-synced node " )
self . restart_node ( 2 , extra_args = [
' -reindex-chainstate=1 ' , * self . extra_args [ 2 ] ] )
assert_equal ( n2 . getblockchaininfo ( ) [ " blocks " ] , FINAL_HEIGHT )
2023-10-02 19:55:10 -03:00
self . wait_until ( lambda : n2 . getblockcount ( ) == FINAL_HEIGHT )
2021-06-17 16:09:38 -04:00
self . log . info ( " Test -reindex of an assumeutxo-synced node " )
self . restart_node ( 2 , extra_args = [ ' -reindex=1 ' , * self . extra_args [ 2 ] ] )
self . connect_nodes ( 0 , 2 )
2023-10-02 19:55:10 -03:00
self . wait_until ( lambda : n2 . getblockcount ( ) == FINAL_HEIGHT )
2021-06-17 16:09:38 -04:00
2024-06-19 09:07:55 -04:00
self . test_snapshot_in_a_divergent_chain ( dump_output [ ' path ' ] )
2024-07-09 10:07:29 -04:00
# The following test cleans node2 and node3 chain directories.
self . test_sync_from_assumeutxo_node ( snapshot = dump_output )
2024-02-05 18:16:14 -03:00
@dataclass
class Block :
hash : str
tx : int
chain_tx : int
2021-06-17 16:09:38 -04:00
if __name__ == ' __main__ ' :
2024-07-16 17:05:14 -04:00
AssumeutxoTest ( __file__ ) . main ( )