From 51dc31db12aa4f96a028664305cbbd3a7f1fc9b4 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Thu, 28 May 2026 19:51:42 +0200 Subject: [PATCH 1/6] test: refresh MiniWallet after node restart --- test/functional/interface_ipc_mining.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 4cd9c17c99f6..5a60bcfee433 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -326,6 +326,7 @@ def run_ipc_option_override_test(self): # blockReservedWeight per template request and are unaffected; later in # the test the IPC template includes a mempool transaction. self.restart_node(0, extra_args=[f"-blockreservedweight={MAX_BLOCK_WEIGHT}"]) + self.miniwallet.rescan_utxos() async def async_routine(): ctx, mining = await make_mining_ctx(self) From a619ececa4a1450612ba93813fb2795cb43fb245 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 25 Nov 2025 18:16:43 +0100 Subject: [PATCH 2/6] 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 b8b3274f090c..6bd8e8a454cd 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -29,6 +29,7 @@ class NetGroupManager; class PeerManager; class TorController; namespace interfaces { +class BlockTemplate; class Chain; class ChainClient; class Mining; @@ -69,6 +70,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 1d72c699c593..7eafd0529c91 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -889,7 +889,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 fd003b91bec3ec3b748b6177c58447e3f5afde00 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 21 Nov 2025 16:12:27 +0100 Subject: [PATCH 3/6] 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 | 10 +++++++++- src/node/interfaces.cpp | 21 ++++++++++++++++++++- src/node/mining_types.h | 7 +++++++ src/util/hasher.h | 16 ++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/node/context.h b/src/node/context.h index 6bd8e8a454cd..297aad1035be 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -70,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 + mutable Mutex template_state_mutex; + //! Track how many templates (which we hold on to on behalf of connected IPC + //! clients) are referencing each transaction. + mutable 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 2f68f414f0da..10509825d62b 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -78,8 +78,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -884,7 +886,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/mining_types.h b/src/node/mining_types.h index 9c4fbcbbafeb..c898ddf21335 100644 --- a/src/node/mining_types.h +++ b/src/node/mining_types.h @@ -17,11 +17,13 @@ #include #include