From 0f466e1094edfafc87e1175f9cf73143cf61f361 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 5 Dec 2025 15:50:55 +0100 Subject: [PATCH 1/5] mempool: add lookup by witness hash Add a simple test for both the Txid and Wtxid variants of CTxMemPool::get(). --- src/test/mempool_tests.cpp | 26 ++++++++++++++++++++++++++ src/txmempool.cpp | 9 +++++++++ src/txmempool.h | 14 ++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 77024c3edc6..ba484f9ba57 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -24,6 +24,32 @@ public: using CTxMemPool::GetMinFee; }; +BOOST_AUTO_TEST_CASE(MempoolLookupTest) +{ + auto& pool = static_cast(*Assert(m_node.mempool)); + LOCK2(cs_main, pool.cs); + TestMemPoolEntryHelper entry; + + CMutableTransaction tx = CMutableTransaction(); + tx.vin.resize(1); + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout.resize(1); + tx.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL; + tx.vout[0].nValue = 10 * COIN; + + // Not in the mempool, so can't find it by txid or wtxid + BOOST_CHECK(!pool.get(tx.GetHash())); + BOOST_CHECK(!pool.get(CTransaction(tx).GetWitnessHash())); + + TryAddToMempool(pool, entry.Fee(1000LL).FromTx(tx)); + + // Lookup by Txid + BOOST_CHECK(pool.get(tx.GetHash())); + + // Lookup by Wtxid + BOOST_CHECK(pool.get(CTransaction(tx).GetWitnessHash())); +} + BOOST_AUTO_TEST_CASE(MempoolRemoveTest) { // Test CTxMemPool::remove functionality diff --git a/src/txmempool.cpp b/src/txmempool.cpp index a22cd2b199d..86cbc1adc43 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -627,6 +627,15 @@ CTransactionRef CTxMemPool::get(const Txid& hash) const return i->GetSharedTx(); } +CTransactionRef CTxMemPool::get(const Wtxid& hash) const +{ + LOCK(cs); + const auto& wtxid_map{mapTx.get()}; + const auto it{wtxid_map.find(hash)}; + if (it == wtxid_map.end()) return nullptr; + return it->GetSharedTx(); +} + void CTxMemPool::PrioritiseTransaction(const Txid& hash, const CAmount& nFeeDelta) { { diff --git a/src/txmempool.h b/src/txmempool.h index ae59057ca62..1a5405d5bf0 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -512,8 +512,22 @@ public: const CTxMemPoolEntry* GetEntry(const Txid& txid) const LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(cs); + /** + * Return a mempool transaction with a given hash. + * + * @param[in] hash the txid + * @returns the tx if found, otherwise nullptr + */ CTransactionRef get(const Txid& hash) const; + /** + * Return a mempool transaction with a given witness hash. + * + * @param[in] hash the wtxid + * @returns the tx if found, otherwise nullptr + */ + CTransactionRef get(const Wtxid& hash) const; + template TxMempoolInfo info(const T& id) const { From f16b3613cd06db056eba636106047756dcdc1e74 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 20 Feb 2026 10:55:43 +0100 Subject: [PATCH 2/5] ipc: Serialize null CTransactionRef as empty Data Co-authored-by: Russell Yanofsky --- src/ipc/capnp/common-types.h | 12 ++++++++++++ src/ipc/test/ipc_test.capnp | 1 + src/ipc/test/ipc_test.cpp | 8 ++++++++ src/ipc/test/ipc_test.h | 1 + src/node/interfaces.cpp | 1 + test/functional/interface_ipc_mining.py | 13 ++++++++++++- 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h index 309799b8aff..9b6fa464de9 100644 --- a/src/ipc/capnp/common-types.h +++ b/src/ipc/capnp/common-types.h @@ -127,6 +127,18 @@ decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& i }); } +//! Interpret empty Data fields as null CTransactionRef values. This is safe to +//! do because no CTransaction is ever serialized as empty Data, and it is +//! convenient because this allows std::vector to be passed as +//! List(Data) even if the vector contains null values, and even though Cap'n +//! Proto does not (currently) allow distinguishing between null and empty Data +//! values in a List. Interpreting empty Data values as null CTransactionRef +//! values works well for this purpose. +template +bool CustomHasField(TypeList, InvokeContext& invoke_context, const Input& input) +{ + return input.get().size() > 0; +} } // namespace mp #endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H diff --git a/src/ipc/test/ipc_test.capnp b/src/ipc/test/ipc_test.capnp index adb92825116..4aa196b6f52 100644 --- a/src/ipc/test/ipc_test.capnp +++ b/src/ipc/test/ipc_test.capnp @@ -18,6 +18,7 @@ interface FooInterface $Proxy.wrap("FooImplementation") { passOutPoint @1 (arg :Data) -> (result :Data); passUniValue @2 (arg :Text) -> (result :Text); passTransaction @3 (arg :Data) -> (result :Data); + passTransactions @6 (arg :List(Data)) -> (result :List(Data)); passVectorChar @4 (arg :Data) -> (result :Data); passScript @5 (arg :Data) -> (result :Data); } diff --git a/src/ipc/test/ipc_test.cpp b/src/ipc/test/ipc_test.cpp index 46366cef1f0..d5c689501da 100644 --- a/src/ipc/test/ipc_test.cpp +++ b/src/ipc/test/ipc_test.cpp @@ -103,6 +103,14 @@ void IpcPipeTest() CTransactionRef tx2{foo->passTransaction(tx1)}; BOOST_CHECK(*Assert(tx1) == *Assert(tx2)); + std::vector txs1; + txs1.push_back(tx1); + txs1.push_back(nullptr); + std::vector txs2(foo->passTransactions(txs1)); + BOOST_CHECK_EQUAL(txs2.size(), 2); + BOOST_CHECK(*Assert(txs1[0]) == *Assert(txs2[0])); + BOOST_CHECK(!txs2[1]); + std::vector vec1{'H', 'e', 'l', 'l', 'o'}; std::vector vec2{foo->passVectorChar(vec1)}; BOOST_CHECK_EQUAL(std::string_view(vec1.begin(), vec1.end()), std::string_view(vec2.begin(), vec2.end())); diff --git a/src/ipc/test/ipc_test.h b/src/ipc/test/ipc_test.h index 8ef3bc90fb3..392f2b48826 100644 --- a/src/ipc/test/ipc_test.h +++ b/src/ipc/test/ipc_test.h @@ -18,6 +18,7 @@ public: COutPoint passOutPoint(COutPoint o) { return o; } UniValue passUniValue(UniValue v) { return v; } CTransactionRef passTransaction(CTransactionRef t) { return t; } + std::vector passTransactions(std::vector t) { return t; } std::vector passVectorChar(std::vector v) { return v; } BlockValidationState passBlockState(BlockValidationState s) { return s; } CScript passScript(CScript s) { return s; } diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 2f68f414f0d..641252d2afa 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -919,6 +919,7 @@ public: bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) override { + if (!coinbase) return false; AddMerkleRootAndCoinbase(m_block_template->block, std::move(coinbase), version, timestamp, nonce); std::string reason; std::string debug; diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 4cd9c17c99f..b5a0328042c 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -511,9 +511,13 @@ class IPCMiningTest(BitcoinTestFramework): # lets node 2 accept/reject complete blocks independently. self.disconnect_nodes(1, 2) + self.log.debug("submitSolution should reject an empty coinbase") + submitted = (await template.submitSolution(ctx, 0, 0, 0, b"")).result + assert_equal(submitted, False) + self.log.debug("Submit solution that can't be deserialized") try: - await template.submitSolution(ctx, 0, 0, 0, b"") + await template.submitSolution(ctx, 0, 0, 0, b"\x00") raise AssertionError("submitSolution unexpectedly succeeded") except capnp.lib.capnp.KjException as e: assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") @@ -654,6 +658,13 @@ class IPCMiningTest(BitcoinTestFramework): raise AssertionError("submitBlock unexpectedly succeeded") except capnp.lib.capnp.KjException as e: assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") + + self.log.debug("Submit empty block data") + try: + await mining2.submitBlock(ctx2, b"") + raise AssertionError("submitBlock unexpectedly succeeded") + except capnp.lib.capnp.KjException as e: + assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") assert_equal(self.nodes[2].is_node_stopped(), False) asyncio.run(capnp.run(async_routine())) From 0d5e4d4712b222b9dfcad67e9817ece254689821 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 31 Mar 2026 17:53:14 +0200 Subject: [PATCH 3/5] test: restart node after IPC option override test Add cleanup so it doesn't need to be the last test. --- test/functional/interface_ipc_mining.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index b5a0328042c..4cbc6eb8c54 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -369,6 +369,9 @@ class IPCMiningTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) asyncio.run(capnp.run(async_routine_check_max_reserved_weight())) asyncio.run(capnp.run(async_routine_check_sigops_limit())) + self.restart_node(0) + self.connect_nodes(0, 1) + self.miniwallet.rescan_utxos() def run_waitnext_mining_policy_test(self): """Verify that waitNext() preserves the mining policy from -blockmintxfee From d282ae688325c3e39fc2fd5a40f4bfc09e2e87d6 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 5 Dec 2025 19:50:58 +0100 Subject: [PATCH 4/5] mining: add getTransactionsByTxID() IPC method --- src/interfaces/mining.h | 9 ++++++++ src/ipc/capnp/mining.capnp | 1 + src/node/interfaces.cpp | 13 +++++++++++ src/test/miner_tests.cpp | 10 +++++++++ test/functional/interface_ipc_mining.py | 25 ++++++++++++++++++++++ test/functional/test_framework/messages.py | 9 ++++++-- 6 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index ff4f87109f9..7f69c7580db 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -180,6 +180,15 @@ public: */ virtual bool submitBlock(const CBlock& block, std::string& reason, std::string& debug) = 0; + /** + * Fetch raw transactions from the mempool by txid. + * + * @param[in] txids transaction ids to look up + * @returns one entry per requested txid containing the + * transaction if found, otherwise nullptr + */ + virtual std::vector getTransactionsByTxID(const std::vector& txids) = 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 a6dd8d71f31..e1c9e116c26 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -26,6 +26,7 @@ interface Mining $Proxy.wrap("interfaces::Mining") { checkBlock @5 (context :Proxy.Context, block: Data, options: BlockCheckOptions) -> (reason: Text, debug: Text, result: Bool); 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)); } interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 641252d2afa..33473fbf4ed 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1035,6 +1035,19 @@ public: return accepted && new_block && reason.empty(); } + std::vector getTransactionsByTxID(const std::vector& txids) override + { + if (!m_node.mempool) return {}; + + std::vector results; + results.reserve(txids.size()); + LOCK(m_node.mempool->cs); + for (const auto& txid : txids) { + results.emplace_back(m_node.mempool->get(txid)); + } + 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 fd9559b543c..a366e5e494d 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -184,6 +184,16 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const const auto high_fee_tx{entry.Fee(50000).Time(Now()).SpendsCoinbase(false).FromTx(tx)}; TryAddToMempool(tx_mempool, high_fee_tx); + // Test getTransactionsByTxID() + const std::vector tx_id_list{ + hashParentTx, + Txid::FromUint256(uint256::ZERO) // non-existing tx + }; + auto raw_txs = mining->getTransactionsByTxID(tx_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 4cbc6eb8c54..a81e76da05b 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -672,6 +672,30 @@ class IPCMiningTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) + def run_transaction_lookup_test(self): + """Test getTransactionsByTxID().""" + self.log.info("Running transaction lookup test") + + async def async_routine(): + ctx, mining = await make_mining_ctx(self) + tx1 = self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]) + tx2 = self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0], utxo_to_spend=tx1["new_utxo"]) + + self.log.debug("getTransactionsByTxID() returns mempool txs and nulls") + raw_txs_txid = await mining.getTransactionsByTxID(ctx, [tx1["tx"].txid, tx2["tx"].txid, bytes(32)]) + assert_equal(len(raw_txs_txid.result), 3) + assert_equal(raw_txs_txid.result[0].hex(), tx1["hex"]) + assert_equal(raw_txs_txid.result[1].hex(), tx2["hex"]) + assert_equal(raw_txs_txid.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'') + + asyncio.run(capnp.run(async_routine())) + def run_low_height_test(self): """Test that IPC createNewBlock() works at low block heights on a clean chain, in particular with regard to bad-cb-length. @@ -727,6 +751,7 @@ class IPCMiningTest(BitcoinTestFramework): self.run_waitnext_mining_policy_test() self.run_block_max_weight_test() self.run_ipc_option_override_test() + self.run_transaction_lookup_test() # Needs to run last because it resets the chain. self.run_low_height_test() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index a0f2a174023..7614bd92c0b 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -697,15 +697,20 @@ class CTransaction: """Return wtxid (transaction hash with witness) as integer.""" return uint256_from_str(hash256(self.serialize_with_witness())) + @property + def txid(self): + """Return txid (transaction hash without witness) as little-endian bytes.""" + return hash256(self.serialize_without_witness()) + @property def txid_hex(self): """Return txid (transaction hash without witness) as hex string.""" - return hash256(self.serialize_without_witness())[::-1].hex() + return self.txid[::-1].hex() @property def txid_int(self): """Return txid (transaction hash without witness) as integer.""" - return uint256_from_str(hash256(self.serialize_without_witness())) + return uint256_from_str(self.txid) def is_valid(self): for tout in self.vout: From 9784818442f5f66fff7609025e7d530020d7d613 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 5 Dec 2025 19:10:25 +0100 Subject: [PATCH 5/5] 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):