Merge bitcoin/bitcoin#35675: mining: add block template manager
What changed, and why it matters
This is a large internal code reorganization (refactor) in Bitcoin Core. It creates a new BlockTemplateManager class that takes over block-template creation, block submission, and tip-waiting helpers that were previously spread across several modules. The pull request description explicitly says it is intended to be a pure refactor that preserves behavior. No new user-facing feature is added and no vulnerability is evident in the diff.
No immediate security action required. Treat as a normal refactor review: verify CI passes, confirm the new reset ordering prevents use-after-free during shutdown/reindex, and monitor for any follow-up fixes if behavior diverges from the old Mining interface.
Security signals we found
Large refactor touching mining, RPC, interfaces, and test shutdown paths
New object lifetime dependency: BlockTemplateManager holds references to mempool, chainman, and notifications; explicit reset ordering added in Shutdown/InitAndLoadChainstate/test setups
Removal of early-init node.mining interface; BlockTemplateManager is now created after chainstate load, with a comment that it must exist before setChainstateLoaded(true) unblocks IPC waiters
getblocktemplate no longer copies the entire CBlock; works with block_template->block header and fields directly
Fuzz harness marks additional mining/wait RPCs as unsafe for fuzzing
Evidence from the diff
The commit introduces node::BlockTemplateManager, moves BlockCreateOptions ownership out of NodeContext, routes Mining IPC interface calls through the manager, and makes RPC/tests use raw CBlockTemplate objects directly. It also adjusts shutdown/reset ordering so the manager is destroyed before its dependencies (mempool, chainman, notifications). The code changes are mechanical moves with preserved logic; a few RPC paths now avoid an extra copy of the full block in getblocktemplate by working with the header and template fields directly. Fuzz tests are updated to mark generateblock/waitforblock/waitforblockheight/waitfornewblock as unsafe because the new direct manager usage can block or change chain state during fuzzing.
Changed components
src/node/block_template_manager.cpp/hsrc/node/context.cpp/hsrc/node/interfaces.cppsrc/node/miner.cpp/hsrc/init.cppsrc/rpc/mining.cppsrc/rpc/blockchain.cppsrc/rpc/server_util.cpp/htest setup/utilities and fuzz targetsInspect captured patch +561 / −449
### ci/test/03_test_script.sh
@@ -232,7 +232,7 @@ fi
if [[ "${RUN_IWYU}" == true ]]; then
# TODO: Consider enforcing IWYU across the entire codebase.
- FILES_WITH_ENFORCED_IWYU='/src/((bench|common|consensus|crypto|index|init|kernel|primitives|rpc|script|univalue/(lib|test)|util|zmq)/.*|node/(blockstorage|interfaces|miner|mining_args|utxo_snapshot)|test/fuzz/(kitchen_sink|minisketch|parse_univalue)|clientversion|core_io|rest|signet|init)\.cpp'
+ FILES_WITH_ENFORCED_IWYU='/src/((bench|common|consensus|crypto|index|init|kernel|primitives|rpc|script|univalue/(lib|test)|util|zmq)/.*|node/(block_template_manager|blockstorage|interfaces|miner|mining_args|utxo_snapshot)|test/fuzz/(kitchen_sink|minisketch|parse_univalue)|clientversion|core_io|rest|signet|init)\.cpp'
jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns)))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_errors.json"
jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns) | not))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_warnings.json"
### src/CMakeLists.txt
@@ -215,6 +215,7 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
net_processing.cpp
netgroup.cpp
node/abort.cpp
+ node/block_template_manager.cpp
node/blockmanager_args.cpp
node/blockstorage.cpp
node/caches.cpp
### src/init.cpp
@@ -33,7 +33,6 @@
#include <interfaces/chain.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
-#include <interfaces/mining.h>
#include <interfaces/node.h>
#include <ipc/exception.h>
#include <kernel/blockmanager_opts.h>
@@ -51,6 +50,7 @@
#include <netaddress.h>
#include <netbase.h>
#include <netgroup.h>
+#include <node/block_template_manager.h>
#include <node/blockmanager_args.h>
#include <node/blockstorage.h>
#include <node/caches.h>
@@ -63,7 +63,6 @@
#include <node/mempool_persist.h>
#include <node/mempool_persist_args.h>
#include <node/mining_args.h>
-#include <node/mining_types.h>
#include <node/peerman_args.h>
#include <policy/feerate.h>
#include <policy/fees/block_policy_estimator.h>
@@ -428,6 +427,7 @@ void Shutdown(NodeContext& node)
node.validation_signals->UnregisterAllValidationInterfaces();
}
node.fee_estimator_man.reset();
+ node.block_template_manager.reset();
node.mempool.reset();
node.chainman.reset();
node.validation_signals.reset();
@@ -1279,9 +1279,6 @@ 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);
return true;
}
@@ -1379,6 +1376,7 @@ static ChainstateLoadResult InitAndLoadChainstate(
{
// This function may be called twice, so any dirty state must be reset.
node.notifications->setChainstateLoaded(false); // Drop state, such as a cached tip block
+ node.block_template_manager.reset();
node.mempool.reset();
node.chainman.reset(); // Drop state, such as an initialized m_block_tree_db
@@ -1395,9 +1393,6 @@ static ChainstateLoadResult InitAndLoadChainstate(
if (!mempool_error.empty()) {
return {ChainstateLoadStatus::FAILURE_FATAL, mempool_error};
}
- auto mining_args{node::ReadMiningArgs(args)};
- Assert(mining_args); // no error can happen, already checked in AppInitParameterInteraction
- node.mining_args = std::move(*mining_args);
LogInfo("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)",
cache_sizes.coins / double(1_MiB),
mempool_opts.max_size_bytes / double(1_MiB));
@@ -1487,6 +1482,10 @@ static ChainstateLoadResult InitAndLoadChainstate(
std::tie(status, error) = catch_exceptions([&] { return VerifyLoadedChainstate(chainman, options); });
if (status == node::ChainstateLoadStatus::SUCCESS) {
LogInfo("Block index and chainstate loaded");
+ auto mining_args{node::ReadMiningArgs(args)};
+ Assert(mining_args); // no error can happen, already checked in AppInitParameterInteraction
+ // Must be set before setChainstateLoaded(true), which unblocks MakeMining waiters that assume it is non-null.
+ node.block_template_manager = std::make_unique<node::BlockTemplateManager>(*node.mempool, chainman, *node.notifications, std::move(*mining_args));
node.notifications->setChainstateLoaded(true);
}
}
@@ -1902,6 +1901,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
assert(!node.mempool);
assert(!node.chainman);
+ assert(!node.block_template_manager);
bool do_reindex{args.GetBoolArg("-reindex", false)};
const bool do_reindex_chainstate{args.GetBoolArg("-reindex-chainstate", false)};
### src/node/block_template_manager.cpp
@@ -0,0 +1,288 @@
+// Copyright (c) The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <node/block_template_manager.h>
+
+#include <chain.h>
+#include <consensus/amount.h>
+#include <consensus/params.h>
+#include <consensus/validation.h>
+#include <interfaces/types.h>
+#include <kernel/chainparams.h>
+#include <node/kernel_notifications.h>
+#include <node/miner.h>
+#include <node/mining_args.h>
+#include <primitives/block.h>
+#include <sync.h>
+#include <uint256.h>
+#include <util/check.h>
+#include <util/signalinterrupt.h>
+#include <validation.h>
+#include <validationinterface.h>
+
+#include <algorithm>
+#include <compare>
+#include <condition_variable>
+#include <numeric>
+#include <utility>
+#include <vector>
+
+namespace node {
+
+using interfaces::BlockRef;
+
+BlockTemplateManager::BlockTemplateManager(CTxMemPool& mempool, ChainstateManager& chainman,
+ KernelNotifications& notifications,
+ BlockCreateOptions block_create_args)
+ : m_mempool(mempool), m_chainman(chainman), m_notifications(notifications), m_block_create_args(std::move(block_create_args))
+{
+}
+
+std::unique_ptr<CBlockTemplate> BlockTemplateManager::CreateNewTemplate(const BlockCreateOptions& options)
+{
+ return BlockAssembler{
+ m_chainman.ActiveChainstate(),
+ &m_mempool,
+ MergeMiningOptions(options, m_block_create_args),
+ }.CreateNewBlock();
+}
+
+namespace {
+class SubmitBlockStateCatcher final : public CValidationInterface
+{
+public:
+ uint256 m_hash;
+ bool m_found{false};
+ BlockValidationState m_state;
+
+ explicit SubmitBlockStateCatcher(const uint256& hash) : m_hash{hash} {}
+
+protected:
+ void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state) override
+ {
+ if (block->GetHash() != m_hash) return;
+ // ProcessNewBlock emits BlockChecked synchronously while holding cs_main,
+ // so SubmitBlock can read these fields after ProcessNewBlock returns
+ // without extra synchronization.
+ m_found = true;
+ m_state = state;
+ }
+};
+} // namespace
+
+bool BlockTemplateManager::SubmitBlock(const std::shared_ptr<const CBlock>& block, std::string& reason, std::string& debug)
+{
+ reason.clear();
+ debug.clear();
+
+ // This follows the submitblock RPC's validation-state capture pattern, but
+ // is intentionally kept separate from the RPC implementation. The RPC entry
+ // point decodes hex, formats BIP22/JSONRPC results, and calls
+ // UpdateUncommittedBlockStructures() for legacy witness handling. IPC
+ // callers submit already-formed blocks and need bool + reason/debug
+ // results.
+ auto sc = std::make_shared<SubmitBlockStateCatcher>(block->GetHash());
+ CHECK_NONFATAL(m_chainman.m_options.signals)->RegisterSharedValidationInterface(sc);
+ bool new_block;
+ bool accepted = m_chainman.ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
+ // No queue drain is needed. The BlockChecked notification used above is
+ // emitted synchronously by ProcessNewBlock, unlike most validation signals.
+ CHECK_NONFATAL(m_chainman.m_options.signals)->UnregisterSharedValidationInterface(sc);
+
+ if (!new_block && accepted) {
+ reason = "duplicate";
+ } else if (!accepted && (!sc->m_found || sc->m_state.IsValid())) {
+ // ProcessNewBlock can fail without a validation result, for example
+ // from an activation or system error. It can also fail after a valid
+ // BlockChecked result. In these cases the validation result is
+ // inconclusive.
+ reason = "inconclusive";
+ } else if (!sc->m_found) {
+ // The block was accepted but not connected, for example if it does not
+ // have more work than the current tip.
+ reason = "inconclusive";
+ } else if (!sc->m_state.IsValid()) {
+ reason = sc->m_state.GetRejectReason();
+ debug = sc->m_state.GetDebugMessage();
+ }
+ const bool result{accepted && new_block && reason.empty()};
+ CHECK_NONFATAL(result == reason.empty());
+ return result;
+}
+
+std::optional<BlockRef> BlockTemplateManager::GetTip()
+{
+ LOCK(::cs_main);
+ CBlockIndex* tip{m_chainman.ActiveChain().Tip()};
+ if (!tip) return {};
+ return BlockRef{tip->GetBlockHash(), tip->nHeight};
+}
+
+void BlockTemplateManager::InterruptWait(bool& interrupt_wait)
+{
+ LOCK(m_notifications.m_tip_block_mutex);
+ interrupt_wait = true;
+ m_notifications.m_tip_block_cv.notify_all();
+}
+
+std::unique_ptr<CBlockTemplate> BlockTemplateManager::WaitAndCreateNewBlock(
+ const std::unique_ptr<CBlockTemplate>& block_template,
+ const BlockWaitOptions& wait_options,
+ const BlockCreateOptions& create_options,
+ bool& interrupt_wait)
+{
+ // Delay calculating the current template fees, just in case a new block
+ // comes in before the next tick.
+ CAmount current_fees = -1;
+
+ // Alternate waiting for a new tip and checking if fees have risen.
+ // The latter check is expensive so we only run it once per second.
+ auto now{NodeClock::now()};
+ const auto deadline = now + wait_options.timeout;
+ const MillisecondsDouble tick{1000};
+ const bool allow_min_difficulty{m_chainman.GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};
+
+ do {
+ bool tip_changed{false};
+ {
+ WAIT_LOCK(m_notifications.m_tip_block_mutex, lock);
+ // Note that wait_until() checks the predicate before waiting
+ m_notifications.m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
+ AssertLockHeld(m_notifications.m_tip_block_mutex);
+ const auto tip_block{m_notifications.TipBlock()};
+ // We assume tip_block is set, because this is an instance
+ // method on BlockTemplate and no template could have been
+ // generated before a tip exists.
+ tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
+ return tip_changed || m_chainman.m_interrupt || interrupt_wait;
+ });
+ if (interrupt_wait) {
+ interrupt_wait = false;
+ return nullptr;
+ }
+ }
+
+ if (m_chainman.m_interrupt) return nullptr;
+ // At this point the tip changed, a full tick went by or we reached
+ // the deadline.
+
+ // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
+ LOCK(::cs_main);
+
+ // On test networks return a minimum difficulty block after 20 minutes
+ if (!tip_changed && allow_min_difficulty) {
+ const NodeClock::time_point tip_time{std::chrono::seconds{m_chainman.ActiveChain().Tip()->GetBlockTime()}};
+ if (now > tip_time + 20min) {
+ tip_changed = true;
+ }
+ }
+
+ /**
+ * We determine if fees increased compared to the previous template by generating
+ * a fresh template. There may be more efficient ways to determine how much
+ * (approximate) fees for the next block increased, perhaps more so after
+ * Cluster Mempool.
+ *
+ * We'll also create a new template if the tip changed during this iteration.
+ */
+ if (wait_options.fee_threshold < MAX_MONEY || tip_changed) {
+ auto new_tmpl{CreateNewTemplate(create_options)};
+
+ // If the tip changed, return the new template regardless of its fees.
+ if (tip_changed) return new_tmpl;
+
+ // Calculate the original template total fees if we haven't already
+ if (current_fees == -1) {
+ current_fees = std::accumulate(block_template->vTxFees.begin(), block_template->vTxFees.end(), CAmount{0});
+ }
+
+ // Check if fees increased enough to return the new template
+ const CAmount new_fees = std::accumulate(new_tmpl->vTxFees.begin(), new_tmpl->vTxFees.end(), CAmount{0});
+ Assume(wait_options.fee_threshold != MAX_MONEY);
+ if (new_fees >= current_fees + wait_options.fee_threshold) return new_tmpl;
+ }
+
+ now = NodeClock::now();
+ } while (now < deadline);
+
+ return nullptr;
+}
+
+bool BlockTemplateManager::CooldownIfHeadersAhead(const BlockRef& last_tip, bool& interrupt_mining)
+{
+ uint256 last_tip_hash{last_tip.hash};
+
+ while (const std::optional<int> remaining = m_chainman.BlocksAheadOfTip()) {
+ const int cooldown_seconds = std::clamp(*remaining, 3, 20);
+ const auto cooldown_deadline{MockableSteadyClock::now() + std::chrono::seconds{cooldown_seconds}};
+
+ {
+ WAIT_LOCK(m_notifications.m_tip_block_mutex, lock);
+ m_notifications.m_tip_block_cv.wait_until(lock, cooldown_deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
+ const auto tip_block = m_notifications.TipBlock();
+ return m_chainman.m_interrupt || interrupt_mining || (tip_block && *tip_block != last_tip_hash);
+ });
+ if (m_chainman.m_interrupt || interrupt_mining) {
+ interrupt_mining = false;
+ return false;
+ }
+
+ // If the tip changed during the wait, extend the deadline
+ const auto tip_block = m_notifications.TipBlock();
+ if (tip_block && *tip_block != last_tip_hash) {
+ last_tip_hash = *tip_block;
+ continue;
+ }
+ }
+
+ // No tip change and the cooldown window has expired.
+ if (MockableSteadyClock::now() >= cooldown_deadline) break;
+ }
+
+ return true;
+}
+
+std::optional<BlockRef> BlockTemplateManager::WaitTipChanged(const uint256& current_tip, MillisecondsDouble timeout)
+{
+ bool interrupt_wait{false};
+ return WaitTipChanged(current_tip, timeout, interrupt_wait);
+}
+
+std::optional<BlockRef> BlockTemplateManager::WaitTipChanged(const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
+{
+ Assume(timeout >= 0ms); // No internal callers should use a negative timeout
+ if (timeout < 0ms) timeout = 0ms;
+ if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
+ auto deadline{std::chrono::steady_clock::now() + timeout};
+ {
+ WAIT_LOCK(m_notifications.m_tip_block_mutex, lock);
+ // For callers convenience, wait longer than the provided timeout
+ // during startup for the tip to be non-null. That way this function
+ // always returns valid tip information when possible and only
+ // returns null when shutting down, not when timing out.
+ m_notifications.m_tip_block_cv.wait(lock, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
+ AssertLockHeld(m_notifications.m_tip_block_mutex);
+ return m_notifications.TipBlock() || m_chainman.m_interrupt || interrupt;
+ });
+ if (m_chainman.m_interrupt || interrupt) {
+ interrupt = false;
+ return {};
+ }
+ // At this point TipBlock is set, so continue to wait until it is
+ // different from `current_tip` provided by caller.
+ m_notifications.m_tip_block_cv.wait_until(lock, deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(m_notifications.m_tip_block_mutex) {
+ return Assume(m_notifications.TipBlock()) != current_tip || m_chainman.m_interrupt || interrupt;
+ });
+ if (m_chainman.m_interrupt || interrupt) {
+ interrupt = false;
+ return {};
+ }
+ }
+
+ // Must release m_tip_block_mutex before GetTip() locks cs_main, to
+ // avoid deadlocks.
+ return GetTip();
+}
+
+} // namespace node
### src/node/block_template_manager.h
@@ -0,0 +1,106 @@
+// Copyright (c) The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_NODE_BLOCK_TEMPLATE_MANAGER_H
+#define BITCOIN_NODE_BLOCK_TEMPLATE_MANAGER_H
+
+#include <node/mining_types.h>
+#include <util/time.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+
+class CBlock;
+class ChainstateManager;
+class CTxMemPool;
+class uint256;
+
+namespace interfaces {
+struct BlockRef;
+} // namespace interfaces
+
+namespace node {
+class KernelNotifications;
+struct CBlockTemplate;
+
+/**
+ * Creates block templates, submits solved blocks, and provides tip-waiting
+ * helpers for mining code. Owns the init-time block creation args.
+ */
+class BlockTemplateManager
+{
+private:
+ CTxMemPool& m_mempool;
+ ChainstateManager& m_chainman;
+ KernelNotifications& m_notifications;
+ const BlockCreateOptions m_block_create_args;
+
+public:
+ explicit BlockTemplateManager(CTxMemPool& mempool,
+ ChainstateManager& chainman,
+ KernelNotifications& notifications,
+ BlockCreateOptions block_create_args = {});
+
+ /** @return the block creation args set during node init. */
+ const BlockCreateOptions& BlockCreateArgs() const { return m_block_create_args; }
+
+ /** Create a fresh block template, applying init-time defaults to any unset options. */
+ std::unique_ptr<CBlockTemplate> CreateNewTemplate(const BlockCreateOptions& options);
+
+ /** Submit a block via ProcessNewBlock and capture validation state.
+ * @return whether the block was accepted as a new valid block. */
+ bool SubmitBlock(const std::shared_ptr<const CBlock>& block, std::string& reason, std::string& debug);
+
+ /** Locks cs_main.
+ * @return the active chain tip, or nullopt if none exists. */
+ std::optional<interfaces::BlockRef> GetTip();
+
+ /** Wait for the tip to differ from @p current_tip or timeout.
+ * Waits indefinitely during startup for a non-null tip.
+ * @return the current tip, or nullopt if the node is shutting down or
+ * interrupt is set (not when the timeout is reached). */
+ std::optional<interfaces::BlockRef> WaitTipChanged(const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt);
+
+ /** Convenience overload for in-process callers (e.g. RPC) that have no
+ * interrupt handle. Takes the timeout by value so the caller's variable is
+ * left unchanged; the wait still ends on chain shutdown. */
+ std::optional<interfaces::BlockRef> WaitTipChanged(const uint256& current_tip, MillisecondsDouble timeout = MillisecondsDouble::max());
+
+ /**
+ * Wait while the best known header extends the current chain tip AND at
+ * least one block is being added to the tip every 3 seconds. If the tip is
+ * sufficiently far behind, allow up to 20 seconds for the next tip update.
+ *
+ * It's not safe to keep waiting, because a malicious miner could announce
+ * a header and delay revealing the block, causing all other miners using
+ * this software to stall. At the same time, we need to balance between the
+ * default waiting time being brief, but not ending the cooldown prematurely
+ * when a random block is slow to download (or process).
+ *
+ * The cooldown only applies to createNewBlock(), which is typically called
+ * once per connected client. Subsequent templates are provided by
+ * waitNext().
+ *
+ * @param last_tip tip at the start of the cooldown window.
+ * @param interrupt_mining set to true to interrupt the cooldown.
+ *
+ * @returns false if interrupted.
+ */
+ bool CooldownIfHeadersAhead(const interfaces::BlockRef& last_tip, bool& interrupt_mining);
+
+ /** Interrupt a blocking wait. */
+ void InterruptWait(bool& interrupt_wait);
+
+ /** Return a new block template when fees rise to a certain threshold or
+ * after a new tip; return nullptr if timeout is reached. */
+ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(
+ const std::unique_ptr<CBlockTemplate>& block_template,
+ const BlockWaitOptions& wait_options,
+ const BlockCreateOptions& create_options,
+ bool& interrupt_wait);
+};
+} // namespace node
+
+#endif // BITCOIN_NODE_BLOCK_TEMPLATE_MANAGER_H
### src/node/context.cpp
@@ -7,12 +7,12 @@
#include <addrman.h>
#include <banman.h>
#include <interfaces/chain.h>
-#include <interfaces/mining.h>
#include <kernel/context.h>
#include <key.h>
#include <net.h>
#include <net_processing.h>
#include <netgroup.h>
+#include <node/block_template_manager.h>
#include <node/kernel_notifications.h>
#include <node/warnings.h>
#include <policy/fees/estimator_man.h>
### src/node/context.h
@@ -5,8 +5,6 @@
#ifndef BITCOIN_NODE_CONTEXT_H
#define BITCOIN_NODE_CONTEXT_H
-#include <node/mining_types.h>
-
#include <atomic>
#include <cstdlib>
#include <functional>
@@ -31,7 +29,6 @@ class TorController;
namespace interfaces {
class Chain;
class ChainClient;
-class Mining;
class Init;
class WalletLoader;
} // namespace interfaces
@@ -43,6 +40,7 @@ class SignalInterrupt;
}
namespace node {
+class BlockTemplateManager;
class KernelNotifications;
class Warnings;
@@ -80,19 +78,13 @@ struct NodeContext {
std::unique_ptr<interfaces::Chain> chain;
//! List of all chain clients (wallet processes or other client) connected to node.
std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients;
- //! Reference to chain client that should used to load or create wallets
- //! opened by the gui.
- std::unique_ptr<interfaces::Mining> mining;
- //! Mining options used to create block templates. This value member is an
- //! exception to the dependency guidance above because BlockCreateOptions is
- //! a minimal dependency. It could be moved to the BlockTemplateCache
- //! proposed in bitcoin/bitcoin#33421.
- BlockCreateOptions mining_args;
interfaces::WalletLoader* wallet_loader{nullptr};
std::unique_ptr<CScheduler> scheduler;
std::function<void()> rpc_interruption_point = [] {};
//! Issues blocking calls about sync status, errors and warnings
std::unique_ptr<KernelNotifications> notifications;
+ //! Must be destroyed before its dependencies (holds references to them).
+ std::unique_ptr<BlockTemplateManager> block_template_manager;
//! Issues calls about blocks and transactions
std::unique_ptr<ValidationSignals> validation_signals;
std::atomic<int> exit_status{EXIT_SUCCESS};
### src/node/interfaces.cpp
@@ -33,14 +33,14 @@
#include <net_types.h>
#include <netaddress.h>
#include <netbase.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/interface_ui.h>
#include <node/kernel_notifications.h>
#include <node/miner.h>
#include <node/mini_miner.h>
-#include <node/mining_args.h>
#include <node/mining_types.h>
#include <node/transaction.h>
#include <node/types.h>
@@ -97,7 +97,6 @@ using interfaces::Node;
using interfaces::Rpc;
using interfaces::WalletLoader;
using kernel::ChainstateRole;
-using node::BlockAssembler;
using node::BlockCreateOptions;
using node::BlockWaitOptions;
using node::CoinbaseTx;
@@ -923,34 +922,28 @@ class BlockTemplateImpl : public BlockTemplate
{
if (!coinbase) return false;
AddMerkleRootAndCoinbase(m_block_template->block, std::move(coinbase), version, timestamp, nonce);
- return SubmitBlock(chainman(), std::make_shared<const CBlock>(m_block_template->block), reason, debug);
+ return block_template_manager().SubmitBlock(std::make_shared<const CBlock>(m_block_template->block), reason, debug);
}
std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override
{
- auto new_template = WaitAndCreateNewBlock(chainman(),
- notifications(),
- m_node.mempool.get(),
- m_block_template,
- /*wait_options=*/options,
- /*create_options=*/m_create_options,
- /*interrupt_wait=*/m_interrupt_wait);
+ auto new_template = block_template_manager().WaitAndCreateNewBlock(
+ m_block_template, options, m_create_options, m_interrupt_wait);
if (new_template) return std::make_unique<BlockTemplateImpl>(m_create_options, std::move(new_template), m_node);
return nullptr;
}
void interruptWait() override
{
- InterruptWait(notifications(), m_interrupt_wait);
+ block_template_manager().InterruptWait(m_interrupt_wait);
}
const BlockCreateOptions m_create_options;
const std::unique_ptr<CBlockTemplate> m_block_template;
bool m_interrupt_wait{false};
- ChainstateManager& chainman() { return *Assert(m_node.chainman); }
- KernelNotifications& notifications() { return *Assert(m_node.notifications); }
+ node::BlockTemplateManager& block_template_manager() { return *Assert(m_node.block_template_manager); }
const NodeContext& m_node;
};
@@ -971,12 +964,12 @@ class MinerImpl : public Mining
std::optional<BlockRef> getTip() override
{
- return GetTip(chainman());
+ return block_template_manager().GetTip();
}
std::optional<BlockRef> waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
{
- return WaitTipChanged(chainman(), notifications(), current_tip, timeout, m_interrupt_mining);
+ return block_template_manager().WaitTipChanged(current_tip, timeout, m_interrupt_mining);
}
std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options, bool cooldown) override
@@ -998,21 +991,15 @@ class MinerImpl : public Mining
}
// Also wait during the final catch-up moments after IBD.
- if (!CooldownIfHeadersAhead(chainman(), notifications(), *maybe_tip, m_interrupt_mining)) return {};
+ if (!block_template_manager().CooldownIfHeadersAhead(*maybe_tip, m_interrupt_mining)) return {};
}
- const BlockCreateOptions create_options{MergeMiningOptions(options, m_node.mining_args)};
- return std::make_unique<BlockTemplateImpl>(create_options,
- BlockAssembler{
- chainman().ActiveChainstate(),
- m_node.mempool.get(),
- create_options,
- }.CreateNewBlock(),
- m_node);
+ auto new_template = block_template_manager().CreateNewTemplate(options);
+ return std::make_unique<BlockTemplateImpl>(options, std::move(new_template), m_node);
}
void interrupt() override
{
- InterruptWait(notifications(), m_interrupt_mining);
+ block_template_manager().InterruptWait(m_interrupt_mining);
}
bool checkBlock(const CBlock& block, const node::BlockCheckOptions& options, std::string& reason, std::string& debug) override
@@ -1026,7 +1013,7 @@ class MinerImpl : public Mining
bool submitBlock(const CBlock& block_in, std::string& reason, std::string& debug) override
{
- return SubmitBlock(chainman(), std::make_shared<const CBlock>(block_in), reason, debug);
+ return block_template_manager().SubmitBlock(std::make_shared<const CBlock>(block_in), reason, debug);
}
std::vector<CTransactionRef> getTransactionsByTxID(const std::vector<Txid>& txids) override
@@ -1058,6 +1045,7 @@ class MinerImpl : public Mining
const NodeContext* context() override { return &m_node; }
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
+ node::BlockTemplateManager& block_template_manager() { return *Assert(m_node.block_template_manager); }
// Treat as if guarded by notifications().m_tip_block_mutex
bool m_interrupt_mining{false};
const NodeContext& m_node;
### src/node/miner.cpp
@@ -14,9 +14,7 @@
#include <consensus/params.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
-#include <interfaces/types.h>
#include <node/blockstorage.h>
-#include <node/kernel_notifications.h>
#include <node/mining_args.h>
#include <node/mining_types.h>
#include <policy/feerate.h>
@@ -34,19 +32,14 @@
#include <util/feefrac.h>
#include <util/log.h>
#include <util/result.h>
-#include <util/signalinterrupt.h>
#include <util/time.h>
#include <util/translation.h>
#include <validation.h>
-#include <validationinterface.h>
#include <versionbits.h>
#include <algorithm>
-#include <compare>
-#include <condition_variable>
#include <cstddef>
#include <functional>
-#include <numeric>
#include <span>
#include <stdexcept>
#include <string>
@@ -371,240 +364,4 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
block.fChecked = false;
}
-namespace {
-class SubmitBlockStateCatcher final : public CValidationInterface
-{
-public:
- uint256 m_hash;
- bool m_found{false};
- BlockValidationState m_state;
-
- explicit SubmitBlockStateCatcher(const uint256& hash) : m_hash{hash} {}
-
-protected:
- void BlockChecked(const std::shared_ptr<const CBlock>& block, const BlockValidationState& state) override
- {
- if (block->GetHash() != m_hash) return;
- // ProcessNewBlock emits BlockChecked synchronously while holding cs_main,
- // so SubmitBlock can read these fields after ProcessNewBlock returns
- // without extra synchronization.
- m_found = true;
- m_state = state;
- }
-};
-} // namespace
-
-bool SubmitBlock(ChainstateManager& chainman, const std::shared_ptr<const CBlock>& block, std::string& reason, std::string& debug)
-{
- reason.clear();
- debug.clear();
-
- // This follows the submitblock RPC's validation-state capture pattern, but
- // is intentionally kept separate from the RPC implementation. The RPC entry
- // point decodes hex, formats BIP22/JSONRPC results, and calls
- // UpdateUncommittedBlockStructures() for legacy witness handling. IPC
- // callers submit already-formed blocks and need bool + reason/debug
- // results.
- auto sc = std::make_shared<SubmitBlockStateCatcher>(block->GetHash());
- CHECK_NONFATAL(chainman.m_options.signals)->RegisterSharedValidationInterface(sc);
- bool new_block;
- bool accepted = chainman.ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
- // No queue drain is needed. The BlockChecked notification used above is
- // emitted synchronously by ProcessNewBlock, unlike most validation signals.
- CHECK_NONFATAL(chainman.m_options.signals)->UnregisterSharedValidationInterface(sc);
-
- if (!new_block && accepted) {
- reason = "duplicate";
- } else if (!accepted && (!sc->m_found || sc->m_state.IsValid())) {
- // ProcessNewBlock can fail without a validation result, for example
- // from an activation or system error. It can also fail after a valid
- // BlockChecked result. In these cases the validation result is
- // inconclusive.
- reason = "inconclusive";
- } else if (!sc->m_found) {
- // The block was accepted but not connected, for example if it does not
- // have more work than the current tip.
- reason = "inconclusive";
- } else if (!sc->m_state.IsValid()) {
- reason = sc->m_state.GetRejectReason();
- debug = sc->m_state.GetDebugMessage();
- }
- const bool result{accepted && new_block && reason.empty()};
- CHECK_NONFATAL(result == reason.empty());
- return result;
-}
-
-void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait)
-{
- LOCK(kernel_notifications.m_tip_block_mutex);
- interrupt_wait = true;
- kernel_notifications.m_tip_block_cv.notify_all();
-}
-
-std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,
- KernelNotifications& kernel_notifications,
- CTxMemPool* mempool,
- const std::unique_ptr<CBlockTemplate>& block_template,
- const BlockWaitOptions& wait_options,
- const BlockCreateOptions& create_options,
- bool& interrupt_wait)
-{
- // Delay calculating the current template fees, just in case a new block
- // comes in before the next tick.
- CAmount current_fees = -1;
-
- // Alternate waiting for a new tip and checking if fees have risen.
- // The latter check is expensive so we only run it once per second.
- auto now{NodeClock::now()};
- const auto deadline = now + wait_options.timeout;
- const MillisecondsDouble tick{1000};
- const bool allow_min_difficulty{chainman.GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};
-
- do {
- bool tip_changed{false};
- {
- WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
- // Note that wait_until() checks the predicate before waiting
- kernel_notifications.m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
- AssertLockHeld(kernel_notifications.m_tip_block_mutex);
- const auto tip_block{kernel_notifications.TipBlock()};
- // We assume tip_block is set, because this is an instance
- // method on BlockTemplate and no template could have been
- // generated before a tip exists.
- tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
- return tip_changed || chainman.m_interrupt || interrupt_wait;
- });
- if (interrupt_wait) {
- interrupt_wait = false;
- return nullptr;
- }
- }
-
- if (chainman.m_interrupt) return nullptr;
- // At this point the tip changed, a full tick went by or we reached
- // the deadline.
-
- // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
- LOCK(::cs_main);
-
- // On test networks return a minimum difficulty block after 20 minutes
- if (!tip_changed && allow_min_difficulty) {
- const NodeClock::time_point tip_time{std::chrono::seconds{chainman.ActiveChain().Tip()->GetBlockTime()}};
- if (now > tip_time + 20min) {
- tip_changed = true;
- }
- }
-
- /**
- * We determine if fees increased compared to the previous template by generating
- * a fresh template. There may be more efficient ways to determine how much
- * (approximate) fees for the next block increased, perhaps more so after
- * Cluster Mempool.
- *
- * We'll also create a new template if the tip changed during this iteration.
- */
- if (wait_options.fee_threshold < MAX_MONEY || tip_changed) {
- auto new_tmpl{BlockAssembler{
- chainman.ActiveChainstate(),
- mempool,
- create_options
- }.CreateNewBlock()};
-
- // If the tip changed, return the new template regardless of its fees.
- if (tip_changed) return new_tmpl;
-
- // Calculate the original template total fees if we haven't already
- if (current_fees == -1) {
- current_fees = std::accumulate(block_template->vTxFees.begin(), block_template->vTxFees.end(), CAmount{0});
- }
-
- // Check if fees increased enough to return the new template
- const CAmount new_fees = std::accumulate(new_tmpl->vTxFees.begin(), new_tmpl->vTxFees.end(), CAmount{0});
- Assume(wait_options.fee_threshold != MAX_MONEY);
- if (new_fees >= current_fees + wait_options.fee_threshold) return new_tmpl;
- }
-
- now = NodeClock::now();
- } while (now < deadline);
-
- return nullptr;
-}
-
-std::optional<BlockRef> GetTip(ChainstateManager& chainman)
-{
- LOCK(::cs_main);
- CBlockIndex* tip{chainman.ActiveChain().Tip()};
- if (!tip) return {};
- return BlockRef{tip->GetBlockHash(), tip->nHeight};
-}
-
-bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip, bool& interrupt_mining)
-{
- uint256 last_tip_hash{last_tip.hash};
-
- while (const std::optional<int> remaining = chainman.BlocksAheadOfTip()) {
- const int cooldown_seconds = std::clamp(*remaining, 3, 20);
- const auto cooldown_deadline{MockableSteadyClock::now() + std::chrono::seconds{cooldown_seconds}};
-
- {
- WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
- kernel_notifications.m_tip_block_cv.wait_until(lock, cooldown_deadline, [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
- const auto tip_block = kernel_notifications.TipBlock();
- return chainman.m_interrupt || interrupt_mining || (tip_block && *tip_block != last_tip_hash);
- });
- if (chainman.m_interrupt || interrupt_mining) {
- interrupt_mining = false;
- return false;
- }
-
- // If the tip changed during the wait, extend the deadline
- const auto tip_block = kernel_notifications.TipBlock();
- if (tip_block && *tip_block != last_tip_hash) {
- last_tip_hash = *tip_block;
- continue;
- }
- }
-
- // No tip change and the cooldown window has expired.
- if (MockableSteadyClock::now() >= cooldown_deadline) break;
- }
-
- return true;
-}
-
-std::optional<BlockRef> WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout, bool& interrupt)
-{
- Assume(timeout >= 0ms); // No internal callers should use a negative timeout
- if (timeout < 0ms) timeout = 0ms;
- if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
- auto deadline{std::chrono::steady_clock::now() + timeout};
- {
- WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
- // For callers convenience, wait longer than the provided timeout
- // during startup for the tip to be non-null. That way this function
- // 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;
- });
- if (chainman.m_interrupt || interrupt) {
- interrupt = false;
- return {};
- }
- // 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;
- });
- if (chainman.m_interrupt || interrupt) {
- interrupt = false;
- return {};
- }
- }
-
- // Must release m_tip_block_mutex before getTip() locks cs_main, to
- // avoid deadlocks.
- return GetTip(chainman);
-}
-
} // namespace node
### src/node/miner.h
@@ -13,12 +13,10 @@
#include <threadsafety.h>
#include <txmempool.h>
#include <util/feefrac.h>
-#include <util/time.h>
#include <cstdint>
#include <memory>
#include <optional>
-#include <string>
#include <vector>
class CBlockIndex;
@@ -29,15 +27,7 @@ class ChainstateManager;
namespace Consensus {
struct Params;
} // namespace Consensus
-class uint256;
-namespace interfaces {
-struct BlockRef;
-} // namespace interfaces
-
-using interfaces::BlockRef;
-
namespace node {
-class KernelNotifications;
struct CBlockTemplate
{
@@ -129,54 +119,6 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman);
/* Compute the block's merkle root, insert or replace the coinbase transaction and the merkle root into the block */
void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t version, uint32_t timestamp, uint32_t nonce);
-
-//! Submit a block and capture the validation state via the BlockChecked callback.
-//! Returns whether the block was accepted as a new valid block.
-bool SubmitBlock(ChainstateManager& chainman, const std::shared_ptr<const CBlock>& block, std::string& reason, std::string& debug);
-
-/* Interrupt a blocking call. */
-void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait);
-/**
- * Return a new block template when fees rise to a certain threshold or after a
- * new tip; return nullopt if timeout is reached.
- */
-std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,
- KernelNotifications& kernel_notifications,
- CTxMemPool* mempool,
- const std::unique_ptr<CBlockTemplate>& block_template,
- const BlockWaitOptions& wait_options,
- const BlockCreateOptions& create_options,
- bool& interrupt_wait);
-
-/* Locks cs_main and returns the block hash and block height of the active chain if it exists; otherwise, returns nullopt.*/
-std::optional<BlockRef> GetTip(ChainstateManager& chainman);
-
-/* Waits for the connected tip to change until timeout has elapsed. During node initialization, this will wait until the tip is connected (regardless of `timeout`).
- * 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);
-
-/**
- * Wait while the best known header extends the current chain tip AND at least
- * one block is being added to the tip every 3 seconds. If the tip is
- * sufficiently far behind, allow up to 20 seconds for the next tip update.
- *
- * It’s not safe to keep waiting, because a malicious miner could announce a
- * header and delay revealing the block, causing all other miners using this
- * software to stall. At the same time, we need to balance between the default
- * waiting time being brief, but not ending the cooldown prematurely when a
- * random block is slow to download (or process).
- *
- * The cooldown only applies to createNewBlock(), which is typically called
- * once per connected client. Subsequent templates are provided by waitNext().
- *
- * @param last_tip tip at the start of the cooldown window.
- * @param interrupt_mining set to true to interrupt the cooldown.
- *
- * @returns false if interrupted.
- */
-bool CooldownIfHeadersAhead(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const BlockRef& last_tip, bool& interrupt_mining);
} // namespace node
#endif // BITCOIN_NODE_MINER_H
### src/rpc/blockchain.cpp
@@ -25,12 +25,12 @@
#include <index/base.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
-#include <interfaces/mining.h>
#include <interfaces/types.h>
#include <kernel/coinstats.h>
#include <logging/timer.h>
#include <net.h>
#include <net_processing.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
@@ -100,7 +100,6 @@ using kernel::CCoinsStats;
using kernel::CoinStatsHashType;
using interfaces::BlockRef;
-using interfaces::Mining;
using node::BlockManager;
using node::NodeContext;
using node::SnapshotMetadata;
@@ -356,24 +355,24 @@ static RPCMethod waitfornewblock()
if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
NodeContext& node = EnsureAnyNodeContext(request.context);
- Mining& miner = EnsureMining(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
- // If the caller provided a current_tip value, pass it to waitTipChanged().
+ // If the caller provided a current_tip value, pass it to WaitTipChanged().
//
- // If the caller did not provide a current tip hash, call getTip() to get
+ // If the caller did not provide a current tip hash, call GetTip() to get
// one and wait for the tip to be different from this value. This mode is
// less reliable because if the tip changed between waitfornewblock calls,
// it will need to change a second time before this call returns.
- BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
+ BlockRef current_block{CHECK_NONFATAL(block_template_manager.GetTip()).value()};
uint256 tip_hash{request.params[1].isNull()
? current_block.hash
: ParseHashV(request.params[1], "current_tip")};
// If the user provided an invalid current_tip then this call immediately
// returns the current tip.
- std::optional<BlockRef> block = timeout ? miner.waitTipChanged(tip_hash, std::chrono::milliseconds(timeout)) :
- miner.waitTipChanged(tip_hash);
+ std::optional<BlockRef> block = timeout ? block_template_manager.WaitTipChanged(tip_hash, std::chrono::milliseconds(timeout)) :
+ block_template_manager.WaitTipChanged(tip_hash);
// Return current block upon shutdown
if (block) current_block = *block;
@@ -418,10 +417,10 @@ static RPCMethod waitforblock()
if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
NodeContext& node = EnsureAnyNodeContext(request.context);
- Mining& miner = EnsureMining(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
// Abort if RPC came out of warmup too early
- BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
+ BlockRef current_block{CHECK_NONFATAL(block_template_manager.GetTip()).value()};
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
while (current_block.hash != hash) {
@@ -430,9 +429,9 @@ static RPCMethod waitforblock()
auto now{std::chrono::steady_clock::now()};
if (now >= deadline) break;
const MillisecondsDouble remaining{deadline - now};
- block = miner.waitTipChanged(current_block.hash, remaining);
+ block = block_template_manager.WaitTipChanged(current_block.hash, remaining);
} else {
- block = miner.waitTipChanged(current_block.hash);
+ block = block_template_manager.WaitTipChanged(current_block.hash);
}
// Return current block upon shutdown
if (!block) break;
@@ -480,10 +479,10 @@ static RPCMethod waitforblockheight()
if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout");
NodeContext& node = EnsureAnyNodeContext(request.context);
- Mining& miner = EnsureMining(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
// Abort if RPC came out of warmup too early
- BlockRef current_block{CHECK_NONFATAL(miner.getTip()).value()};
+ BlockRef current_block{CHECK_NONFATAL(block_template_manager.GetTip()).value()};
const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout};
@@ -493,9 +492,9 @@ static RPCMethod waitforblockheight()
auto now{std::chrono::steady_clock::now()};
if (now >= deadline) break;
const MillisecondsDouble remaining{deadline - now};
- block = miner.waitTipChanged(current_block.hash, remaining);
+ block = block_template_manager.WaitTipChanged(current_block.hash, remaining);
} else {
- block = miner.waitTipChanged(current_block.hash);
+ block = block_template_manager.WaitTipChanged(current_block.hash);
}
// Return current block on shutdown
if (!block) break;
### src/rpc/mining.cpp
@@ -5,7 +5,6 @@
#include <bitcoin-build-config.h> // IWYU pragma: keep
-#include <interfaces/mining.h>
#include <rpc/register.h> // IWYU pragma: associated
#include <addresstype.h>
@@ -24,6 +23,7 @@
#include <key_io.h>
#include <net.h>
#include <netbase.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <node/miner.h>
@@ -78,8 +78,6 @@
#include <vector>
using interfaces::BlockRef;
-using interfaces::BlockTemplate;
-using interfaces::Mining;
using node::BlockAssembler;
using node::GetMinimumTime;
using node::NodeContext;
@@ -192,15 +190,15 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock&& block, uint64_t&
return true;
}
-static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const CScript& coinbase_output_script, int nGenerate, uint64_t nMaxTries)
+static UniValue generateBlocks(ChainstateManager& chainman, node::BlockTemplateManager& block_template_manager, const CScript& coinbase_output_script, int nGenerate, uint64_t nMaxTries)
{
UniValue blockHashes(UniValue::VARR);
while (nGenerate > 0 && !chainman.m_interrupt) {
- std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({ .coinbase_output_script = coinbase_output_script }, /*cooldown=*/false));
+ std::unique_ptr<node::CBlockTemplate> block_template{block_template_manager.CreateNewTemplate({.coinbase_output_script = coinbase_output_script})};
CHECK_NONFATAL(block_template);
std::shared_ptr<const CBlock> block_out;
- if (!GenerateBlock(chainman, block_template->getBlock(), nMaxTries, block_out, /*process_new_block=*/true)) {
+ if (!GenerateBlock(chainman, CBlock{block_template->block}, nMaxTries, block_out, /*process_new_block=*/true)) {
break;
}
@@ -277,10 +275,10 @@ static RPCMethod generatetodescriptor()
}
NodeContext& node = EnsureAnyNodeContext(request.context);
- Mining& miner = EnsureMining(node);
ChainstateManager& chainman = EnsureChainman(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
- return generateBlocks(chainman, miner, coinbase_output_script, num_blocks, max_tries);
+ return generateBlocks(chainman, block_template_manager, coinbase_output_script, num_blocks, max_tries);
},
};
}
@@ -323,12 +321,12 @@ static RPCMethod generatetoaddress()
}
NodeContext& node = EnsureAnyNodeContext(request.context);
- Mining& miner = EnsureMining(node);
ChainstateManager& chainman = EnsureChainman(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
CScript coinbase_output_script = GetScriptForDestination(destination);
- return generateBlocks(chainman, miner, coinbase_output_script, num_blocks, max_tries);
+ return generateBlocks(chainman, block_template_manager, coinbase_output_script, num_blocks, max_tries);
},
};
}
@@ -376,7 +374,7 @@ static RPCMethod generateblock()
}
NodeContext& node = EnsureAnyNodeContext(request.context);
- Mining& miner = EnsureMining(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
const CTxMemPool& mempool = EnsureMemPool(node);
std::vector<CTransactionRef> txs;
@@ -408,10 +406,10 @@ static RPCMethod generateblock()
{
LOCK(chainman.GetMutex());
{
- std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script}, /*cooldown=*/false)};
+ std::unique_ptr<node::CBlockTemplate> block_template{block_template_manager.CreateNewTemplate({.use_mempool = false, .coinbase_output_script = coinbase_output_script})};
CHECK_NONFATAL(block_template);
- block = block_template->getBlock();
+ block = block_template->block;
}
CHECK_NONFATAL(block.vtx.size() == 1);
@@ -504,7 +502,7 @@ static RPCMethod getmininginfo()
obj.pushKV("target", GetTarget(tip, chainman.GetConsensus().powLimit).GetHex());
obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request));
obj.pushKV("pooledtx", mempool.size());
- const auto mining_options{node::FlattenMiningOptions(node.mining_args)};
+ const auto mining_options{node::FlattenMiningOptions(EnsureBlockTemplateManager(node).BlockCreateArgs())};
obj.pushKV("blockmintxfee", ValueFromAmount(CHECK_NONFATAL(mining_options.block_min_fee_rate)->GetFeePerK()));
obj.pushKV("chain", chainman.GetParams().GetChainTypeString());
@@ -741,7 +739,7 @@ static RPCMethod getblocktemplate()
{
NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
- Mining& miner = EnsureMining(node);
+ node::BlockTemplateManager& block_template_manager = EnsureBlockTemplateManager(node);
std::string strMode = "template";
UniValue lpval = NullUniValue;
@@ -796,13 +794,13 @@ static RPCMethod getblocktemplate()
if (strMode != "template")
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
- if (!miner.isTestChain()) {
+ if (!chainman.GetParams().IsTestChain()) {
const CConnman& connman = EnsureConnman(node);
if (connman.GetNodeCount(ConnectionDirection::Both) == 0) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, CLIENT_NAME " is not connected!");
}
- if (miner.isInitialBlockDownload()) {
+ if (chainman.IsInitialBlockDownload()) {
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, CLIENT_NAME " is in initial sync and waiting for blocks...");
}
}
@@ -811,7 +809,7 @@ static RPCMethod getblocktemplate()
const CTxMemPool& mempool = EnsureMemPool(node);
WAIT_LOCK(cs_main, cs_main_lock);
- uint256 tip{CHECK_NONFATAL(miner.getTip()).value().hash};
+ uint256 tip{CHECK_NONFATAL(block_template_manager.GetTip()).value().hash};
// Long Polling (BIP22)
if (!lpval.isNull()) {
@@ -856,7 +854,7 @@ static RPCMethod getblocktemplate()
while (IsRPCRunning()) {
// If hashWatchedChain is not a real block hash, this will
// return immediately.
- std::optional<BlockRef> maybe_tip{miner.waitTipChanged(hashWatchedChain, checktxtime)};
+ std::optional<BlockRef> maybe_tip{block_template_manager.WaitTipChanged(hashWatchedChain, checktxtime)};
// Node is shutting down
if (!maybe_tip) break;
tip = maybe_tip->hash;
@@ -870,7 +868,7 @@ static RPCMethod getblocktemplate()
checktxtime = std::chrono::seconds(10);
}
}
- tip = CHECK_NONFATAL(miner.getTip()).value().hash;
+ tip = CHECK_NONFATAL(block_template_manager.GetTip()).value().hash;
if (!IsRPCRunning())
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
@@ -892,7 +890,7 @@ static RPCMethod getblocktemplate()
// Update block
static CBlockIndex* pindexPrev;
static int64_t time_start;
- static std::unique_ptr<BlockTemplate> block_template;
+ static std::unique_ptr<node::CBlockTemplate> block_template;
if (!pindexPrev || pindexPrev->GetBlockHash() != tip ||
(mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5))
{
@@ -908,19 +906,19 @@ static RPCMethod getblocktemplate()
// a delay to each getblocktemplate call. This differs from typical
// long-lived IPC usage, where the overhead is paid only when creating
// the initial template.
- block_template = miner.createNewBlock({}, /*cooldown=*/false);
+ block_template = block_template_manager.CreateNewTemplate({});
CHECK_NONFATAL(block_template);
// Need to update only after we know createNewBlock succeeded
pindexPrev = pindexPrevNew;
}
CHECK_NONFATAL(pindexPrev);
- CBlock block{block_template->getBlock()};
+ CBlockHeader block_header{block_template->block};
// Update nTime
- UpdateTime(&block, consensusParams, pindexPrev);
- block.nNonce = 0;
+ UpdateTime(&block_header, consensusParams, pindexPrev);
+ block_header.nNonce = 0;
// NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration
const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT);
@@ -929,11 +927,11 @@ static RPCMethod getblocktemplate()
UniValue transactions(UniValue::VARR);
std::map<Txid, int64_t> setTxIndex;
- std::vector<CAmount> tx_fees{block_template->getTxFees()};
- std::vector<int64_t> tx_sigops{block_template->getTxSigops()};
+ const std::vector<CAmount>& tx_fees{block_template->vTxFees};
+ const std::vector<int64_t>& tx_sigops{block_template->vTxSigOpsCost};
int i = 0;
- for (const auto& it : block.vtx) {
+ for (const auto& it : block_template->block.vtx) {
const CTransaction& tx = *it;
Txid txHash = tx.GetHash();
setTxIndex[txHash] = i++;
@@ -970,7 +968,7 @@ static RPCMethod getblocktemplate()
UniValue aux(UniValue::VOBJ);
- arith_uint256 hashTarget = arith_uint256().SetCompact(block.nBits);
+ arith_uint256 hashTarget = arith_uint256().SetCompact(block_header.nBits);
UniValue aMutable(UniValue::VARR);
aMutable.push_back("time");
@@ -1002,16 +1000,16 @@ static RPCMethod getblocktemplate()
vbavailable.pushKV(gbt_rule_value(name, info.gbt_optional_rule), info.bit);
if (!info.gbt_optional_rule && !setClientRules.contains(name)) {
// If the client doesn't support this, don't indicate it in the [default] version
- block.nVersion &= ~info.mask;
+ block_header.nVersion &= ~info.mask;
}
}
for (const auto& [name, info] : gbtstatus.locked_in) {
- block.nVersion |= info.mask;
+ block_header.nVersion |= info.mask;
vbavailable.pushKV(gbt_rule_value(name, info.gbt_optional_rule), info.bit);
if (!info.gbt_optional_rule && !setClientRules.contains(name)) {
// If the client doesn't support this, don't indicate it in the [default] version
- block.nVersion &= ~info.mask;
+ block_header.nVersion &= ~info.mask;
}
}
@@ -1023,15 +1021,15 @@ static RPCMethod getblocktemplate()
}
}
- result.pushKV("version", block.nVersion);
+ result.pushKV("version", block_header.nVersion);
result.pushKV("rules", std::move(aRules));
result.pushKV("vbavailable", std::move(vbavailable));
result.pushKV("vbrequired", 0);
- result.pushKV("previousblockhash", block.hashPrevBlock.GetHex());
+ result.pushKV("previousblockhash", block_header.hashPrevBlock.GetHex());
result.pushKV("transactions", std::move(transactions));
result.pushKV("coinbaseaux", std::move(aux));
- result.pushKV("coinbasevalue", block.vtx[0]->vout[0].nValue);
+ result.pushKV("coinbasevalue", block_template->block.vtx[0]->vout[0].nValue);
result.pushKV("longpollid", tip.GetHex() + ToString(nTransactionsUpdatedLast));
result.pushKV("target", hashTarget.GetHex());
result.pushKV("mintime", GetMinimumTime(pindexPrev, consensusParams.DifficultyAdjustmentInterval()));
@@ -1050,15 +1048,15 @@ static RPCMethod getblocktemplate()
if (!fPreSegWit) {
result.pushKV("weightlimit", MAX_BLOCK_WEIGHT);
}
- result.pushKV("curtime", block.GetBlockTime());
- result.pushKV("bits", strprintf("%08x", block.nBits));
+ result.pushKV("curtime", block_header.GetBlockTime());
+ result.pushKV("bits", strprintf("%08x", block_header.nBits));
result.pushKV("height", pindexPrev->nHeight + 1);
if (consensusParams.signet_blocks) {
result.pushKV("signet_challenge", HexStr(consensusParams.signet_challenge));
}
- if (auto coinbase{block_template->getCoinbaseTx()}; coinbase.required_outputs.size() > 0) {
+ if (const auto& coinbase{block_template->m_coinbase_tx}; coinbase.required_outputs.size() > 0) {
CHECK_NONFATAL(coinbase.required_outputs.size() == 1); // Only one output is currently expected
result.pushKV("default_witness_commitment", HexStr(coinbase.required_outputs[0].scriptPubKey));
}
### src/rpc/server_util.cpp
@@ -108,12 +108,12 @@ CConnman& EnsureConnman(const NodeContext& node)
return *node.connman;
}
-interfaces::Mining& EnsureMining(const NodeContext& node)
+node::BlockTemplateManager& EnsureBlockTemplateManager(const NodeContext& node)
{
- if (!node.mining) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Node miner not found");
+ if (!node.block_template_manager) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Block template manager not found");
}
- return *node.mining;
+ return *node.block_template_manager;
}
PeerManager& EnsurePeerman(const NodeContext& node)
### src/rpc/server_util.h
@@ -22,10 +22,8 @@ struct Params;
namespace node {
struct NodeContext;
+class BlockTemplateManager;
} // namespace node
-namespace interfaces {
-class Mining;
-} // namespace interfaces
node::NodeContext& EnsureAnyNodeContext(const std::any& context);
CTxMemPool& EnsureMemPool(const node::NodeContext& node);
@@ -39,7 +37,7 @@ ChainstateManager& EnsureAnyChainman(const std::any& context);
FeeRateEstimatorManager& EnsureFeeEstimatorMan(const node::NodeContext& node);
FeeRateEstimatorManager& EnsureAnyFeeEstimatorMan(const std::any& context);
CConnman& EnsureConnman(const node::NodeContext& node);
-interfaces::Mining& EnsureMining(const node::NodeContext& node);
+node::BlockTemplateManager& EnsureBlockTemplateManager(const node::NodeContext& node);
PeerManager& EnsurePeerman(const node::NodeContext& node);
AddrMan& EnsureAddrman(const node::NodeContext& node);
AddrMan& EnsureAnyAddrman(const std::any& context);
### src/test/fuzz/connect_block.cpp
@@ -6,6 +6,7 @@
#include <chain.h>
#include <consensus/amount.h>
#include <consensus/merkle.h>
+#include <node/block_template_manager.h>
#include <node/kernel_notifications.h>
#include <node/mining_types.h>
#include <primitives/block.h>
@@ -167,9 +168,11 @@ static void LoadCurrentChain()
void ResetChainman(TestingSetup& setup)
{
SetMockTime(setup.m_node.chainman->GetParams().GenesisBlock().Time());
+ setup.m_node.block_template_manager.reset();
setup.m_node.chainman.reset();
setup.m_node.notifications->m_shutdown_on_fatal_error = false;
setup.m_make_chainman();
+ setup.CreateBlockTemplateManager();
setup.LoadVerifyActivateChainstate();
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
### src/test/fuzz/rpc.cpp
@@ -73,6 +73,7 @@ const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
"enumeratesigners",
"echoipc", // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.)
"exportasmap", // avoid writing to disk
+ "generateblock", // avoid chain state changes and disk writes (mines and submits a block)
"generatetoaddress", // avoid prohibitively slow execution (when `num_blocks` is large)
"generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large)
"gettxoutproof", // avoid prohibitively slow execution
@@ -82,6 +83,9 @@ const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
"savemempool", // disabled as a precautionary measure: may take a file path argument in the future
"setban", // avoid DNS lookups
"stop", // avoid shutdown state
+ "waitforblock", // avoid blocking forever (tip never changes during fuzzing; no timeout by default)
+ "waitforblockheight", // avoid blocking forever (tip never changes during fuzzing; no timeout by default)
+ "waitfornewblock", // avoid blocking forever (tip never changes during fuzzing; no timeout by default)
};
// RPC commands which are safe for fuzzing.
@@ -107,7 +111,6 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"estimatesmartfee",
"finalizepsbt",
"generate",
- "generateblock",
"getaddednodeinfo",
"getaddrmaninfo",
"getbestblockhash",
@@ -184,9 +187,6 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"verifychain",
"verifymessage",
"verifytxoutproof",
- "waitforblock",
- "waitforblockheight",
- "waitfornewblock",
};
UniValue ConsumeBasicRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
### src/test/fuzz/utxo_snapshot.cpp
@@ -8,6 +8,7 @@
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <kernel/coinstats.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
#include <node/utxo_snapshot.h>
#include <primitives/block.h>
@@ -211,9 +212,11 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
Assert(!dirty_chainman);
}
if (dirty_chainman) {
+ setup.m_node.block_template_manager.reset();
setup.m_node.chainman.reset();
setup.m_make_chainman();
setup.LoadVerifyActivateChainstate();
+ setup.CreateBlockTemplateManager();
}
}
### src/test/miner_tests.cpp
@@ -12,6 +12,7 @@
#include <interfaces/mining.h>
#include <interfaces/types.h>
#include <kernel/chainparams.h>
+#include <node/block_template_manager.h>
#include <node/miner.h>
#include <node/mining_args.h>
#include <node/mining_types.h>
@@ -53,7 +54,6 @@
using namespace util::hex_literals;
using interfaces::BlockTemplate;
using interfaces::Mining;
-using node::BlockAssembler;
using node::BlockCreateOptions;
namespace miner_tests {
@@ -71,6 +71,7 @@ struct MinerTestingSetup : public TestingSetup {
}
CTxMemPool& MakeMempool()
{
+ m_node.block_template_manager.reset();
// Delete the previous mempool to ensure with valgrind that the old
// pointer is not accessed, when the new one should be accessed
// instead.
@@ -83,6 +84,7 @@ struct MinerTestingSetup : public TestingSetup {
opts.limits.cluster_size_vbytes = 1'200'000;
m_node.mempool = std::make_unique<CTxMemPool>(opts, error);
Assert(error.empty());
+ CreateBlockTemplateManager();
return *m_node.mempool;
}
std::unique_ptr<Mining> MakeMining()
@@ -217,11 +219,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
// Test the inclusion of package feerates in the block template and ensure they are sequential.
// Can't use the Mining interface because it needs access to m_package_feerates.
- const auto block_package_feerates = BlockAssembler{
- m_node.chainman->ActiveChainstate(),
- &tx_mempool,
- MergeMiningOptions(options, m_node.mining_args),
- }.CreateNewBlock()->m_package_feerates;
+ const auto block_package_feerates = Assert(m_node.block_template_manager)->CreateNewTemplate(options)->m_package_feerates;
BOOST_CHECK(block_package_feerates.size() == 2);
// parent_tx and high_fee_tx are added to the block as a package.
@@ -968,4 +966,20 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
TestSigOpsAdjustedWeightChunkLimit(scriptPubKey, txFirst);
}
+BOOST_AUTO_TEST_CASE(block_template_manager)
+{
+ auto& block_template_manager = *Assert(m_node.block_template_manager);
+ BlockCreateOptions options;
+ options.use_mempool = false;
+ auto block_template = block_template_manager.CreateNewTemplate(options);
+ BOOST_REQUIRE(block_template);
+ const CBlock& block{block_template->block};
+ // Without the mempool the template holds only the coinbase, and the per-tx
+ // fee/sigops vectors exclude it.
+ BOOST_CHECK_EQUAL(block.vtx.size(), 1U);
+ BOOST_CHECK(block.vtx[0]->IsCoinBase());
+ BOOST_CHECK(block_template->vTxFees.empty());
+ BOOST_CHECK(block_template->vTxSigOpsCost.empty());
+}
+
BOOST_AUTO_TEST_SUITE_END()
### src/test/peerman_tests.cpp
@@ -5,8 +5,9 @@
#include <chain.h>
#include <chainparams.h>
#include <consensus/params.h>
-#include <interfaces/mining.h>
#include <net_processing.h>
+#include <node/block_template_manager.h>
+#include <node/miner.h>
#include <pow.h>
#include <primitives/block.h>
#include <protocol.h>
@@ -31,10 +32,10 @@ static void mineBlock(node::NodeContext& node, FakeNodeClock& clock, std::chrono
{
auto curr_time = GetTime<std::chrono::seconds>();
clock.set(block_time); // update time so the block is created with it
- auto mining{interfaces::MakeMining(node)};
- auto block_template{mining->createNewBlock({}, /*cooldown=*/false)};
+ auto& block_template_manager{*Assert(node.block_template_manager)};
+ auto block_template{block_template_manager.CreateNewTemplate({})};
BOOST_REQUIRE(block_template);
- CBlock block{block_template->getBlock()};
+ CBlock block{block_template->block};
while (!CheckProofOfWork(block.GetHash(), block.nBits, node.chainman->GetConsensus())) ++block.nNonce;
block.fChecked = true; // little speedup
clock.set(curr_time); // process block at current time
### src/test/util/mining.cpp
@@ -9,9 +9,10 @@
#include <chainparams.h>
#include <consensus/merkle.h>
#include <consensus/validation.h>
-#include <interfaces/mining.h>
#include <key_io.h>
+#include <node/block_template_manager.h>
#include <node/context.h>
+#include <node/miner.h>
#include <pow.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
@@ -80,16 +81,16 @@ bool BuildChain(const NodeContext& node, const CBlockIndex* pindex,
size_t length,
std::vector<std::shared_ptr<CBlock>>& chain)
{
- auto mining{interfaces::MakeMining(node)};
+ auto& block_template_manager{*Assert(node.block_template_manager)};
const Consensus::Params& consensus{Assert(node.chainman)->GetConsensus()};
chain.resize(length);
for (auto& chain_block : chain) {
- auto block_template{mining->createNewBlock({
+ auto block_template{block_template_manager.CreateNewTemplate({
.use_mempool = false,
.coinbase_output_script = coinbase_script_pub_key,
- }, /*cooldown=*/false)};
- CBlock block{Assert(block_template)->getBlock()};
+ })};
+ CBlock block{Assert(block_template)->block};
// The template is built on the active tip, so repoint it at pindex and
// redo the fields that depend on the predecessor.
@@ -172,9 +173,9 @@ COutPoint ProcessBlock(const NodeContext& node, const std::shared_ptr<CBlock>& b
std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node,
const node::BlockCreateOptions& assembler_options)
{
- auto mining = interfaces::MakeMining(node);
- auto block_template = mining->createNewBlock(assembler_options, /*cooldown=*/false);
- auto block = std::make_shared<CBlock>(Assert(block_template)->getBlock());
+ auto& block_template_manager = *Assert(node.block_template_manager);
+ auto block_template = block_template_manager.CreateNewTemplate(assembler_options);
+ auto block = std::make_shared<CBlock>(Assert(block_template)->block);
LOCK(cs_main);
block->nTime = Assert(node.chainman)->ActiveChain().Tip()->GetMedianTimePast() + 1;
### src/test/util/setup_common.cpp
@@ -17,7 +17,6 @@
#include <dbwrapper.h>
#include <init.h>
#include <interfaces/chain.h>
-#include <interfaces/mining.h>
#include <kernel/caches.h>
#include <kernel/context.h>
#include <key.h>
@@ -26,6 +25,7 @@
#include <net_processing.h>
#include <netbase.h>
#include <netgroup.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
#include <node/chainstate.h>
#include <node/context.h>
@@ -330,12 +330,22 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown_signal), chainman_opts, blockman_opts);
};
m_make_chainman();
+ CreateBlockTemplateManager();
+}
+
+void ChainTestingSetup::CreateBlockTemplateManager()
+{
+ auto mining_args{node::ReadMiningArgs(*Assert(m_node.args))};
+ Assert(mining_args);
+ Assert(!m_node.block_template_manager);
+ m_node.block_template_manager = std::make_unique<node::BlockTemplateManager>(*m_node.mempool, *m_node.chainman, *Assert(m_node.notifications), std::move(*mining_args));
}
ChainTestingSetup::~ChainTestingSetup()
{
if (m_node.scheduler) m_node.scheduler->stop();
if (m_node.validation_signals) m_node.validation_signals->FlushBackgroundCallbacks();
+ m_node.block_template_manager.reset();
m_node.connman.reset();
m_node.banman.reset();
m_node.addrman.reset();
@@ -394,9 +404,6 @@ TestingSetup::TestingSetup(
m_node.args->GetIntArg("-checkaddrman", 0));
m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
m_node.connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params()); // Deterministic randomness for tests.
- auto mining_args{node::ReadMiningArgs(*m_node.args)};
- Assert(mining_args);
- m_node.mining_args = std::move(*mining_args);
PeerManager::Options peerman_opts;
ApplyArgsManOptions(*m_node.args, peerman_opts);
peerman_opts.deterministic_rng = true;
@@ -447,13 +454,13 @@ CBlock TestChain100Setup::CreateBlock(
const std::vector<CMutableTransaction>& txns,
const CScript& scriptPubKey)
{
- auto mining{interfaces::MakeMining(m_node)};
- auto block_template{mining->createNewBlock({
+ auto& block_template_manager{*Assert(m_node.block_template_manager)};
+ auto block_template{block_template_manager.CreateNewTemplate({
.use_mempool = false,
.coinbase_output_script = scriptPubKey,
- }, /*cooldown=*/false)};
+ })};
Assert(block_template);
- CBlock block{block_template->getBlock()};
+ CBlock block{block_template->block};
Assert(block.vtx.size() == 1);
for (const CMutableTransaction& tx : txns) {
### src/test/util/setup_common.h
@@ -106,6 +106,10 @@ struct ChainTestingSetup : public BasicTestingSetup {
explicit ChainTestingSetup(ChainType chainType = ChainType::MAIN, TestOpts = {});
~ChainTestingSetup();
+ //! Create the block template manager. Must be destroyed before resetting
+ //! any of its dependencies.
+ void CreateBlockTemplateManager();
+
// Supplies a chainstate, if one is needed
void LoadVerifyActivateChainstate();
};
### src/test/util/validation.cpp
@@ -6,6 +6,7 @@
#include <coins.h>
#include <consensus/consensus.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
#include <node/mining_types.h>
#include <test/util/mining.h>
@@ -110,13 +111,15 @@ std::vector<std::pair<COutPoint, CAmount>> ResetChainmanAndMempool(TestingSetup&
node_clock.set(setup.m_node.chainman->GetParams().GenesisBlock().Time());
bilingual_str error{};
+ setup.m_node.block_template_manager.reset();
setup.m_node.mempool.reset();
setup.m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(setup.m_node), error);
Assert(error.empty());
setup.m_node.chainman.reset();
setup.m_make_chainman();
setup.LoadVerifyActivateChainstate();
+ setup.CreateBlockTemplateManager();
node::BlockCreateOptions options;
options.coinbase_output_script = P2WSH_OP_TRUE;
### src/test/validation_block_tests.cpp
@@ -7,8 +7,9 @@
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <consensus/validation.h>
-#include <interfaces/mining.h>
+#include <node/block_template_manager.h>
#include <node/blockstorage.h>
+#include <node/miner.h>
#include <pow.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
@@ -80,12 +81,12 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash)
static int i = 0;
static uint64_t time = Params().GenesisBlock().nTime;
- auto mining{interfaces::MakeMining(m_node)};
- auto block_template{mining->createNewBlock({
+ auto& block_template_manager{*Assert(m_node.block_template_manager)};
+ auto block_template{block_template_manager.CreateNewTemplate({
.coinbase_output_script = CScript{} << i++ << OP_TRUE,
- }, /*cooldown=*/false)};
+ })};
BOOST_REQUIRE(block_template);
- auto pblock = std::make_shared<CBlock>(block_template->getBlock());
+ auto pblock = std::make_shared<CBlock>(block_template->block);
pblock->hashPrevBlock = prev_hash;
pblock->nTime = ++time;
@@ -350,12 +351,12 @@ BOOST_AUTO_TEST_CASE(witness_commitment_index)
LOCK(Assert(m_node.chainman)->GetMutex());
CScript pubKey;
pubKey << 1 << OP_TRUE;
- auto mining{interfaces::MakeMining(m_node)};
- auto block_template{mining->createNewBlock({
+ auto& block_template_manager{*Assert(m_node.block_template_manager)};
+ auto block_template{block_template_manager.CreateNewTemplate({
.coinbase_output_script = pubKey,
- }, /*cooldown=*/false)};
+ })};
BOOST_REQUIRE(block_template);
- CBlock pblock{block_template->getBlock()};
+ CBlock pblock{block_template->block};
CTxOut witness;
witness.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT);
### src/test/validation_chainstatemanager_tests.cpp
@@ -5,6 +5,7 @@
#include <chainparams.h>
#include <consensus/validation.h>
#include <kernel/disconnected_transactions.h>
+#include <node/block_template_manager.h>
#include <node/chainstatemanager_args.h>
#include <node/kernel_notifications.h>
#include <node/utxo_snapshot.h>
@@ -431,6 +432,7 @@ struct SnapshotTestSetup : TestChain100Setup {
{
// Process all callbacks referring to the old manager before wiping it.
m_node.validation_signals->SyncWithValidationInterfaceQueue();
+ m_node.block_template_manager.reset();
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.m_chainstates.size(), 0);
@@ -455,6 +457,7 @@ struct SnapshotTestSetup : TestChain100Setup {
// new one.
m_node.chainman.reset();
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown_signal), chainman_opts, blockman_opts);
+ CreateBlockTemplateManager();
}
return *Assert(m_node.chainman);
}
### src/wallet/test/fuzz/fees.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <node/block_template_manager.h>
#include <policy/fees/estimator_man.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
@@ -85,7 +86,9 @@ FUZZ_TARGET(wallet_fees, .init = initialize_setup)
.min_relay_feerate = CFeeRate{ConsumeMoney(fuzzed_data_provider, 1'000'000)},
.dust_relay_feerate = CFeeRate{ConsumeMoney(fuzzed_data_provider, 1'000'000)}
};
+ node.block_template_manager.reset();
node.mempool = std::make_unique<CTxMemPool>(mempool_opts, error);
+ g_setup->CreateBlockTemplateManager();
std::unique_ptr<FeeRateEstimatorManager> fee_estimator_man = std::make_unique<FuzzedFeeEstimatorMan>(fuzzed_data_provider, *node.mempool, *node.chainman);
g_setup->SetFeeEstimatorMan(std::move(fee_estimator_man));
auto target_feerate{CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/1'000'000)}};Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.