mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-12 05:32:22 +02:00
Merge bitcoin/bitcoin#34020: mining: add getTransactions(ByWitnessID) IPC methods
9784818442mining: add getTransactionsByWitnessID() IPC method (Sjors Provoost)d282ae6883mining: add getTransactionsByTxID() IPC method (Sjors Provoost)0d5e4d4712test: restart node after IPC option override test (Sjors Provoost)f16b3613cdipc: Serialize null CTransactionRef as empty Data (Sjors Provoost)0f466e1094mempool: add lookup by witness hash (Sjors Provoost) Pull request description: For Stratum v2 custom job declaration to be bandwidth efficient, the pool can request[^0] only the transactions that it doesn't know about. The spec doesn't specify how this is achieved, but one method is to call the `getrawtransaction` RPC on each transaction id listed in [DeclareMiningJob](https://stratumprotocol.org/specification/06-Job-Declaration-Protocol?query=DeclareMiningJob#644-declareminingjob-client-server) (or a subset if the pool software maintains a cache). Using RPC is inefficient, made worse by the need to make multiple calls. It also doesn't support queuing by witness id (yet, see #34013). This PR introduces two new IPC methods: - `getTransactionsById()`: takes a list of `Txid`'s - `getTransactionsByWitnessID()`: : takes a list of `Wtxid`'s Both return a list of serialised transactions. An empty element is returned for transactions that were not found. Unlike the RPC counterpart, the IPC methods do not take advantage of `-txindex`. This could be done in a followup. For `Wtxid` that would involve adding a `-witnesstxindex`. I thought about having a single (or overloaded) `getTransactions()` that works with both `Txid` and `Wtxid`, but I prefer that clients are intentional about which one they want. A unit and functional test cover the new functionality. Sv2 probably only needs `getTransactionsByWitnessID()`, but it's easy enough to just add both. To rest with Rust use: - https://github.com/2140-dev/bitcoin-capnp-types/pull/11 [^0]: there's two reasons the pool requests these transactions: to approve the template and to broadcast the block if a solution is found (the miner will also broadcast via their template provider). See also https://github.com/stratum-mining/sv2-spec/issues/170 ACKs for top commit: achow101: ACK9784818442sedited: Re-ACK9784818442ViniciusCestarii: Re-ACK9784818442ismaelsadeeq: Code review ACK9784818442Tree-SHA512: 3c6ceb572ab7d8bd090a8f31b5e331304a7a19a3d1f1551c9c2e1ee41339d76f96ca6c41bd634c87fca0a969e7d9bfa6a16c26fb06c0dd2315f6ca1c76a16a31
This commit is contained in:
@@ -368,6 +368,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
|
||||
@@ -510,9 +513,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:")
|
||||
@@ -653,10 +660,50 @@ 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()))
|
||||
|
||||
def run_transaction_lookup_test(self):
|
||||
"""Test getTransactionsByTxID() and getTransactionsByWitnessID()."""
|
||||
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("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()))
|
||||
|
||||
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.
|
||||
@@ -714,6 +761,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()
|
||||
|
||||
@@ -687,25 +687,35 @@ 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):
|
||||
"""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:
|
||||
|
||||
Reference in New Issue
Block a user