test: Add functional test for bitcoin-chainstate

Adds basic coverage for successfully validating a mainnet block as well
as some duplicate and invalid data.
This commit is contained in:
TheCharlatan 2025-03-06 13:31:08 +01:00
parent 3f9c716e7f
commit ca55613fd1
No known key found for this signature in database
GPG key ID: 9B79B45691DB4173
5 changed files with 66 additions and 0 deletions

View file

@ -19,6 +19,7 @@ function(create_test_config)
set_configure_variable(WITH_BDB USE_BDB) set_configure_variable(WITH_BDB USE_BDB)
set_configure_variable(BUILD_CLI BUILD_BITCOIN_CLI) set_configure_variable(BUILD_CLI BUILD_BITCOIN_CLI)
set_configure_variable(BUILD_UTIL BUILD_BITCOIN_UTIL) set_configure_variable(BUILD_UTIL BUILD_BITCOIN_UTIL)
set_configure_variable(BUILD_UTIL_CHAINSTATE BUILD_BITCOIN_CHAINSTATE)
set_configure_variable(BUILD_WALLET_TOOL BUILD_BITCOIN_WALLET) set_configure_variable(BUILD_WALLET_TOOL BUILD_BITCOIN_WALLET)
set_configure_variable(BUILD_DAEMON BUILD_BITCOIND) set_configure_variable(BUILD_DAEMON BUILD_BITCOIND)
set_configure_variable(BUILD_FUZZ_BINARY ENABLE_FUZZ_BINARY) set_configure_variable(BUILD_FUZZ_BINARY ENABLE_FUZZ_BINARY)

View file

@ -19,6 +19,7 @@ RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py
@USE_BDB_TRUE@USE_BDB=true @USE_BDB_TRUE@USE_BDB=true
@BUILD_BITCOIN_CLI_TRUE@ENABLE_CLI=true @BUILD_BITCOIN_CLI_TRUE@ENABLE_CLI=true
@BUILD_BITCOIN_UTIL_TRUE@ENABLE_BITCOIN_UTIL=true @BUILD_BITCOIN_UTIL_TRUE@ENABLE_BITCOIN_UTIL=true
@BUILD_BITCOIN_CHAINSTATE_TRUE@ENABLE_BITCOIN_CHAINSTATE=true
@BUILD_BITCOIN_WALLET_TRUE@ENABLE_WALLET_TOOL=true @BUILD_BITCOIN_WALLET_TRUE@ENABLE_WALLET_TOOL=true
@BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=true @BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=true
@ENABLE_FUZZ_BINARY_TRUE@ENABLE_FUZZ_BINARY=true @ENABLE_FUZZ_BINARY_TRUE@ENABLE_FUZZ_BINARY=true

View file

@ -88,6 +88,10 @@ class Binaries:
"Return argv array that should be used to invoke bitcoin-wallet" "Return argv array that should be used to invoke bitcoin-wallet"
return self._argv(self.paths.bitcoinwallet) return self._argv(self.paths.bitcoinwallet)
def chainstate_argv(self):
"Return argv array that should be used to invoke bitcoin-chainstate"
return self._argv(self.paths.bitcoinchainstate)
def _argv(self, bin_path): def _argv(self, bin_path):
"""Return argv array that should be used to invoke the command. """Return argv array that should be used to invoke the command.
Normally this will return binary paths directly from the paths object, Normally this will return binary paths directly from the paths object,
@ -291,6 +295,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"bitcoind": ("bitcoind", "BITCOIND"), "bitcoind": ("bitcoind", "BITCOIND"),
"bitcoin-cli": ("bitcoincli", "BITCOINCLI"), "bitcoin-cli": ("bitcoincli", "BITCOINCLI"),
"bitcoin-util": ("bitcoinutil", "BITCOINUTIL"), "bitcoin-util": ("bitcoinutil", "BITCOINUTIL"),
"bitcoin-chainstate": ("bitcoinchainstate", "BITCOINCHAINSTATE"),
"bitcoin-wallet": ("bitcoinwallet", "BITCOINWALLET"), "bitcoin-wallet": ("bitcoinwallet", "BITCOINWALLET"),
} }
for binary, [attribute_name, env_variable_name] in binaries.items(): for binary, [attribute_name, env_variable_name] in binaries.items():
@ -1022,6 +1027,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
if not self.is_bitcoin_util_compiled(): if not self.is_bitcoin_util_compiled():
raise SkipTest("bitcoin-util has not been compiled") raise SkipTest("bitcoin-util has not been compiled")
def skip_if_no_bitcoin_chainstate(self):
"""Skip the running test if bitcoin-chainstate has not been compiled."""
if not self.is_bitcoin_chainstate_compiled():
raise SkipTest("bitcoin-chainstate has not been compiled")
def skip_if_no_cli(self): def skip_if_no_cli(self):
"""Skip the running test if bitcoin-cli has not been compiled.""" """Skip the running test if bitcoin-cli has not been compiled."""
if not self.is_cli_compiled(): if not self.is_cli_compiled():
@ -1073,6 +1083,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
"""Checks whether bitcoin-util was compiled.""" """Checks whether bitcoin-util was compiled."""
return self.config["components"].getboolean("ENABLE_BITCOIN_UTIL") return self.config["components"].getboolean("ENABLE_BITCOIN_UTIL")
def is_bitcoin_chainstate_compiled(self):
"""Checks whether bitcoin-chainstate was compiled."""
return self.config["components"].getboolean("ENABLE_BITCOIN_CHAINSTATE")
def is_zmq_compiled(self): def is_zmq_compiled(self):
"""Checks whether the zmq module was compiled.""" """Checks whether the zmq module was compiled."""
return self.config["components"].getboolean("ENABLE_ZMQ") return self.config["components"].getboolean("ENABLE_ZMQ")

View file

@ -193,6 +193,7 @@ BASE_SCRIPTS = [
'feature_bind_extra.py', 'feature_bind_extra.py',
'mempool_resurrect.py', 'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock', 'wallet_txn_doublespend.py --mineblock',
'tool_bitcoin_chainstate.py',
'tool_wallet.py --legacy-wallet', 'tool_wallet.py --legacy-wallet',
'tool_wallet.py --legacy-wallet --bdbro', 'tool_wallet.py --legacy-wallet --bdbro',
'tool_wallet.py --legacy-wallet --bdbro --swap-bdb-endian', 'tool_wallet.py --legacy-wallet --bdbro --swap-bdb-endian',

View file

@ -0,0 +1,49 @@
#!/usr/bin/env python3
# Copyright (c) 2022-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import subprocess
from test_framework.test_framework import BitcoinTestFramework
class BitcoinChainstateTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_bitcoin_chainstate()
def set_test_params(self):
self.setup_clean_chain = True
self.chain = ""
self.num_nodes = 1
# Set prune to avoid disk space warning.
self.extra_args = [["-prune=550"]]
def add_block(self, datadir, input, expected_stderr):
proc = subprocess.Popen(
self.get_binaries().chainstate_argv() + [datadir],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = proc.communicate(input=input + "\n", timeout=5)
self.log.debug("STDOUT: {0}".format(stdout.strip("\n")))
self.log.info("STDERR: {0}".format(stderr.strip("\n")))
if expected_stderr not in stderr:
raise AssertionError(f"Expected stderr output {expected_stderr} does not partially match stderr:\n{stderr}")
def run_test(self):
node = self.nodes[0]
datadir = node.cli.datadir
node.stop_node()
self.log.info(f"Testing bitcoin-chainstate {self.get_binaries().chainstate_argv()} with datadir: {datadir}")
block_one = "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"
self.add_block(datadir, block_one, "Block has not yet been rejected")
self.add_block(datadir, block_one, "duplicate")
self.add_block(datadir, "00", "Block decode failed")
self.add_block(datadir, "", "Empty line found")
if __name__ == "__main__":
BitcoinChainstateTest(__file__).main()