I think a goal for the mining interface should be to be self-contained and not require to miners to call other interfaces to connect and get startup information, and the wait_loaded=true behavior makes it not possible to add mining methods that return startup progress. Having the wait_loaded option also makes the mining interface different from other interfaces internally. (Dropping the option and always using wait_loaded=true behavior would not really solve this problem either since it would require moving the MakeMining call in init code,)
0--- a/src/init.cpp
1+++ b/src/init.cpp
2@@ -1207,9 +1207,7 @@ bool AppInitLockDirectories()
3 bool AppInitInterfaces(NodeContext& node)
4 {
5 node.chain = interfaces::MakeChain(node);
6- // Specify wait_loaded=false so internal mining interface can be initialized
7- // on early startup and does not need to be tied to chainstate loading.
8- node.mining = interfaces::MakeMining(node, /*wait_loaded=*/false);
9+ node.mining = interfaces::MakeMining(node);
10 return true;
11 }
12
13--- a/src/interfaces/mining.h
14+++ b/src/interfaces/mining.h
15@@ -160,11 +160,7 @@ public:
16 };
17
18 //! Return implementation of Mining interface.
19-//!
20-//! [@param](/bitcoin-bitcoin/contributor/param/)[in] wait_loaded waits for chainstate data to be loaded before
21-//! returning. Used to prevent external clients from
22-//! being able to crash the node during startup.
23-std::unique_ptr<Mining> MakeMining(node::NodeContext& node, bool wait_loaded=true);
24+std::unique_ptr<Mining> MakeMining(node::NodeContext& node);
25
26 } // namespace interfaces
27
28--- a/src/node/interfaces.cpp
29+++ b/src/node/interfaces.cpp
30@@ -939,22 +939,24 @@ public:
31
32 bool isTestChain() override
33 {
34- return chainman().GetParams().IsTestChain();
35+ return Params().IsTestChain();
36 }
37
38 bool isInitialBlockDownload() override
39 {
40- return chainman().IsInitialBlockDownload();
41+ ChainstateManager* chainman{this->chainman()};
42+ return !chainman || chainman->IsInitialBlockDownload();
43 }
44
45 std::optional<BlockRef> getTip() override
46 {
47- return GetTip(chainman());
48+ ChainstateManager* chainman{this->chainman()};
49+ return chainman ? GetTip(*chainman) : std::nullopt;
50 }
51
52 std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
53 {
54- return WaitTipChanged(chainman(), notifications(), current_tip, timeout, m_interrupt_mining);
55+ return WaitTipChanged(notifications(), m_node.chainman, *Assert(m_node.shutdown_signal), current_tip, timeout, m_interrupt_mining);
56 }
57
58 std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options, bool cooldown) override
59@@ -971,6 +973,7 @@ public:
60
61 // Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
62 std::optional<BlockRef> maybe_tip{waitTipChanged(uint256::ZERO, MillisecondsDouble::max())};
63+ ChainstateManager& chainman{*Assert(this->chainman())};
64
65 if (!maybe_tip) return {};
66
67@@ -980,18 +983,18 @@ public:
68 // is useful in general, it's gated behind the cooldown argument,
69 // because on regtest and single miner signets this would wait
70 // forever if no block was mined in the past day.
71- while (chainman().IsInitialBlockDownload()) {
72+ while (chainman.IsInitialBlockDownload()) {
73 maybe_tip = waitTipChanged(maybe_tip->hash, MillisecondsDouble{1000});
74- if (!maybe_tip || chainman().m_interrupt || WITH_LOCK(notifications().m_tip_block_mutex, return m_interrupt_mining)) return {};
75+ if (!maybe_tip || chainman.m_interrupt || WITH_LOCK(notifications().m_tip_block_mutex, return m_interrupt_mining)) return {};
76 }
77
78 // Also wait during the final catch-up moments after IBD.
79- if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip, m_interrupt_mining)) return {};
80+ if (!CooldownIfHeadersAhead(chainman, notifications(), *maybe_tip, m_interrupt_mining)) return {};
81 }
82
83 BlockAssembler::Options assemble_options{options};
84 ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
85- return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
86+ return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman.ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
87 }
88
89 void interrupt() override
90@@ -1001,16 +1004,26 @@ public:
91
92 bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
93 {
94- LOCK(chainman().GetMutex());
95- BlockValidationState state{TestBlockValidity(chainman().ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*check_merkle_root=*/options.check_merkle_root)};
96+ ChainstateManager* chainman{this->chainman()};
97+ if (!chainman) {
98+ reason = debug = "Chainstate not loaded.";
99+ return false;
100+ }
101+ LOCK(chainman->GetMutex());
102+ BlockValidationState state{TestBlockValidity(chainman->ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*check_merkle_root=*/options.check_merkle_root)};
103 reason = state.GetRejectReason();
104 debug = state.GetDebugMessage();
105 return state.IsValid();
106 }
107
108 NodeContext* context() override { return &m_node; }
109- ChainstateManager& chainman() { return *Assert(m_node.chainman); }
110 KernelNotifications& notifications() { return *Assert(m_node.notifications); }
111+ ChainstateManager* chainman(bool wait=false)
112+ {
113+ LOCK(notifications().m_tip_block_mutex);
114+ return notifications().m_state.chainstate_loaded ? m_node.chainman.get() : nullptr;
115+ }
116+
117 // Treat as if guarded by notifications().m_tip_block_mutex
118 bool m_interrupt_mining{false};
119 NodeContext& m_node;
120@@ -1021,17 +1034,5 @@ public:
121 namespace interfaces {
122 std::unique_ptr<Node> MakeNode(node::NodeContext& context) { return std::make_unique<node::NodeImpl>(context); }
123 std::unique_ptr<Chain> MakeChain(node::NodeContext& context) { return std::make_unique<node::ChainImpl>(context); }
124-std::unique_ptr<Mining> MakeMining(node::NodeContext& context, bool wait_loaded)
125-{
126- if (wait_loaded) {
127- node::KernelNotifications& kernel_notifications(*Assert(context.notifications));
128- util::SignalInterrupt& interrupt(*Assert(context.shutdown_signal));
129- WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
130- kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
131- return kernel_notifications.m_state.chainstate_loaded || interrupt;
132- });
133- if (interrupt) return nullptr;
134- }
135- return std::make_unique<node::MinerImpl>(context);
136-}
137+std::unique_ptr<Mining> MakeMining(node::NodeContext& context) { return std::make_unique<node::MinerImpl>(context); }
138 } // namespace interfaces
139--- a/src/node/miner.cpp
140+++ b/src/node/miner.cpp
141@@ -489,7 +489,11 @@ bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& ke
142 return true;
143 }
144
145-std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
146+std::optional<BlockRef> WaitTipChanged(
147+ KernelNotifications& kernel_notifications,
148+ const std::unique_ptr<ChainstateManager>& chainman,
149+ const util::SignalInterrupt& shutdown_signal,
150+ const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
151 {
152 Assume(timeout >= 0ms); // No internal callers should use a negative timeout
153 if (timeout < 0ms) timeout = 0ms;
154@@ -502,18 +506,21 @@ std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifi
155 // always returns valid tip information when possible and only
156 // returns null when shutting down, not when timing out.
157 kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
158- return kernel_notifications.TipBlock() || chainman.m_interrupt || interrupt;
159+ return (kernel_notifications.m_state.chainstate_loaded && kernel_notifications.TipBlock()) || shutdown_signal || interrupt;
160 });
161- if (chainman.m_interrupt || interrupt) {
162+
163+ if (shutdown_signal || interrupt) {
164 interrupt = false;
165 return {};
166 }
167+ Assert(chainman);
168+ Assert(&chainman->m_interrupt == &shutdown_signal);
169 // At this point TipBlock is set, so continue to wait until it is
170 // different then `current_tip` provided by caller.
171 kernel_notifications.m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
172- return Assume(kernel_notifications.TipBlock()) != current_tip || chainman.m_interrupt || interrupt;
173+ return Assume(kernel_notifications.TipBlock()) != current_tip || chainman->m_interrupt || interrupt;
174 });
175- if (chainman.m_interrupt || interrupt) {
176+ if (chainman->m_interrupt || interrupt) {
177 interrupt = false;
178 return {};
179 }
180@@ -521,7 +528,7 @@ std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifi
181
182 // Must release m_tip_block_mutex before getTip() locks cs_main, to
183 // avoid deadlocks.
184- return GetTip(chainman);
185+ return GetTip(*chainman);
186 }
187
188 } // namespace node
189--- a/src/node/miner.h
190+++ b/src/node/miner.h
191@@ -30,6 +30,10 @@ class CScript;
192 class Chainstate;
193 class ChainstateManager;
194
195+namespace util {
196+class SignalInterrupt;
197+} // namespace util
198+
199 namespace Consensus { struct Params; };
200
201 using interfaces::BlockRef;
202@@ -162,7 +166,13 @@ std::optional<BlockRef> GetTip(ChainstateManager& chainman);
203 * Returns the current tip, or nullopt if the node is shutting down or interrupt()
204 * is called.
205 */
206-std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt);
207+std::optional<BlockRef> WaitTipChanged(
208+ KernelNotifications& kernel_notifications,
209+ const std::unique_ptr<ChainstateManager>& chainman_ptr,
210+ const util::SignalInterrupt& shutdown_signal,
211+ const uint256& current_tip,
212+ MillisecondsDouble& timeout,
213+ bool& interrupt);
214
215 /**
216 * Wait while the best known header extends the current chain tip AND at least
217--- a/src/test/miner_tests.cpp
218+++ b/src/test/miner_tests.cpp
219@@ -68,7 +68,7 @@ struct MinerTestingSetup : public TestingSetup {
220 }
221 std::unique_ptr<Mining> MakeMining()
222 {
223- return interfaces::MakeMining(m_node, /*wait_loaded=*/false);
224+ return interfaces::MakeMining(m_node);
225 }
226 };
227 } // namespace miner_tests
228--- a/src/test/testnet4_miner_tests.cpp
229+++ b/src/test/testnet4_miner_tests.cpp
230@@ -22,7 +22,7 @@ namespace testnet4_miner_tests {
231 struct Testnet4MinerTestingSetup : public Testnet4Setup {
232 std::unique_ptr<Mining> MakeMining()
233 {
234- return interfaces::MakeMining(m_node, /*wait_loaded=*/false);
235+ return interfaces::MakeMining(m_node);
236 }
237 };
238 } // namespace testnet4_miner_tests