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