Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2017-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test that the wallet resends transactions periodically."""6import time8from test_framework.blocktools import (9create_block,10)11from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS12from test_framework.p2p import P2PTxInvStore13from test_framework.test_framework import BitcoinTestFramework14from test_framework.util import (15assert_equal,16assert_raises_rpc_error,17)19# 36 hours is the upper limit of the resend timer, see CWallet::SetNextResend()20RESEND_TIMER_LIMIT = 36 * 60 * 6023class ResendWalletTransactionsTest(BitcoinTestFramework):24def set_test_params(self):25self.num_nodes = 226self.noban_tx_relay = True # Needed due to mocktime28def skip_test_if_missing_module(self):29self.skip_if_no_wallet()31def setup_network(self):32self.setup_nodes() # Don't connect nodes34def run_test(self):35node = self.nodes[0] # alias37peer_first = node.add_p2p_connection(P2PTxInvStore())39self.log.info("Create a new transaction and wait until it's broadcast")40parent_utxo, indep_utxo = node.listunspent()[:2]41addr = node.getnewaddress()42txid = node.send(outputs=[{addr: 1}], inputs=[parent_utxo])["txid"]44# Can take a few seconds due to transaction trickling45peer_first.wait_for_broadcast([txid])47# Add a second peer since txs aren't rebroadcast to the same peer (see m_tx_inventory_known_filter)48peer_second = node.add_p2p_connection(P2PTxInvStore())50self.log.info("Create a block")51# Create and submit a block without the transaction.52# Transactions are only rebroadcast if there has been a block at least five minutes53# after the last time we tried to broadcast. Use mocktime and give an extra minute to be sure.54block_time = int(time.time()) + 6 * 6055node.setmocktime(block_time)56block = create_block(int(node.getbestblockhash(), 16), height=node.getblockcount() + 1, ntime=block_time)57block.solve()58node.submitblock(block.serialize().hex())60# Set correct m_best_block_time, which is used in ResubmitWalletTransactions61node.syncwithvalidationinterfacequeue()62now = int(time.time())64# Transaction should not be rebroadcast within first 12 hours65# Leave 2 mins for buffer66twelve_hrs = 12 * 60 * 6067two_min = 2 * 6068node.setmocktime(now + twelve_hrs - two_min)69node.mockscheduler(60) # Tell scheduler to call MaybeResendWalletTxs now70assert_equal(int(txid, 16) in peer_second.get_invs(), False)72self.log.info("Bump time & check that transaction is rebroadcast")73# Transaction should be rebroadcast approximately 24 hours in the future,74# but can range from 12-36. So bump 36 hours to be sure.75with node.assert_debug_log(['resubmit 1 unconfirmed transactions']):76node.setmocktime(now + RESEND_TIMER_LIMIT)77# Tell scheduler to call MaybeResendWalletTxs now.78node.mockscheduler(60)79peer_second.wait_for_broadcast([txid])81self.log.info("Chain of unconfirmed not-in-mempool txs are rebroadcast")82# We cannot predict the ordering in mapWallet of parent and child, so83# try a few times to get both.84evict_time = 085for _ in range(10):86child_inputs = [{"txid": txid, "vout": 0}]87child_txid = node.sendall(recipients=[addr], inputs=child_inputs)["txid"]88# Get the child tx's info for manual bumping89entry_time = node.getmempoolentry(child_txid)["time"]91# tx must be at least 5 minutes older than the last block to be rebroadcast92block_time = entry_time + 5 * 60 + 193node.setmocktime(block_time)94block = create_block(int(node.getbestblockhash(), 16), height=node.getblockcount() + 1, ntime=block_time)95block.solve()96node.submitblock(block.serialize().hex())97# Set correct m_best_block_time, which is used in ResubmitWalletTransactions98node.syncwithvalidationinterfacequeue()100evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5101# Flush out currently scheduled resubmit attempt now so that there can't be one right between eviction and check.102with node.assert_debug_log(['resubmit 2 unconfirmed transactions'], timeout=2):103node.setmocktime(evict_time)104node.mockscheduler(60)106# Evict these txs from the mempool107indep_send = node.send(outputs=[{node.getnewaddress(): 1}], inputs=[indep_utxo])108node.getmempoolentry(indep_send["txid"])109assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid)110assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid)112# Rebroadcast and check that parent and child are both in the mempool113with node.assert_debug_log(['resubmit 2 unconfirmed transactions'], timeout=2):114node.setmocktime(evict_time + RESEND_TIMER_LIMIT)115node.mockscheduler(60)116node.getmempoolentry(txid)117node.getmempoolentry(child_txid)119# clear mempool120self.generate(node, 1, sync_fun=self.no_op)121parent_utxo, indep_utxo = node.listunspent()[:2]122txid = node.send(outputs=[{addr: 1}], inputs=[parent_utxo])["txid"]124self.log.info("Test rebroadcast of transactions received by others")125# clear mempool126self.generate(node, 1, sync_fun=self.no_op)127# Sync node1's mocktime to node0's before connecting so it accepts node0's blocks128node1 = self.nodes[1]129node1.setmocktime(evict_time + RESEND_TIMER_LIMIT)130self.connect_nodes(1, 0)131self.sync_all()133self.log.info("node0 sends a tx to node1 and disconnects")134recv_addr = node1.getnewaddress()135recv_txid = node.sendtoaddress(recv_addr, 1)136self.sync_mempools()137node1.syncwithvalidationinterfacequeue()139wallet_tx = node1.gettransaction(recv_txid)140assert_equal(wallet_tx["confirmations"], 0)141recv_wtxid = node1.getmempoolentry(recv_txid)["wtxid"]142self.disconnect_nodes(0, 1)144self.log.info("Create a block without the transaction")145node1.bumpmocktime(6 * 60)146block = create_block(int(node1.getbestblockhash(), 16), height=node1.getblockcount() + 1, ntime=node1.mocktime)147block.solve()148node1.submitblock(block.serialize().hex())149node1.syncwithvalidationinterfacequeue()151self.log.info("Connect p2p who hasn't seen the tx")152peer = node1.add_p2p_connection(P2PTxInvStore())154self.log.info("Check that rebroadcast happens after 36 hours")155with node1.assert_debug_log(['resubmit 1 unconfirmed transactions']):156node1.bumpmocktime(RESEND_TIMER_LIMIT)157node1.mockscheduler(60)158peer.wait_for_broadcast([recv_wtxid])161if __name__ == '__main__':162ResendWalletTransactionsTest(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.