re: #34661 (review)
Why are we enabling the creation of mining without chainstate being loaded, is it necessary? perhaps just delete the flag and make this standard behavior?
IMO, the wait_loaded option is a hack, and there should be no need for chain and block data to be loaded to get an pointer to the mining interface.
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,)
So your comments made me think it would be better to drop wait_loaded and update mining interface methods to not crash during startup instead. This was not as difficult as I thought and the change is shown in the diff below. I am thinking about whether to make it a PR and curious if it seems reasonable and addresses your concerns
<details><summary>diff</summary>
<p>
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1207,9 +1207,7 @@ bool AppInitLockDirectories()
bool AppInitInterfaces(NodeContext& node)
{
node.chain = interfaces::MakeChain(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);
+ node.mining = interfaces::MakeMining(node);
return true;
}
--- a/src/interfaces/mining.h
+++ b/src/interfaces/mining.h
@@ -160,11 +160,7 @@ public:
};
//! Return implementation of Mining interface.
-//!
-//! [@param](/bitcoin-bitcoin/contributor/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<Mining> MakeMining(node::NodeContext& node, bool wait_loaded=true);
+std::unique_ptr<Mining> MakeMining(node::NodeContext& node);
} // namespace interfaces
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -939,22 +939,24 @@ public:
bool isTestChain() override
{
- return chainman().GetParams().IsTestChain();
+ return Params().IsTestChain();
}
bool isInitialBlockDownload() override
{
- return chainman().IsInitialBlockDownload();
+ ChainstateManager* chainman{this->chainman()};
+ return !chainman || chainman->IsInitialBlockDownload();
}
std::optional<BlockRef> getTip() override
{
- return GetTip(chainman());
+ ChainstateManager* chainman{this->chainman()};
+ return chainman ? GetTip(*chainman) : std::nullopt;
}
std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
{
- return WaitTipChanged(chainman(), notifications(), current_tip, timeout, m_interrupt_mining);
+ return WaitTipChanged(notifications(), m_node.chainman, *Assert(m_node.shutdown_signal), current_tip, timeout, m_interrupt_mining);
}
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options, bool cooldown) override
@@ -971,6 +973,7 @@ public:
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
std::optional<BlockRef> maybe_tip{waitTipChanged(uint256::ZERO, MillisecondsDouble::max())};
+ ChainstateManager& chainman{*Assert(this->chainman())};
if (!maybe_tip) return {};
@@ -980,18 +983,18 @@ public:
// is useful in general, it's gated behind the cooldown argument,
// because on regtest and single miner signets this would wait
// forever if no block was mined in the past day.
- while (chainman().IsInitialBlockDownload()) {
+ while (chainman.IsInitialBlockDownload()) {
maybe_tip = waitTipChanged(maybe_tip->hash, MillisecondsDouble{1000});
- if (!maybe_tip || chainman().m_interrupt || WITH_LOCK(notifications().m_tip_block_mutex, return m_interrupt_mining)) return {};
+ if (!maybe_tip || chainman.m_interrupt || WITH_LOCK(notifications().m_tip_block_mutex, return m_interrupt_mining)) return {};
}
// Also wait during the final catch-up moments after IBD.
- if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip, m_interrupt_mining)) return {};
+ if (!CooldownIfHeadersAhead(chainman, notifications(), *maybe_tip, m_interrupt_mining)) return {};
}
BlockAssembler::Options assemble_options{options};
ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
- return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
+ return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman.ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
}
void interrupt() override
@@ -1001,16 +1004,26 @@ public:
bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
{
- LOCK(chainman().GetMutex());
- BlockValidationState state{TestBlockValidity(chainman().ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*check_merkle_root=*/options.check_merkle_root)};
+ ChainstateManager* chainman{this->chainman()};
+ if (!chainman) {
+ reason = debug = "Chainstate not loaded.";
+ return false;
+ }
+ LOCK(chainman->GetMutex());
+ BlockValidationState state{TestBlockValidity(chainman->ActiveChainstate(), block, /*check_pow=*/options.check_pow, /*check_merkle_root=*/options.check_merkle_root)};
reason = state.GetRejectReason();
debug = state.GetDebugMessage();
return state.IsValid();
}
NodeContext* context() override { return &m_node; }
- ChainstateManager& chainman() { return *Assert(m_node.chainman); }
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
+ ChainstateManager* chainman(bool wait=false)
+ {
+ LOCK(notifications().m_tip_block_mutex);
+ return notifications().m_state.chainstate_loaded ? m_node.chainman.get() : nullptr;
+ }
+
// Treat as if guarded by notifications().m_tip_block_mutex
bool m_interrupt_mining{false};
NodeContext& m_node;
@@ -1021,17 +1034,5 @@ public:
namespace interfaces {
std::unique_ptr<Node> MakeNode(node::NodeContext& context) { return std::make_unique<node::NodeImpl>(context); }
std::unique_ptr<Chain> MakeChain(node::NodeContext& context) { return std::make_unique<node::ChainImpl>(context); }
-std::unique_ptr<Mining> 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<node::MinerImpl>(context);
-}
+std::unique_ptr<Mining> MakeMining(node::NodeContext& context) { return std::make_unique<node::MinerImpl>(context); }
} // namespace interfaces
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -489,7 +489,11 @@ bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& ke
return true;
}
-std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
+std::optional<BlockRef> WaitTipChanged(
+ KernelNotifications& kernel_notifications,
+ const std::unique_ptr<ChainstateManager>& chainman,
+ const util::SignalInterrupt& shutdown_signal,
+ const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
{
Assume(timeout >= 0ms); // No internal callers should use a negative timeout
if (timeout < 0ms) timeout = 0ms;
@@ -502,18 +506,21 @@ std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifi
// always returns valid tip information when possible and only
// returns null when shutting down, not when timing out.
kernel_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
- return kernel_notifications.TipBlock() || chainman.m_interrupt || interrupt;
+ return (kernel_notifications.m_state.chainstate_loaded && kernel_notifications.TipBlock()) || shutdown_signal || interrupt;
});
- if (chainman.m_interrupt || interrupt) {
+
+ if (shutdown_signal || interrupt) {
interrupt = false;
return {};
}
+ Assert(chainman);
+ Assert(&chainman->m_interrupt == &shutdown_signal);
// At this point TipBlock is set, so continue to wait until it is
// different then `current_tip` provided by caller.
kernel_notifications.m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
- return Assume(kernel_notifications.TipBlock()) != current_tip || chainman.m_interrupt || interrupt;
+ return Assume(kernel_notifications.TipBlock()) != current_tip || chainman->m_interrupt || interrupt;
});
- if (chainman.m_interrupt || interrupt) {
+ if (chainman->m_interrupt || interrupt) {
interrupt = false;
return {};
}
@@ -521,7 +528,7 @@ std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifi
// Must release m_tip_block_mutex before getTip() locks cs_main, to
// avoid deadlocks.
- return GetTip(chainman);
+ return GetTip(*chainman);
}
} // namespace node
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -30,6 +30,10 @@ class CScript;
class Chainstate;
class ChainstateManager;
+namespace util {
+class SignalInterrupt;
+} // namespace util
+
namespace Consensus { struct Params; };
using interfaces::BlockRef;
@@ -162,7 +166,13 @@ std::optional<BlockRef> GetTip(ChainstateManager& chainman);
* Returns the current tip, or nullopt if the node is shutting down or interrupt()
* is called.
*/
-std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt);
+std::optional<BlockRef> WaitTipChanged(
+ KernelNotifications& kernel_notifications,
+ const std::unique_ptr<ChainstateManager>& chainman_ptr,
+ const util::SignalInterrupt& shutdown_signal,
+ const uint256& current_tip,
+ MillisecondsDouble& timeout,
+ bool& interrupt);
/**
* Wait while the best known header extends the current chain tip AND at least
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -68,7 +68,7 @@ struct MinerTestingSetup : public TestingSetup {
}
std::unique_ptr<Mining> MakeMining()
{
- return interfaces::MakeMining(m_node, /*wait_loaded=*/false);
+ return interfaces::MakeMining(m_node);
}
};
} // namespace miner_tests
--- 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<Mining> MakeMining()
{
- return interfaces::MakeMining(m_node, /*wait_loaded=*/false);
+ return interfaces::MakeMining(m_node);
}
};
} // namespace testnet4_miner_tests
</p>
</details>