#!/usr/bin/env python3 # Copyright (c) 2021 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 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 `CRegTestParams::m_assumeutxo_data` in `src/chainparams.cpp`. ## Possible test improvements - TODO: test submitting a transaction and verifying it appears in mempool - TODO: test what happens with -reindex and -reindex-chainstate before the snapshot is validated, and make sure it's deleted successfully. Interesting test cases could be loading an assumeutxo snapshot file with: - TODO: An invalid hash - TODO: Valid hash but invalid snapshot file (bad coin height or truncated file or bad other serialization) - TODO: Valid snapshot file, but referencing an unknown block - TODO: Valid snapshot file, but referencing a snapshot block that turns out to be invalid, or has an invalid parent - TODO: Valid snapshot file and snapshot block, but the block is not on the most-work chain Interesting starting states could be loading a snapshot when the current chain tip is: - TODO: An ancestor of snapshot block - TODO: Not an ancestor of the snapshot block but has less work - TODO: The snapshot block - TODO: A descendant of the snapshot block - TODO: Not an ancestor or a descendant of the snapshot block and has more work """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) import struct 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.""" self.num_nodes = 3 self.rpc_timeout = 120 self.extra_args = [ [], ["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"], ["-txindex=1", "-blockfilterindex=1", "-coinstatsindex=1"], ] def setup_network(self): """Start with the nodes disconnected so that one can generate a snapshot including blocks the other hasn't yet seen.""" self.add_nodes(3) self.start_nodes(extra_args=self.extra_args) 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() bad_snapshot_path = valid_snapshot_path + '.mod' self.log.info(" - snapshot file refering to a block that is not in the assumeutxo parameters") bad_snapshot_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1) with open(bad_snapshot_path, 'wb') as f: # block hash of the snapshot base is stored right at the start (first 32 bytes) f.write(bytes.fromhex(bad_snapshot_block_hash)[::-1] + valid_snapshot_contents[32:]) error_details = f"assumeutxo block hash in snapshot metadata not recognized ({bad_snapshot_block_hash})" assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot, {error_details}", self.nodes[1].loadtxoutset, bad_snapshot_path) self.log.info(" - snapshot file with wrong number of coins") valid_num_coins = struct.unpack("