diff --git a/chronik/chronik-cpp/chronik_validationinterface.cpp b/chronik/chronik-cpp/chronik_validationinterface.cpp --- a/chronik/chronik-cpp/chronik_validationinterface.cpp +++ b/chronik/chronik-cpp/chronik_validationinterface.cpp @@ -72,9 +72,11 @@ } void StopChronikValidationInterface() { - g_chronik_validation_interface->Unregister(); - // Reset so the Box is dropped and all handles are released. - g_chronik_validation_interface.reset(); + if (g_chronik_validation_interface) { + g_chronik_validation_interface->Unregister(); + // Reset so the Box is dropped and all handles are released. + g_chronik_validation_interface.reset(); + } } } // namespace chronik diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -250,10 +250,6 @@ node.connman->Stop(); } -#if ENABLE_CHRONIK - chronik::Stop(); -#endif - StopTorControl(); // After everything has been shut down, but before things get flushed, stop @@ -296,6 +292,10 @@ // generate CValidationInterface callbacks, flush them... GetMainSignals().FlushBackgroundCallbacks(); +#if ENABLE_CHRONIK + chronik::Stop(); +#endif + // Stop and delete all indexes only after flushing background callbacks. if (g_txindex) { g_txindex->Stop(); diff --git a/test/functional/chronik_shutdown.py b/test/functional/chronik_shutdown.py new file mode 100644 --- /dev/null +++ b/test/functional/chronik_shutdown.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test if Chronik properly shuts down. + +This tests a case where, when processing large blocks with lots of ins/outs, Chronik +would lag behind. When shutting down at that moment, Chronik would continue to process +blocks which, if we're not careful, might have been freed in the node already, resulting +in a segfault. +""" + +from test_framework.blocktools import ( + GENESIS_BLOCK_HASH, + create_block, + create_coinbase, + make_conform_to_ctor, +) +from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut, msg_block +from test_framework.p2p import P2PDataStore +from test_framework.script import OP_TRUE, CScript +from test_framework.test_framework import BitcoinTestFramework + + +class ChronikShutdown(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [["-chronik"]] + self.rpc_timeout = 240 + + def skip_test_if_missing_module(self): + self.skip_if_no_chronik() + + def run_test(self): + mocktime = 1300000000 + node = self.nodes[0] + node.setmocktime(mocktime) + peer = node.add_p2p_connection(P2PDataStore()) + self.wait_until(lambda: peer.is_connected, timeout=10) + + last_block_hash = GENESIS_BLOCK_HASH + coinbase_txs = [] + for i in range(1, 200): + coinbase_tx = create_coinbase(i) + coinbase_tx.vout[0].scriptPubKey = CScript([OP_TRUE]) + coinbase_tx.rehash() + coinbase_txs.append(coinbase_tx) + block = create_block(int(last_block_hash, 16), coinbase_tx, mocktime + i) + if i > 101: + txid = coinbase_txs[i - 101].sha256 + fan_tx = CTransaction() + fan_tx.vin = [CTxIn(COutPoint(txid, 0))] + fan_tx.vout = [CTxOut(1000, CScript([OP_TRUE]))] * 8000 + fan_tx.rehash() + block.vtx.append(fan_tx) + for i in range(0, 7997, 3): + tx = CTransaction() + tx.vin = [ + CTxIn(COutPoint(fan_tx.sha256, j)) for j in range(i, i + 3) + ] + tx.vout = [CTxOut(1000, CScript([OP_TRUE]))] + tx.rehash() + block.vtx.append(tx) + make_conform_to_ctor(block) + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + last_block_hash = block.hash + peer.send_message(msg_block(block)) + + self.stop_nodes() + + +if __name__ == "__main__": + ChronikShutdown().main()