1 #!/usr/bin/env python3
2 # Copyright (c) 2017-present 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 wallet resends transactions periodically."""
6 import time
7 8 from decimal import Decimal
9 10 from test_framework.blocktools import (
11 create_block,
12 )
13 from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS
14 from test_framework.p2p import P2PTxInvStore
15 from test_framework.test_framework import BitcoinTestFramework
16 from test_framework.util import (
17 assert_equal,
18 assert_raises_rpc_error,
19 get_fee,
20 try_rpc,
21 )
22 23 # 36 hours is the upper limit of the resend timer, see CWallet::SetNextResend()
24 RESEND_TIMER_LIMIT = 36 * 60 * 60
25 26 27 class ResendWalletTransactionsTest(BitcoinTestFramework):
28 def set_test_params(self):
29 self.num_nodes = 2
30 self.noban_tx_relay = True # Needed due to mocktime
31 32 def skip_test_if_missing_module(self):
33 self.skip_if_no_wallet()
34 35 def setup_network(self):
36 self.setup_nodes() # Don't connect nodes
37 38 def run_test(self):
39 node = self.nodes[0] # alias
40 41 peer_first = node.add_p2p_connection(P2PTxInvStore())
42 43 self.log.info("Create a new transaction and wait until it's broadcast")
44 parent_utxo, indep_utxo = node.listunspent()[:2]
45 addr = node.getnewaddress()
46 txid = node.send(outputs=[{addr: 1}], inputs=[parent_utxo])["txid"]
47 48 # Can take a few seconds due to transaction trickling
49 peer_first.wait_for_broadcast([txid])
50 51 # Add a second peer since txs aren't rebroadcast to the same peer (see m_tx_inventory_known_filter)
52 peer_second = node.add_p2p_connection(P2PTxInvStore())
53 54 self.log.info("Create a block")
55 # Create and submit a block without the transaction.
56 # Transactions are only rebroadcast if there has been a block at least five minutes
57 # after the last time we tried to broadcast. Use mocktime and give an extra minute to be sure.
58 block_time = int(time.time()) + 6 * 60
59 node.setmocktime(block_time)
60 block = create_block(int(node.getbestblockhash(), 16), height=node.getblockcount() + 1, ntime=block_time)
61 block.solve()
62 node.submitblock(block.serialize().hex())
63 64 # Set correct m_best_block_time, which is used in ResubmitWalletTransactions
65 node.syncwithvalidationinterfacequeue()
66 now = int(time.time())
67 68 # Transaction should not be rebroadcast within first 12 hours
69 # Leave 2 mins for buffer
70 twelve_hrs = 12 * 60 * 60
71 two_min = 2 * 60
72 node.setmocktime(now + twelve_hrs - two_min)
73 node.mockscheduler(60) # Tell scheduler to call MaybeResendWalletTxs now
74 assert_equal(int(txid, 16) in peer_second.get_invs(), False)
75 76 self.log.info("Bump time & check that transaction is rebroadcast")
77 # Transaction should be rebroadcast approximately 24 hours in the future,
78 # but can range from 12-36. So bump 36 hours to be sure.
79 with node.assert_debug_log(['resubmit 1 unconfirmed transactions']):
80 node.setmocktime(now + RESEND_TIMER_LIMIT)
81 # Tell scheduler to call MaybeResendWalletTxs now.
82 node.mockscheduler(60)
83 peer_second.wait_for_broadcast([txid])
84 85 self.log.info("Chain of unconfirmed not-in-mempool txs are rebroadcast")
86 # This tests that the node broadcasts the parent transaction before the child transaction.
87 # To test that scenario, we need a method to reliably get a child transaction placed
88 # in mapWallet positioned before the parent. We cannot predict the position in mapWallet,
89 # but we can observe it using listreceivedbyaddress and other related RPCs.
90 #
91 # So we will create the child transaction, use listreceivedbyaddress to see what the
92 # ordering of mapWallet is, if the child is not before the parent, we will create a new
93 # child (via bumpfee) and remove the old child (via removeprunedfunds) until we get the
94 # ordering of child before parent.
95 child_inputs = [{"txid": txid, "vout": 0}]
96 child_txid = node.sendall(recipients=[addr], inputs=child_inputs)["txid"]
97 # Get the child tx's info for manual bumping
98 child_tx_info = node.gettransaction(txid=child_txid, verbose=True)
99 child_output_value = child_tx_info["decoded"]["vout"][0]["value"]
100 # Include an additional 1 vbyte buffer to handle when we have a smaller signature
101 additional_child_fee = get_fee(child_tx_info["decoded"]["vsize"] + 1, Decimal(0.00001100))
102 while True:
103 txids = node.listreceivedbyaddress(minconf=0, address_filter=addr)[0]["txids"]
104 if txids == [child_txid, txid]:
105 break
106 # Manually bump the tx
107 # The inputs and the output address stay the same, just changing the amount for the new fee
108 child_output_value -= additional_child_fee
109 bumped_raw = node.createrawtransaction(inputs=child_inputs, outputs=[{addr: child_output_value}])
110 bumped = node.signrawtransactionwithwallet(bumped_raw)
111 bumped_txid = node.decoderawtransaction(bumped["hex"])["txid"]
112 # Sometimes we will get a signature that is a little bit shorter than we expect which causes the
113 # feerate to be a bit higher, then the followup to be a bit lower. This results in a replacement
114 # that can't be broadcast. We can just skip that and keep grinding.
115 if try_rpc(-26, "insufficient fee, rejecting replacement", node.sendrawtransaction, bumped["hex"]):
116 continue
117 # The scheduler queue creates a copy of the added tx after
118 # send/bumpfee and re-adds it to the wallet (undoing the next
119 # removeprunedfunds). So empty the scheduler queue:
120 node.syncwithvalidationinterfacequeue()
121 node.removeprunedfunds(child_txid)
122 child_txid = bumped_txid
123 entry_time = node.getmempoolentry(child_txid)["time"]
124 125 # tx must be at least 5 minutes older than the last block to be rebroadcast
126 block_time = entry_time + 6 * 60
127 node.setmocktime(block_time)
128 block = create_block(int(node.getbestblockhash(), 16), height=node.getblockcount() + 1, ntime=block_time)
129 block.solve()
130 node.submitblock(block.serialize().hex())
131 # Set correct m_best_block_time, which is used in ResubmitWalletTransactions
132 node.syncwithvalidationinterfacequeue()
133 134 evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5
135 # Flush out currently scheduled resubmit attempt now so that there can't be one right between eviction and check.
136 with node.assert_debug_log(['resubmit 2 unconfirmed transactions'], timeout=2):
137 node.setmocktime(evict_time)
138 node.mockscheduler(60)
139 140 # Evict these txs from the mempool
141 indep_send = node.send(outputs=[{node.getnewaddress(): 1}], inputs=[indep_utxo])
142 node.getmempoolentry(indep_send["txid"])
143 assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid)
144 assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid)
145 146 # Rebroadcast and check that parent and child are both in the mempool
147 with node.assert_debug_log(['resubmit 2 unconfirmed transactions'], timeout=2):
148 node.setmocktime(evict_time + RESEND_TIMER_LIMIT)
149 node.mockscheduler(60)
150 node.getmempoolentry(txid)
151 node.getmempoolentry(child_txid)
152 153 self.log.info("Test rebroadcast of transactions received by others")
154 # clear mempool
155 self.generate(node, 1, sync_fun=self.no_op)
156 # Sync node1's mocktime to node0's before connecting so it accepts node0's blocks
157 node1 = self.nodes[1]
158 node1.setmocktime(evict_time + RESEND_TIMER_LIMIT)
159 self.connect_nodes(1, 0)
160 self.sync_all()
161 162 self.log.info("node0 sends a tx to node1 and disconnects")
163 recv_addr = node1.getnewaddress()
164 recv_txid = node.sendtoaddress(recv_addr, 1)
165 self.sync_mempools()
166 node1.syncwithvalidationinterfacequeue()
167 168 wallet_tx = node1.gettransaction(recv_txid)
169 assert_equal(wallet_tx["confirmations"], 0)
170 recv_wtxid = node1.getmempoolentry(recv_txid)["wtxid"]
171 self.disconnect_nodes(0, 1)
172 173 self.log.info("Create a block without the transaction")
174 node1.bumpmocktime(6 * 60)
175 block = create_block(int(node1.getbestblockhash(), 16), height=node1.getblockcount() + 1, ntime=node1.mocktime)
176 block.solve()
177 node1.submitblock(block.serialize().hex())
178 node1.syncwithvalidationinterfacequeue()
179 180 self.log.info("Connect p2p who hasn't seen the tx")
181 peer = node1.add_p2p_connection(P2PTxInvStore())
182 183 self.log.info("Check that rebroadcast happens after 36 hours")
184 with node1.assert_debug_log(['resubmit 1 unconfirmed transactions']):
185 node1.bumpmocktime(RESEND_TIMER_LIMIT)
186 node1.mockscheduler(60)
187 peer.wait_for_broadcast([recv_wtxid])
188 189 190 if __name__ == '__main__':
191 ResendWalletTransactionsTest(__file__).main()
192