From 6f1298817a861cbd03888eccbdaf89a020315831 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 3a7488fd25ff..37b459d5877a 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -26,6 +26,7 @@ class ECC_Context; class NetGroupManager; class PeerManager; namespace interfaces { +class BlockTemplate; class Chain; class ChainClient; class Mining; @@ -66,6 +67,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 043e11ff63ad..9376c9a8d9cb 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -858,7 +858,7 @@ static RPCHelpMan 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 b22e5352976be42272f48825abfdd0a2a3e2cc26 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. --- src/node/context.h | 12 +++++++++++- src/node/interfaces.cpp | 20 +++++++++++++++++++- src/node/types.h | 6 ++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/node/context.h b/src/node/context.h index 37b459d5877a..e84b773cd395 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 @@ -67,7 +70,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 84fd5bb79474..e931b86b3cdc 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -67,6 +67,7 @@ #include #include #include +#include #include #include @@ -871,7 +872,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..164667772a48 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -20,6 +20,7 @@ #include #include #include