/ test / functional / rpc_orphans.py
rpc_orphans.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2014-2024 The Bitcoin Core developers
  3  # Distributed under the MIT software license, see the accompanying
  4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5  """Tests for orphan related RPCs."""
  6  
  7  import time
  8  
  9  from test_framework.mempool_util import (
 10      ORPHAN_TX_EXPIRE_TIME,
 11      tx_in_orphanage,
 12  )
 13  from test_framework.messages import (
 14      CInv,
 15      msg_inv,
 16      msg_tx,
 17      MSG_WTX,
 18  )
 19  from test_framework.p2p import P2PInterface
 20  from test_framework.util import (
 21      assert_equal,
 22      assert_not_equal,
 23      assert_raises_rpc_error,
 24  )
 25  from test_framework.test_framework import BitcoinTestFramework
 26  from test_framework.wallet import MiniWallet
 27  
 28  
 29  class OrphanRPCsTest(BitcoinTestFramework):
 30      def set_test_params(self):
 31          self.num_nodes = 1
 32  
 33      def run_test(self):
 34          self.wallet = MiniWallet(self.nodes[0])
 35          self.test_orphan_activity()
 36          self.test_orphan_details()
 37          self.test_misc()
 38  
 39      def test_orphan_activity(self):
 40          self.log.info("Check that orphaned transactions are returned with getorphantxs")
 41          node = self.nodes[0]
 42  
 43          self.log.info("Create two 1P1C packages, but only broadcast the children")
 44          tx_parent_1 = self.wallet.create_self_transfer()
 45          tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"])
 46          tx_parent_2 = self.wallet.create_self_transfer()
 47          tx_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_2["new_utxo"])
 48          peer = node.add_p2p_connection(P2PInterface())
 49          peer.send_and_ping(msg_tx(tx_child_1["tx"]))
 50          peer.send_and_ping(msg_tx(tx_child_2["tx"]))
 51  
 52          self.log.info("Check that neither parent is in the mempool")
 53          assert_equal(node.getmempoolinfo()["size"], 0)
 54  
 55          orphanage = node.getorphantxs(verbosity=0)
 56          self.log.info("Check the size of the orphanage")
 57          assert_equal(len(orphanage), 2)
 58          self.log.info("Check that undefined verbosity is disallowed")
 59          assert_raises_rpc_error(-8, "Invalid verbosity value -1", node.getorphantxs, verbosity=-1)
 60          assert_raises_rpc_error(-8, "Invalid verbosity value 3", node.getorphantxs, verbosity=3)
 61          self.log.info("Check that both children are in the orphanage")
 62          assert tx_in_orphanage(node, tx_child_1["tx"])
 63          assert tx_in_orphanage(node, tx_child_2["tx"])
 64  
 65          self.log.info("Broadcast parent 1")
 66          peer.send_and_ping(msg_tx(tx_parent_1["tx"]))
 67          self.log.info("Check that parent 1 and child 1 are in the mempool")
 68          raw_mempool = node.getrawmempool()
 69          assert_equal(len(raw_mempool), 2)
 70          assert tx_parent_1["txid"] in raw_mempool
 71          assert tx_child_1["txid"] in raw_mempool
 72  
 73          self.log.info("Check that orphanage only contains child 2")
 74          orphanage = node.getorphantxs()
 75          assert_equal(len(orphanage), 1)
 76          assert tx_in_orphanage(node, tx_child_2["tx"])
 77  
 78          peer.send_and_ping(msg_tx(tx_parent_2["tx"]))
 79          self.log.info("Check that all parents and children are now in the mempool")
 80          raw_mempool = node.getrawmempool()
 81          assert_equal(len(raw_mempool), 4)
 82          assert tx_parent_1["txid"] in raw_mempool
 83          assert tx_child_1["txid"] in raw_mempool
 84          assert tx_parent_2["txid"] in raw_mempool
 85          assert tx_child_2["txid"] in raw_mempool
 86          self.log.info("Check that the orphanage is empty")
 87          assert_equal(len(node.getorphantxs()), 0)
 88  
 89          self.log.info("Confirm the transactions (clears mempool)")
 90          self.generate(node, 1)
 91          assert_equal(node.getmempoolinfo()["size"], 0)
 92  
 93      def test_orphan_details(self):
 94          self.log.info("Check the transaction details returned from getorphantxs")
 95          node = self.nodes[0]
 96  
 97          self.log.info("Create two orphans, from different peers")
 98          tx_parent_1 = self.wallet.create_self_transfer()
 99          tx_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_1["new_utxo"])
100          tx_parent_2 = self.wallet.create_self_transfer()
101          tx_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_parent_2["new_utxo"])
102          peer_1 = node.add_p2p_connection(P2PInterface())
103          peer_2 = node.add_p2p_connection(P2PInterface())
104          entry_time = int(time.time())
105          node.setmocktime(entry_time)
106          peer_1.send_and_ping(msg_tx(tx_child_1["tx"]))
107          peer_2.send_and_ping(msg_tx(tx_child_2["tx"]))
108  
109          orphanage = node.getorphantxs(verbosity=2)
110          assert tx_in_orphanage(node, tx_child_1["tx"])
111          assert tx_in_orphanage(node, tx_child_2["tx"])
112  
113          self.log.info("Check that orphan 1 and 2 were from different peers")
114          assert_not_equal(orphanage[0]["from"][0], orphanage[1]["from"][0])
115          peer_ids = [orphanage[0]["from"][0], orphanage[1]["from"][0]]
116  
117          self.log.info("Unorphan child 2")
118          peer_2.send_and_ping(msg_tx(tx_parent_2["tx"]))
119          assert not tx_in_orphanage(node, tx_child_2["tx"])
120  
121          self.log.info("Check that additional announcers are reflected in RPC result")
122          peer_2.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=int(tx_child_1["wtxid"], 16))]))
123  
124          orphanage = node.getorphantxs(verbosity=2)
125          assert_equal(set(orphanage[0]["from"]), set(peer_ids))
126  
127          self.log.info("Checking orphan details")
128          assert_equal(len(node.getorphantxs()), 1)
129          orphan_1 = orphanage[0]
130          self.orphan_details_match(orphan_1, tx_child_1, verbosity=1)
131          self.log.info("Checking orphan entry/expiration times")
132          assert_equal(orphan_1["entry"], entry_time)
133          assert_equal(orphan_1["expiration"], entry_time + ORPHAN_TX_EXPIRE_TIME)
134  
135          self.log.info("Checking orphan details (verbosity 2)")
136          orphanage = node.getorphantxs(verbosity=2)
137          orphan_1 = orphanage[0]
138          self.orphan_details_match(orphan_1, tx_child_1, verbosity=2)
139  
140      def orphan_details_match(self, orphan, tx, verbosity):
141          self.log.info("Check txid/wtxid of orphan")
142          assert_equal(orphan["txid"], tx["txid"])
143          assert_equal(orphan["wtxid"], tx["wtxid"])
144  
145          self.log.info("Check the sizes of orphan")
146          assert_equal(orphan["bytes"], len(tx["tx"].serialize()))
147          assert_equal(orphan["vsize"], tx["tx"].get_vsize())
148          assert_equal(orphan["weight"], tx["tx"].get_weight())
149  
150          if verbosity == 2:
151              self.log.info("Check the transaction hex of orphan")
152              assert_equal(orphan["hex"], tx["hex"])
153  
154      def test_misc(self):
155          node = self.nodes[0]
156          assert_raises_rpc_error(-3, "Verbosity was boolean but only integer allowed", node.getorphantxs, verbosity=True)
157          assert_raises_rpc_error(-3, "Verbosity was boolean but only integer allowed", node.getorphantxs, verbosity=False)
158          help_output = node.help()
159          self.log.info("Check that getorphantxs is a hidden RPC")
160          assert "getorphantxs" not in help_output
161          assert "unknown command: getorphantxs" not in node.help("getorphantxs")
162  
163  
164  if __name__ == '__main__':
165      OrphanRPCsTest(__file__).main()