From 8e4294a7abd0ac30699be269c9dd3070cf4f21d8 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 25 Nov 2025 18:16:43 +0100 Subject: [PATCH 1/5] rpc: move static block_template to node context The getblocktemplate RPC uses a static BlockTemplate, which goes out of scope only after the node completed its shutdown sequence. This becomes a problem when a later commit implements a destructor that uses m_node. --- src/node/context.h | 3 +++ src/rpc/mining.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/node/context.h b/src/node/context.h index 848c872fd4a9..b4da0cdea39d 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -27,6 +27,7 @@ class NetGroupManager; class PeerManager; class TorController; namespace interfaces { +class BlockTemplate; class Chain; class ChainClient; class Mining; @@ -67,6 +68,8 @@ struct NodeContext { std::unique_ptr addrman; std::unique_ptr connman; std::unique_ptr mempool; + //! Cache latest getblocktemplate result for BIP 22 long polling + std::unique_ptr gbt_result; std::unique_ptr netgroupman; std::unique_ptr fee_estimator; std::unique_ptr peerman; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index dcf5ee26457a..0aa4ce9e30fd 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -859,7 +859,7 @@ static RPCMethod getblocktemplate() // Update block static CBlockIndex* pindexPrev; static int64_t time_start; - static std::unique_ptr block_template; + std::unique_ptr& block_template{node.gbt_result}; if (!pindexPrev || pindexPrev->GetBlockHash() != tip || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5)) { From 56609d959839c6f150f438f48c3db4bae3713492 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 21 Nov 2025 16:12:27 +0100 Subject: [PATCH 2/5] mining: track non-mempool memory usage IPC clients can hold on to block templates indefinately, which has the same impact as when the node holds a shared pointer to the CBlockTemplate. Because each template in turn tracks CTransactionRefs, transactions that are removed from the mempool will not have their memory cleared. This commit adds bookkeeping to the block template constructor and destructor that will let us track the resulting memory footprint. Co-authored-by: Vasil Dimov --- src/node/context.h | 12 +++++++++++- src/node/interfaces.cpp | 20 +++++++++++++++++++- src/node/types.h | 7 +++++++ src/util/hasher.h | 16 ++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/node/context.h b/src/node/context.h index b4da0cdea39d..c70d5ef645a2 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -5,10 +5,13 @@ #ifndef BITCOIN_NODE_CONTEXT_H #define BITCOIN_NODE_CONTEXT_H +#include + #include #include #include #include +#include #include #include @@ -68,7 +71,14 @@ struct NodeContext { std::unique_ptr addrman; std::unique_ptr connman; std::unique_ptr mempool; - //! Cache latest getblocktemplate result for BIP 22 long polling + Mutex template_state_mutex; + //! Track how many templates (which we hold on to on behalf of connected IPC + //! clients) are referencing each transaction. + TxTemplateMap template_tx_refs GUARDED_BY(template_state_mutex); + //! Cache latest getblocktemplate result for BIP 22 long polling. Must be + //! cleared before template_tx_refs because the destructor decrements the + //! count in template_tx_refs of each transaction in the template and aborts + //! if an entry is missing. std::unique_ptr gbt_result; std::unique_ptr netgroupman; std::unique_ptr fee_estimator; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 16db8692a154..ba5e8dead47d 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -70,6 +70,7 @@ #include #include #include +#include #include #include @@ -873,7 +874,24 @@ class BlockTemplateImpl : public BlockTemplate m_block_template(std::move(block_template)), m_node(node) { - assert(m_block_template); + // Don't track the dummy coinbase, because it can be modified in-place + // by submitSolution() + LOCK(m_node.template_state_mutex); + for (const CTransactionRef& tx : Assert(m_block_template)->block.vtx | std::views::drop(1)) { + m_node.template_tx_refs[tx]++; + } + } + + ~BlockTemplateImpl() + { + LOCK(m_node.template_state_mutex); + for (const CTransactionRef& tx : m_block_template->block.vtx | std::views::drop(1)) { + auto ref_count{m_node.template_tx_refs.find(tx)}; + if (!Assume(ref_count != m_node.template_tx_refs.end())) break; + if (--ref_count->second == 0) { + m_node.template_tx_refs.erase(ref_count); + } + } } CBlockHeader getBlockHeader() override diff --git a/src/node/types.h b/src/node/types.h index 01e74528e06f..056218919d95 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -21,6 +21,8 @@ #include #include