Merge bitcoin/bitcoin#30793: rpc: add getorphantxs

98c1536852 test: add getorphantxs tests (tdb3)
93f48fceb7 test: add tx_in_orphanage() (tdb3)
34a9c10e8c rpc: add getorphantxs (tdb3)
f511ff3654 refactor: move verbosity parsing to rpc/util (tdb3)
532491faf1 net: add GetOrphanTransactions() to PeerManager (tdb3)
91b65adff2 refactor: add OrphanTxBase for external use (tdb3)

Pull request description:

  This PR adds a new hidden rpc, `getorphantxs`, that provides the caller with a list of orphan transactions.  This rpc may be helpful when checking orphan behavior/scenarios (e.g. in tests like `p2p_orphan_handling`) or providing additional data for statistics/visualization.

  ```
  getorphantxs ( verbosity )

  Shows transactions in the tx orphanage.

  EXPERIMENTAL warning: this call may be changed in future releases.

  Arguments:
  1. verbosity    (numeric, optional, default=0) 0 for an array of txids (may contain duplicates), 1 for an array of objects with tx details, and 2 for details from (1) and tx hex

  Result (for verbose = 0):
  [           (json array)
    "hex",    (string) The transaction hash in hex
    ...
  ]

  Result (for verbose = 1):
  [                          (json array)
    {                        (json object)
      "txid" : "hex",        (string) The transaction hash in hex
      "wtxid" : "hex",       (string) The transaction witness hash in hex
      "bytes" : n,           (numeric) The serialized transaction size in bytes
      "vsize" : n,           (numeric) The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.
      "weight" : n,          (numeric) The transaction weight as defined in BIP 141.
      "expiration" : xxx,    (numeric) The orphan expiration time expressed in UNIX epoch time
      "from" : [             (json array)
        n,                   (numeric) Peer ID
        ...
      ]
    },
    ...
  ]

  Result (for verbose = 2):
  [                          (json array)
    {                        (json object)
      "txid" : "hex",        (string) The transaction hash in hex
      "wtxid" : "hex",       (string) The transaction witness hash in hex
      "bytes" : n,           (numeric) The serialized transaction size in bytes
      "vsize" : n,           (numeric) The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.
      "weight" : n,          (numeric) The transaction weight as defined in BIP 141.
      "expiration" : xxx,    (numeric) The orphan expiration time expressed in UNIX epoch time
      "from" : [             (json array)
        n,                   (numeric) Peer ID
        ...
      ],
      "hex" : "hex"          (string) The serialized, hex-encoded transaction data
    },
    ...
  ]

  Examples:
  > bitcoin-cli getorphantxs 2
  > curl --user myusername --data-binary '{"jsonrpc": "2.0", "id": "curltest", "method": "getorphantxs", "params": [2]}' -H 'content-type: application/json' http://127.0.0.1:8332/
  ```
  ```
  $ build/src/bitcoin-cli getorphantxs 2
  [
    {
      "txid": "50128aac5deab548228d74d846675ad4def91cd92453d81a2daa778df12a63f2",
      "wtxid": "bb61659336f59fcf23acb47c05dc4bbea63ab533a98c412f3a12cb813308d52c",
      "bytes": 133,
      "vsize": 104,
      "weight": 415,
      "expiration": 1725663854,
      "from": [
        1
      ],
      "hex": "020000000001010b992959eaa2018bbf31a4a3f9aa30896a8144dbd5cfaf263bf07c0845a3a6620000000000000000000140fe042a010000002251202913b252fe537830f843bfdc5fa7d20ba48639a87c86ff837b92d083c55ad7c102015121c0000000000000000000000000000000000000000000000000000000000000000100000000"
    },
    {
      "txid": "330bb7f701604a40ade20aa129e9a3eb8a7bf024e599084ca1026d3222b9f8a1",
      "wtxid": "b7651f7d4c1a40c4d01f6a1e43a121967091fa0f56bb460146c1c5c068e824f6",
      "bytes": 133,
      "vsize": 104,
      "weight": 415,
      "expiration": 1725663854,
      "from": [
        2
      ],
      "hex": "020000000001013600adfe41e0ebd2454838963d270916d2b47239c9eebb93a992b720d3589a080000000000000000000140fe042a010000002251202913b252fe537830f843bfdc5fa7d20ba48639a87c86ff837b92d083c55ad7c102015121c0000000000000000000000000000000000000000000000000000000000000000100000000"
    }
  ]
  ```

ACKs for top commit:
  glozow:
    reACK 98c1536852
  hodlinator:
    re-ACK 98c1536852
  danielabrozzoni:
    ACK 98c1536852
  pablomartin4btc:
    tACK 98c1536852
  itornaza:
    reACK 98c1536852

Tree-SHA512: 66075f9faa83748350b87397302100d08af92cbef5fadb27f2b4903f028c08020bf34a23e17262b41abb3f379ca9f46cf6cd5459b8681f2b83bffbbaf3c03ff9
This commit is contained in:
glozow
2024-10-05 11:19:09 -04:00
14 changed files with 294 additions and 20 deletions

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the getorphantxs RPC."""
from test_framework.mempool_util import tx_in_orphanage
from test_framework.messages import msg_tx
from test_framework.p2p import P2PInterface
from test_framework.util import assert_equal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.wallet import MiniWallet
class GetOrphanTxsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
def run_test(self):
self.wallet = MiniWallet(self.nodes[0])
self.test_orphan_activity()
self.test_orphan_details()
def test_orphan_activity(self):
self.log.info("Check that orphaned transactions are returned with getorphantxs")
node = self.nodes[0]
self.log.info("Create two 1P1C packages, but only broadcast the children")
tx_parent_1 = self.wallet.create_self_transfer()
tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"])
tx_parent_2 = self.wallet.create_self_transfer()
tx_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_2["new_utxo"])
peer = node.add_p2p_connection(P2PInterface())
peer.send_and_ping(msg_tx(tx_child_1["tx"]))
peer.send_and_ping(msg_tx(tx_child_2["tx"]))
self.log.info("Check that neither parent is in the mempool")
assert_equal(node.getmempoolinfo()["size"], 0)
self.log.info("Check that both children are in the orphanage")
orphanage = node.getorphantxs(verbosity=0)
self.log.info("Check the size of the orphanage")
assert_equal(len(orphanage), 2)
self.log.info("Check that negative verbosity is treated as 0")
assert_equal(orphanage, node.getorphantxs(verbosity=-1))
assert tx_in_orphanage(node, tx_child_1["tx"])
assert tx_in_orphanage(node, tx_child_2["tx"])
self.log.info("Broadcast parent 1")
peer.send_and_ping(msg_tx(tx_parent_1["tx"]))
self.log.info("Check that parent 1 and child 1 are in the mempool")
raw_mempool = node.getrawmempool()
assert_equal(len(raw_mempool), 2)
assert tx_parent_1["txid"] in raw_mempool
assert tx_child_1["txid"] in raw_mempool
self.log.info("Check that orphanage only contains child 2")
orphanage = node.getorphantxs()
assert_equal(len(orphanage), 1)
assert tx_in_orphanage(node, tx_child_2["tx"])
peer.send_and_ping(msg_tx(tx_parent_2["tx"]))
self.log.info("Check that all parents and children are now in the mempool")
raw_mempool = node.getrawmempool()
assert_equal(len(raw_mempool), 4)
assert tx_parent_1["txid"] in raw_mempool
assert tx_child_1["txid"] in raw_mempool
assert tx_parent_2["txid"] in raw_mempool
assert tx_child_2["txid"] in raw_mempool
self.log.info("Check that the orphanage is empty")
assert_equal(len(node.getorphantxs()), 0)
self.log.info("Confirm the transactions (clears mempool)")
self.generate(node, 1)
assert_equal(node.getmempoolinfo()["size"], 0)
def test_orphan_details(self):
self.log.info("Check the transaction details returned from getorphantxs")
node = self.nodes[0]
self.log.info("Create two orphans, from different peers")
tx_parent_1 = self.wallet.create_self_transfer()
tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"])
tx_parent_2 = self.wallet.create_self_transfer()
tx_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_2["new_utxo"])
peer_1 = node.add_p2p_connection(P2PInterface())
peer_2 = node.add_p2p_connection(P2PInterface())
peer_1.send_and_ping(msg_tx(tx_child_1["tx"]))
peer_2.send_and_ping(msg_tx(tx_child_2["tx"]))
orphanage = node.getorphantxs(verbosity=2)
assert tx_in_orphanage(node, tx_child_1["tx"])
assert tx_in_orphanage(node, tx_child_2["tx"])
self.log.info("Check that orphan 1 and 2 were from different peers")
assert orphanage[0]["from"][0] != orphanage[1]["from"][0]
self.log.info("Unorphan child 2")
peer_2.send_and_ping(msg_tx(tx_parent_2["tx"]))
assert not tx_in_orphanage(node, tx_child_2["tx"])
self.log.info("Checking orphan details")
orphanage = node.getorphantxs(verbosity=1)
assert_equal(len(node.getorphantxs()), 1)
orphan_1 = orphanage[0]
self.orphan_details_match(orphan_1, tx_child_1, verbosity=1)
self.log.info("Checking orphan details (verbosity 2)")
orphanage = node.getorphantxs(verbosity=2)
orphan_1 = orphanage[0]
self.orphan_details_match(orphan_1, tx_child_1, verbosity=2)
def orphan_details_match(self, orphan, tx, verbosity):
self.log.info("Check txid/wtxid of orphan")
assert_equal(orphan["txid"], tx["txid"])
assert_equal(orphan["wtxid"], tx["wtxid"])
self.log.info("Check the sizes of orphan")
assert_equal(orphan["bytes"], len(tx["tx"].serialize()))
assert_equal(orphan["vsize"], tx["tx"].get_vsize())
assert_equal(orphan["weight"], tx["tx"].get_weight())
if verbosity == 2:
self.log.info("Check the transaction hex of orphan")
assert_equal(orphan["hex"], tx["hex"])
if __name__ == '__main__':
GetOrphanTxsTest(__file__).main()

View File

@@ -8,6 +8,7 @@ from decimal import Decimal
from .blocktools import (
COINBASE_MATURITY,
)
from .messages import CTransaction
from .util import (
assert_equal,
assert_greater_than,
@@ -83,3 +84,8 @@ def fill_mempool(test_framework, node, *, tx_sync_fun=None):
test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee")
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
def tx_in_orphanage(node, tx: CTransaction) -> bool:
"""Returns true if the transaction is in the orphanage."""
found = [o for o in node.getorphantxs(verbosity=1) if o["txid"] == tx.rehash() and o["wtxid"] == tx.getwtxid()]
return len(found) == 1

View File

@@ -160,6 +160,7 @@ BASE_SCRIPTS = [
'wallet_importmulti.py --legacy-wallet',
'mempool_limit.py',
'rpc_txoutproof.py',
'rpc_getorphantxs.py',
'wallet_listreceivedby.py --legacy-wallet',
'wallet_listreceivedby.py --descriptors',
'wallet_abandonconflict.py --legacy-wallet',