From 9784818442f5f66fff7609025e7d530020d7d613 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 5 Dec 2025 19:10:25 +0100 Subject: [PATCH] mining: add getTransactionsByWitnessID() IPC method --- src/interfaces/mining.h | 9 +++++++++ src/ipc/capnp/mining.capnp | 1 + src/node/interfaces.cpp | 13 +++++++++++++ src/test/miner_tests.cpp | 11 +++++++++++ test/functional/interface_ipc_mining.py | 11 ++++++++++- test/functional/test_framework/messages.py | 9 +++++++-- 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index 7f69c7580db..884a1e16578 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -189,6 +189,15 @@ public: */ virtual std::vector getTransactionsByTxID(const std::vector& txids) = 0; + /** + * Fetch raw transactions from the mempool by wtxid. + * + * @param[in] wtxids witness transaction ids to look up + * @returns one entry per requested wtxid containing the + * transaction if found, otherwise nullptr + */ + virtual std::vector getTransactionsByWitnessID(const std::vector& wtxids) = 0; + //! Get internal node context. Useful for RPC and testing, //! but not accessible across processes. virtual const node::NodeContext* context() { return nullptr; } diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp index e1c9e116c26..5f0347fc3c9 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -27,6 +27,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") { interrupt @6 () -> (); submitBlock @7 (context :Proxy.Context, block: Data) -> (reason: Text, debug: Text, result: Bool); getTransactionsByTxID @8 (context :Proxy.Context, txids: List(Data)) -> (result: List(Data)); + getTransactionsByWitnessID @9 (context :Proxy.Context, wtxids: List(Data)) -> (result: List(Data)); } interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 33473fbf4ed..dafca125348 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1048,6 +1048,19 @@ public: return results; } + std::vector getTransactionsByWitnessID(const std::vector& wtxids) override + { + if (!m_node.mempool) return {}; + + std::vector results; + results.reserve(wtxids.size()); + LOCK(m_node.mempool->cs); + for (const auto& wtxid : wtxids) { + results.emplace_back(m_node.mempool->get(wtxid)); + } + return results; + } + const NodeContext* context() override { return &m_node; } ChainstateManager& chainman() { return *Assert(m_node.chainman); } KernelNotifications& notifications() { return *Assert(m_node.notifications); } diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index a366e5e494d..17644deae45 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -194,6 +194,17 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const BOOST_CHECK(raw_txs[0]); BOOST_CHECK(raw_txs[0]->GetHash() == hashParentTx); BOOST_CHECK(!raw_txs[1]); + // Test getTransactionsByWitnessID() + // tx has no witness, so just cast to Wtxid + const std::vector wtx_id_list{ + Wtxid::FromUint256(hashParentTx.ToUint256()), + Wtxid::FromUint256(uint256::ZERO) + }; + raw_txs = mining->getTransactionsByWitnessID(wtx_id_list); + BOOST_REQUIRE_EQUAL(raw_txs.size(), tx_id_list.size()); + BOOST_CHECK(raw_txs[0]); + BOOST_CHECK(raw_txs[0]->GetHash() == hashParentTx); + BOOST_CHECK(!raw_txs[1]); block_template = mining->createNewBlock(options, /*cooldown=*/false); BOOST_REQUIRE(block_template); block = block_template->getBlock(); diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index a81e76da05b..f023b12da7c 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -673,7 +673,7 @@ class IPCMiningTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) def run_transaction_lookup_test(self): - """Test getTransactionsByTxID().""" + """Test getTransactionsByTxID() and getTransactionsByWitnessID().""" self.log.info("Running transaction lookup test") async def async_routine(): @@ -688,11 +688,20 @@ class IPCMiningTest(BitcoinTestFramework): assert_equal(raw_txs_txid.result[1].hex(), tx2["hex"]) assert_equal(raw_txs_txid.result[2], b'') + self.log.debug("getTransactionsByWitnessID() returns mempool txs and nulls") + raw_txs_wtxid = await mining.getTransactionsByWitnessID(ctx, [tx1["tx"].wtxid, tx2["tx"].wtxid, bytes(32)]) + assert_equal(len(raw_txs_wtxid.result), 3) + assert_equal(raw_txs_wtxid.result[0].hex(), tx1["hex"]) + assert_equal(raw_txs_wtxid.result[1].hex(), tx2["hex"]) + assert_equal(raw_txs_wtxid.result[2], b'') + self.log.debug("Mined transactions are not returned") self.generate(self.nodes[0], 1) self.sync_all() raw_txs = await mining.getTransactionsByTxID(ctx, [tx1["tx"].txid]) assert_equal(raw_txs.result[0], b'') + raw_txs = await mining.getTransactionsByWitnessID(ctx, [tx1["tx"].wtxid]) + assert_equal(raw_txs.result[0], b'') asyncio.run(capnp.run(async_routine())) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 7614bd92c0b..9c5b15eca9f 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -687,15 +687,20 @@ class CTransaction: def serialize(self): return self.serialize_with_witness() + @property + def wtxid(self): + """Return wtxid (transaction hash with witness) as little-endian bytes.""" + return hash256(self.serialize_with_witness()) + @property def wtxid_hex(self): """Return wtxid (transaction hash with witness) as hex string.""" - return hash256(self.serialize())[::-1].hex() + return self.wtxid[::-1].hex() @property def wtxid_int(self): """Return wtxid (transaction hash with witness) as integer.""" - return uint256_from_str(hash256(self.serialize_with_witness())) + return uint256_from_str(self.wtxid) @property def txid(self):