/ test / functional / mempool_unbroadcast.py
mempool_unbroadcast.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2017-2022 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  """Test that the mempool ensures transaction delivery by periodically sending
  6  to peers until a GETDATA is received."""
  7  
  8  import time
  9  
 10  from test_framework.p2p import P2PTxInvStore
 11  from test_framework.test_framework import BitcoinTestFramework
 12  from test_framework.util import assert_equal
 13  from test_framework.wallet import MiniWallet
 14  
 15  MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds
 16  
 17  class MempoolUnbroadcastTest(BitcoinTestFramework):
 18      def add_options(self, parser):
 19          self.add_wallet_options(parser)
 20  
 21      def set_test_params(self):
 22          self.num_nodes = 2
 23  
 24      def run_test(self):
 25          self.wallet = MiniWallet(self.nodes[0])
 26          self.test_broadcast()
 27          self.test_txn_removal()
 28  
 29      def test_broadcast(self):
 30          self.log.info("Test that mempool reattempts delivery of locally submitted transaction")
 31          node = self.nodes[0]
 32  
 33          self.disconnect_nodes(0, 1)
 34  
 35          self.log.info("Generate transactions that only node 0 knows about")
 36  
 37          if self.is_wallet_compiled():
 38              self.import_deterministic_coinbase_privkeys()
 39              # generate a wallet txn
 40              addr = node.getnewaddress()
 41              wallet_tx_hsh = node.sendtoaddress(addr, 0.0001)
 42  
 43          # generate a txn using sendrawtransaction
 44          txFS = self.wallet.create_self_transfer()
 45          rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
 46  
 47          # check transactions are in unbroadcast using rpc
 48          mempoolinfo = self.nodes[0].getmempoolinfo()
 49          unbroadcast_count = 1
 50          if self.is_wallet_compiled():
 51              unbroadcast_count += 1
 52          assert_equal(mempoolinfo['unbroadcastcount'], unbroadcast_count)
 53          mempool = self.nodes[0].getrawmempool(True)
 54          for tx in mempool:
 55              assert_equal(mempool[tx]['unbroadcast'], True)
 56  
 57          # check that second node doesn't have these two txns
 58          mempool = self.nodes[1].getrawmempool()
 59          assert rpc_tx_hsh not in mempool
 60          if self.is_wallet_compiled():
 61              assert wallet_tx_hsh not in mempool
 62  
 63          # ensure that unbroadcast txs are persisted to mempool.dat
 64          self.restart_node(0)
 65  
 66          self.log.info("Reconnect nodes & check if they are sent to node 1")
 67          self.connect_nodes(0, 1)
 68  
 69          # fast forward into the future & ensure that the second node has the txns
 70          node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
 71          self.sync_mempools(timeout=30)
 72          mempool = self.nodes[1].getrawmempool()
 73          assert rpc_tx_hsh in mempool
 74          if self.is_wallet_compiled():
 75              assert wallet_tx_hsh in mempool
 76  
 77          # check that transactions are no longer in first node's unbroadcast set
 78          mempool = self.nodes[0].getrawmempool(True)
 79          for tx in mempool:
 80              assert_equal(mempool[tx]['unbroadcast'], False)
 81  
 82          self.log.info("Add another connection & ensure transactions aren't broadcast again")
 83  
 84          conn = node.add_p2p_connection(P2PTxInvStore())
 85          node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
 86          time.sleep(2) # allow sufficient time for possibility of broadcast
 87          assert_equal(len(conn.get_invs()), 0)
 88  
 89          self.disconnect_nodes(0, 1)
 90          node.disconnect_p2ps()
 91  
 92          self.log.info("Rebroadcast transaction and ensure it is not added to unbroadcast set when already in mempool")
 93          rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
 94          assert not node.getmempoolentry(rpc_tx_hsh)['unbroadcast']
 95  
 96      def test_txn_removal(self):
 97          self.log.info("Test that transactions removed from mempool are removed from unbroadcast set")
 98          node = self.nodes[0]
 99  
100          # since the node doesn't have any connections, it will not receive
101          # any GETDATAs & thus the transaction will remain in the unbroadcast set.
102          txhsh = self.wallet.send_self_transfer(from_node=node)["txid"]
103  
104          # check transaction was removed from unbroadcast set due to presence in
105          # a block
106          removal_reason = "Removed {} from set of unbroadcast txns before confirmation that txn was sent out".format(txhsh)
107          with node.assert_debug_log([removal_reason]):
108              self.generate(node, 1, sync_fun=self.no_op)
109  
110  
111  if __name__ == "__main__":
112      MempoolUnbroadcastTest().main()