From bbc8f1e0a7e5739f15b2e646a4ace338083309a3 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 24 Feb 2026 08:31:38 -0500 Subject: [PATCH] ipc mining: Prevent ``Assertion `m_node.chainman' failed`` errors on early startup This fixes ``Assertion `m_node.chainman' failed`` errors first reported https://github.com/bitcoin/bitcoin/issues/33994#issuecomment-3602551596 when IPC mining methods are called before ChainstateManager is loaded. The fix works by making the `Init.makeMining` method block until chainstate data is loaded. --- src/init.cpp | 4 +++- src/interfaces/mining.h | 6 ++++- src/node/interfaces.cpp | 14 +++++++++++- src/test/miner_tests.cpp | 2 +- src/test/testnet4_miner_tests.cpp | 2 +- test/functional/interface_ipc_mining.py | 29 +++++++++++++++++++++++++ 6 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 9d7c76a872f..6a6e7a925b2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1207,7 +1207,9 @@ bool AppInitLockDirectories() bool AppInitInterfaces(NodeContext& node) { node.chain = interfaces::MakeChain(node); - node.mining = interfaces::MakeMining(node); + // Specify wait_loaded=false so internal mining interface can be initialized + // on early startup and does not need to be tied to chainstate loading. + node.mining = interfaces::MakeMining(node, /*wait_loaded=*/false); return true; } diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index 72cc0bcb67b..f4c42e204a7 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -160,7 +160,11 @@ public: }; //! Return implementation of Mining interface. -std::unique_ptr MakeMining(node::NodeContext& node); +//! +//! @param[in] wait_loaded waits for chainstate data to be loaded before +//! returning. Used to prevent external clients from +//! being able to crash the node during startup. +std::unique_ptr MakeMining(node::NodeContext& node, bool wait_loaded=true); } // namespace interfaces diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index afcaeb20c3e..6c61b2105d9 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1017,5 +1017,17 @@ public: namespace interfaces { std::unique_ptr MakeNode(node::NodeContext& context) { return std::make_unique(context); } std::unique_ptr MakeChain(node::NodeContext& context) { return std::make_unique(context); } -std::unique_ptr MakeMining(node::NodeContext& context) { return std::make_unique(context); } +std::unique_ptr MakeMining(node::NodeContext& context, bool wait_loaded) +{ + if (wait_loaded) { + node::KernelNotifications& kernel_notifications(*Assert(context.notifications)); + util::SignalInterrupt& interrupt(*Assert(context.shutdown_signal)); + WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock); + kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) { + return kernel_notifications.m_state.chainstate_loaded || interrupt; + }); + if (interrupt) return nullptr; + } + return std::make_unique(context); +} } // namespace interfaces diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 7f05e4e9d81..0ae8e538cb4 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -67,7 +67,7 @@ struct MinerTestingSetup : public TestingSetup { } std::unique_ptr MakeMining() { - return interfaces::MakeMining(m_node); + return interfaces::MakeMining(m_node, /*wait_loaded=*/false); } }; } // namespace miner_tests diff --git a/src/test/testnet4_miner_tests.cpp b/src/test/testnet4_miner_tests.cpp index 614d2fd63ac..d0aeebe0f21 100644 --- a/src/test/testnet4_miner_tests.cpp +++ b/src/test/testnet4_miner_tests.cpp @@ -22,7 +22,7 @@ namespace testnet4_miner_tests { struct Testnet4MinerTestingSetup : public Testnet4Setup { std::unique_ptr MakeMining() { - return interfaces::MakeMining(m_node); + return interfaces::MakeMining(m_node, /*wait_loaded=*/false); } }; } // namespace testnet4_miner_tests diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 5095583a0e2..b741beeb5b0 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -147,6 +147,34 @@ class IPCMiningTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) + def run_early_startup_test(self): + """Make sure mining.createNewBlock safely returns on early startup as + soon as mining interface is available """ + self.log.info("Running Mining interface early startup test") + + node = self.nodes[0] + self.stop_node(node.index) + node.start() + + async def async_routine(): + while True: + try: + ctx, mining = await self.make_mining_ctx() + break + except (ConnectionRefusedError, FileNotFoundError): + # Poll quickly to connect as soon as socket becomes + # available but without using a lot of CPU + await asyncio.sleep(0.005) + + opts = self.capnp_modules['mining'].BlockCreateOptions() + await mining.createNewBlock(ctx, opts) + + asyncio.run(capnp.run(async_routine())) + + # Reconnect nodes so next tests are happy + node.wait_for_rpc_connection() + self.connect_nodes(1, 0) + def run_block_template_test(self): """Test BlockTemplate interface methods.""" self.log.info("Running BlockTemplate interface test") @@ -374,6 +402,7 @@ class IPCMiningTest(BitcoinTestFramework): self.miniwallet = MiniWallet(self.nodes[0]) self.default_block_create_options = self.capnp_modules['mining'].BlockCreateOptions() self.run_mining_interface_test() + self.run_early_startup_test() self.run_block_template_test() self.run_coinbase_and_submission_test() self.run_ipc_option_override_test()