diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index c77f3c30a2638..0d6b402a9d80d 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -56,6 +56,17 @@ class BlockTemplate * @returns if the block was processed, independent of block validity */ virtual bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CMutableTransaction coinbase) = 0; + + /** + * Waits for fees in the next block to rise, a new tip or the timeout. + * + * @param[in] fee_threshold By how much total fees for the next block should rise. + * Default is to not monitor fee changes and only wait for a new chaintip. + * @param[in] timeout How long to wait. Default is forever. + * + * @returns a new BlockTemplate or nullptr if the timeout occurs + */ + virtual std::unique_ptr waitNext(CAmount fee_threshold = MAX_MONEY, MillisecondsDouble timeout = MillisecondsDouble::max()) = 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 5e0216aceacf1..be6f499e2b494 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -33,6 +33,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { getWitnessCommitmentIndex @6 (context: Proxy.Context) -> (result: Int32); getCoinbaseMerklePath @7 (context: Proxy.Context) -> (result: List(Data)); submitSolution@8 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool); + waitNext @9 (context: Proxy.Context, feeThreshold: Int64, timeout: Float64) -> (result: BlockTemplate); } struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") { diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index e4ae9400e37aa..0a8d0051d59a8 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -871,7 +871,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(CScript script_pub_key, BlockAssembler::Options assemble_options, std::unique_ptr block_template, NodeContext& node) : m_script_pub_key(script_pub_key), m_assemble_options(std::move(assemble_options)), m_block_template(std::move(block_template)), m_node(node) { assert(m_block_template); } @@ -938,9 +938,71 @@ class BlockTemplateImpl : public BlockTemplate return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr); } + std::unique_ptr waitNext(CAmount fee_threshold, MillisecondsDouble timeout) override + { + if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono + + CAmount current_fees = 0; + if (fee_threshold < MAX_MONEY) { + for (CAmount fee : m_block_template->vTxFees) { + // Skip coinbase + if (fee < 0) continue; + current_fees += fee; + } + } + + // 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{std::chrono::steady_clock::now()}; + const auto deadline = now + timeout; + const MillisecondsDouble tick{1000}; + std::unique_ptr block_template; + + while (now < deadline) { + { + 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) { + return notifications().m_tip_block != m_block_template->block.hashPrevBlock || 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. + block_template = std::make_unique(m_script_pub_key, m_assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), m_assemble_options}.CreateNewBlock(m_script_pub_key), m_node); + + // If the tip changed, return the new template regardless of its fees. + if (block_template->m_block_template->block.hashPrevBlock != m_block_template->block.hashPrevBlock) { + return block_template; + } + + 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 + fee_threshold) return block_template; + } + // + block_template.reset(); + } + + return block_template; + } + + const CScript m_script_pub_key; + 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; }; @@ -1008,7 +1070,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(script_pub_key), m_node); + return std::make_unique(script_pub_key, assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key), m_node); } NodeContext* context() override { return &m_node; }