Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/bitcoin-chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,18 @@ class TestKernelNotifications : public KernelNotifications
int main(int argc, char* argv[])
{
// SETUP: Argument parsing and handling
if (argc != 2) {
const bool has_regtest_flag{argc == 3 && std::string(argv[1]) == "-regtest"};
if (argc < 2 || argc > 3 || (argc == 3 && !has_regtest_flag)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking lately whether we can reuse some of our own common utilities for these small, kernel-based applications. These small, single use applications should be able to use our usual utilities, while still benefiting from a clearer kernel interface. I think we are at a point now, where we can confidently mix between the two without risking introducing an entanglement regression between what the kernel requires and what new code might introduce in it. For this reason, I added some basic argument handling on my branch over here: https://github.com/sedited/bitcoin/tree/bitcoin_chainstate_args , that might be interesting for this change here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks! It seems I can cherry-pick your commit as is to replace bfceb4c?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks! It seems I can cherry-pick your commit as is to replace bfceb4c?

Sure, if you think it is a good idea too :)

@stringintech stringintech Dec 18, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reviewers:

As discussed offline, adding ArgsManager and the G_TRANSLATION_FUN definition that should be added because of it, is causing different compile/link issues since the symbol is also defined in the kernel library and @sedited is suggesting to have the symbol removed from there.

So it seems adapting bitcoin-chainstate.cpp to use the ArgsManager could be done in a follow-up.

std::cerr
<< "Usage: " << argv[0] << " DATADIR" << std::endl
<< "Usage: " << argv[0] << " [-regtest] DATADIR" << std::endl
<< "Display DATADIR information, and process hex-encoded blocks on standard input." << std::endl
<< "Uses mainnet parameters by default, regtest with -regtest flag" << std::endl
<< std::endl
<< "IMPORTANT: THIS EXECUTABLE IS EXPERIMENTAL, FOR TESTING ONLY, AND EXPECTED TO" << std::endl
<< " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl;
return 1;
}
std::filesystem::path abs_datadir{std::filesystem::absolute(argv[1])};
std::filesystem::path abs_datadir{std::filesystem::absolute(argv[argc-1])};
std::filesystem::create_directories(abs_datadir);

btck_LoggingOptions logging_options = {
Expand All @@ -169,7 +171,7 @@ int main(int argc, char* argv[])
Logger logger{std::make_unique<KernelLog>()};

ContextOptions options{};
ChainParams params{ChainType::MAINNET};
ChainParams params{has_regtest_flag ? ChainType::REGTEST : ChainType::MAINNET};
options.SetChainParams(params);

options.SetNotifications(std::make_shared<TestKernelNotifications>());
Expand Down
2 changes: 1 addition & 1 deletion src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ class CRegTestParams : public CChainParams
.blockhash = consteval_ctor(uint256{"385901ccbd69dff6bbd00065d01fb8a9e464dede7cfe0372443884f9b1dcf6b9"}),
},
{
// For use by test/functional/feature_assumeutxo.py
// For use by test/functional/feature_assumeutxo.py and test/functional/tool_bitcoin_chainstate.py
.height = 299,
.hash_serialized = AssumeutxoHash{uint256{"d2b051ff5e8eef46520350776f4100dd710a63447a8e01d917e92e79751a63e2"}},
.m_chain_tx_count = 334,
Expand Down
2 changes: 1 addition & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6248,7 +6248,7 @@ Chainstate& ChainstateManager::AddChainstate(std::unique_ptr<Chainstate> chainst

// Transfer possession of the mempool to the chainstate.
// Mempool is empty at this point because we're still in IBD.
assert(prev_chainstate.m_mempool->size() == 0);
assert(!prev_chainstate.m_mempool || prev_chainstate.m_mempool->size() == 0);
assert(!curr_chainstate.m_mempool);
std::swap(curr_chainstate.m_mempool, prev_chainstate.m_mempool);
return curr_chainstate;
Expand Down
95 changes: 76 additions & 19 deletions test/functional/tool_bitcoin_chainstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,58 @@
# 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.
"""Test bitcoin-chainstate tool functionality

Test basic block processing via bitcoin-chainstate tool, including detecting
duplicates and malformed input.

Test that bitcoin-chainstate can load a datadir initialized with an assumeutxo
snapshot and extend the snapshot chain with new blocks.
"""

import subprocess

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet

START_HEIGHT = 199
# Hardcoded in regtest chainparams
SNAPSHOT_BASE_BLOCK_HEIGHT = 299
SNAPSHOT_BASE_BLOCK_HASH = "7cc695046fec709f8c9394b6f928f81e81fd3ac20977bb68760fa1faa7916ea2"


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"]]
"""Use the pregenerated, deterministic chain up to height 199."""
self.num_nodes = 2

def add_block(self, datadir, input, expected_stderr):
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(2)
self.start_nodes()

def generate_snapshot_chain(self):
self.log.info(f"Generate deterministic chain up to block {SNAPSHOT_BASE_BLOCK_HEIGHT} for node0 while node1 disconnected")
n0 = self.nodes[0]
assert_equal(n0.getblockcount(), START_HEIGHT)
n0.setmocktime(n0.getblockheader(n0.getbestblockhash())['time'])
mini_wallet = MiniWallet(n0)
for i in range(SNAPSHOT_BASE_BLOCK_HEIGHT - n0.getblockchaininfo()["blocks"]):
if i % 3 == 0:
mini_wallet.send_self_transfer(from_node=n0)
self.generate(n0, nblocks=1, sync_fun=self.no_op)
assert_equal(n0.getblockcount(), SNAPSHOT_BASE_BLOCK_HEIGHT)
assert_equal(n0.getbestblockhash(), SNAPSHOT_BASE_BLOCK_HASH)
return n0.dumptxoutset('utxos.dat', "latest")

def add_block(self, datadir, input, expected_stderr=None, expected_stdout=None):
proc = subprocess.Popen(
self.get_binaries().chainstate_argv() + [datadir],
self.get_binaries().chainstate_argv() + ["-regtest", datadir],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Expand All @@ -30,20 +63,44 @@ def add_block(self, datadir, input, expected_stderr):
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}")
if expected_stderr is not None and expected_stderr not in stderr:
raise AssertionError(f"Expected stderr output '{expected_stderr}' does not partially match stderr:\n{stderr}")
if expected_stdout is not None and expected_stdout not in stdout:
raise AssertionError(f"Expected stdout output '{expected_stdout}' does not partially match stdout:\n{stdout}")

def basic_test(self):
n0 = self.nodes[0]
n1 = self.nodes[1]
datadir = n1.chain_path
n1.stop_node()
block = n0.getblock(n0.getblockhash(START_HEIGHT+1), 0)
self.log.info(f"Test bitcoin-chainstate {self.get_binaries().chainstate_argv()} with datadir: {datadir}")
self.add_block(datadir, block, expected_stderr="Block has not yet been rejected")
self.add_block(datadir, block, expected_stderr="duplicate")
self.add_block(datadir, "00", expected_stderr="Block decode failed")
self.add_block(datadir, "", expected_stderr="Empty line found")

def assumeutxo_test(self, dump_output_path):
n0 = self.nodes[0]
n1 = self.nodes[1]
self.start_node(1)
self.log.info("Submit headers for new blocks to node1, then load the snapshot so it activates")
for height in range(START_HEIGHT+2, SNAPSHOT_BASE_BLOCK_HEIGHT+1):
block = n0.getblock(n0.getblockhash(height), 0)
n1.submitheader(block)
assert_equal(n1.getblockcount(), START_HEIGHT+1)
loaded = n1.loadtxoutset(dump_output_path)
assert_equal(loaded['base_height'], SNAPSHOT_BASE_BLOCK_HEIGHT)
datadir = n1.chain_path
n1.stop_node()
self.log.info(f"Test bitcoin-chainstate {self.get_binaries().chainstate_argv()} with an assumeutxo datadir: {datadir}")
new_tip_hash = self.generate(n0, nblocks=1, sync_fun=self.no_op)[0]
self.add_block(datadir, n0.getblock(new_tip_hash, 0), expected_stdout="Block tip changed")

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")
dump_output = self.generate_snapshot_chain()
self.basic_test()
self.assumeutxo_test(dump_output['path'])

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