mining: add getTransactionsByTxID() IPC method

This commit is contained in:
Sjors Provoost
2025-12-05 19:50:58 +01:00
parent 0d5e4d4712
commit d282ae6883
6 changed files with 65 additions and 2 deletions

View File

@@ -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<CTransactionRef> getTransactionsByTxID(const std::vector<Txid>& txids) = 0;
//! Get internal node context. Useful for RPC and testing,
//! but not accessible across processes.
virtual const node::NodeContext* context() { return nullptr; }

View File

@@ -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") {

View File

@@ -1035,6 +1035,19 @@ public:
return accepted && new_block && reason.empty();
}
std::vector<CTransactionRef> getTransactionsByTxID(const std::vector<Txid>& txids) override
{
if (!m_node.mempool) return {};
std::vector<CTransactionRef> 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); }

View File

@@ -184,6 +184,16 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
TryAddToMempool(tx_mempool, high_fee_tx);
// Test getTransactionsByTxID()
const std::vector<Txid> 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();

View File

@@ -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()

View File

@@ -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: