From 99848eea0bc6068ce8bca7ea542be9cfb2332331 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Wed, 18 Dec 2024 10:42:35 +0700 Subject: [PATCH 01/11] Add waitNext() to BlockTemplate interface --- src/interfaces/mining.h | 12 +++++- src/ipc/capnp/mining.capnp | 6 +++ src/node/interfaces.cpp | 84 +++++++++++++++++++++++++++++++++++++- src/node/types.h | 18 ++++++++ src/test/miner_tests.cpp | 24 ++++++++--- 5 files changed, 135 insertions(+), 9 deletions(-) diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index bc5955ded6628..49aa53feb1dcf 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -7,7 +7,7 @@ #include // for CAmount #include // for BlockRef -#include // for BlockCreateOptions +#include // for BlockCreateOptions, BlockWaitOptions #include // for CBlock, CBlockHeader #include // for CTransactionRef #include // for int64_t @@ -56,6 +56,16 @@ class BlockTemplate * @returns if the block was processed, independent of block validity */ virtual bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) = 0; + + /** + * Waits for fees in the next block to rise, a new tip or the timeout. + * + * @param[in] options Control the timeout (default forever) and by how much total fees + * for the next block should rise (default infinite). + * + * @returns a new BlockTemplate or nullptr if the timeout occurs. + */ + virtual std::unique_ptr waitNext(const node::BlockWaitOptions options = {}) = 0; }; //! Interface giving clients (RPC, Stratum v2 Template Provider in the future) diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp index 50b07bda708b7..bc1005ffa82cd 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -31,6 +31,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { getWitnessCommitmentIndex @7 (context: Proxy.Context) -> (result: Int32); getCoinbaseMerklePath @8 (context: Proxy.Context) -> (result: List(Data)); submitSolution @9 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool); + waitNext @10 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate); } struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") { @@ -39,6 +40,11 @@ struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") { coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops"); } +struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") { + timeout @0 : Float64 $Proxy.name("timeout"); + feeThreshold @1 : Int64 $Proxy.name("fee_threshold"); +} + # Note: serialization of the BlockValidationState C++ type is somewhat fragile # and using the struct can be awkward. It would be good if testBlockValidity # method were changed to return validity information in a simpler format. diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index fe88a6dad1108..372f84aa4cdf2 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -81,6 +81,7 @@ using interfaces::Mining; using interfaces::Node; using interfaces::WalletLoader; using node::BlockAssembler; +using node::BlockWaitOptions; using util::Join; namespace node { @@ -871,7 +872,7 @@ class ChainImpl : public Chain class BlockTemplateImpl : public BlockTemplate { public: - explicit BlockTemplateImpl(std::unique_ptr block_template, NodeContext& node) : m_block_template(std::move(block_template)), m_node(node) + explicit BlockTemplateImpl(BlockAssembler::Options assemble_options, std::unique_ptr block_template, NodeContext& node) : m_assemble_options(std::move(assemble_options)), m_block_template(std::move(block_template)), m_node(node) { assert(m_block_template); } @@ -936,9 +937,88 @@ class BlockTemplateImpl : public BlockTemplate return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr); } + std::unique_ptr waitNext(BlockWaitOptions options) override + { + // 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 + options.timeout; + const MillisecondsDouble tick{1000}; + + while (now <= deadline) { + bool tip_changed{false}; + { + WAIT_LOCK(notifications().m_tip_block_mutex, lock); + notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) { + Assert(notifications().TipBlock()); + tip_changed = notifications().TipBlock() != m_block_template->block.hashPrevBlock; + return tip_changed || chainman().m_interrupt; + }); + } + + if (chainman().m_interrupt) return nullptr; + + // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks. + LOCK(::cs_main); + /** + * The only way to determine if fees increased compared to the previous template, + * is to generate a fresh template. Cluster Mempool may allow for a more efficient + * way to determine how much (approximate) fees for the next block increased. + * + * We'll also create a new template if the tip changed during the last tick. + */ + if (options.fee_threshold < MAX_MONEY || tip_changed) { + auto block_template{std::make_unique(m_assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), m_assemble_options}.CreateNewBlock(), m_node)}; + + // If the tip changed, return the new template regardless of its fees. + if (tip_changed) { + return block_template; + } + + // Calculate the original template total fees if we haven't already + if (current_fees == -1) { + current_fees = 0; + for (CAmount fee : m_block_template->vTxFees) { + // Skip coinbase + if (fee < 0) continue; + current_fees += fee; + } + } + + CAmount new_fees = 0; + for (CAmount fee : block_template->m_block_template->vTxFees) { + // Skip coinbase + if (fee < 0) continue; + new_fees += fee; + if (new_fees >= current_fees + options.fee_threshold) return block_template; + } + } + + /** + * Break out of while when using mock time. While functional tests + * can call setmocktime to move the clock and exit this loop, a + * unit tests would need to spawn a thread to achieve this. + * Making the while loop use now < deadline won't work either, because + * then there's no wait to test its internals. + */ + if (now == deadline) break; + now = NodeClock::now(); + } + + return nullptr; + } + + const BlockAssembler::Options m_assemble_options; + const std::unique_ptr m_block_template; + NodeContext* context() { return &m_node; } ChainstateManager& chainman() { return *Assert(m_node.chainman); } + KernelNotifications& notifications() { return *Assert(m_node.notifications); } NodeContext& m_node; }; @@ -985,7 +1065,7 @@ class MinerImpl : public Mining { BlockAssembler::Options assemble_options{options}; ApplyArgsManOptions(*Assert(m_node.args), assemble_options); - return std::make_unique(BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node); + return std::make_unique(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node); } NodeContext* context() override { return &m_node; } diff --git a/src/node/types.h b/src/node/types.h index 4b0de084ab3d0..6d935f2ecb07f 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -14,7 +14,9 @@ #define BITCOIN_NODE_TYPES_H #include +#include #include