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) 2014-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 mempool persistence.7By default, bitcoind will dump mempool on shutdown and8then reload it on startup. This can be overridden with9the -persistmempool=0 command line option.11Test is as follows:13- start node0, node1 and node2. node1 has -persistmempool=014- create 5 transactions on node2 to its own address. Note that these15are not sent to node0 or node1 addresses because we don't want16them to be saved in the wallet.17- check that node0 and node1 have 5 transactions in their mempools18- shutdown all nodes.19- startup node0. Verify that it still has 5 transactions20in its mempool. Shutdown node0. This tests that by default the21mempool is persistent.22- startup node1. Verify that its mempool is empty. Shutdown node1.23This tests that with -persistmempool=0, the mempool is not24dumped to disk when the node is shut down.25- Restart node0 with -persistmempool=0. Verify that its mempool is26empty. Shutdown node0. This tests that with -persistmempool=0,27the mempool is not loaded from disk on start up.28- Restart node0 with -persistmempool. Verify that it has 529transactions in its mempool. This tests that -persistmempool=030does not overwrite a previously valid mempool stored on disk.31- Remove node0 mempool.dat and verify savemempool RPC recreates it32and verify that node1 can load it and has 5 transactions in its33mempool.34- Verify that savemempool throws when the RPC is called if35node1 can't write to disk.37"""38from decimal import Decimal39import os40import time42from test_framework.p2p import P2PTxInvStore43from test_framework.test_framework import BitcoinTestFramework44from test_framework.util import (45assert_equal,46assert_greater_than_or_equal,47assert_raises_rpc_error,48)49from test_framework.wallet import MiniWallet, COIN52class MempoolPersistTest(BitcoinTestFramework):53def set_test_params(self):54self.num_nodes = 355self.extra_args = [[], ["-persistmempool=0"], []]56self.uses_wallet = None58def run_test(self):59self.mini_wallet = MiniWallet(self.nodes[2])60if self.is_wallet_compiled():61self.nodes[2].createwallet(62wallet_name="watch",63disable_private_keys=True,64load_on_startup=False,65)66wallet_watch = self.nodes[2].get_wallet_rpc("watch")67assert_equal([{'success': True}], wallet_watch.importdescriptors([{'desc': self.mini_wallet.get_descriptor(), 'timestamp': 0}]))69self.log.debug("Send 5 transactions from node2 (to its own address)")70tx_creation_time_lower = int(time.time())71for _ in range(5):72last_txid = self.mini_wallet.send_self_transfer(from_node=self.nodes[2])["txid"]73if self.is_wallet_compiled():74self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet75node2_balance = wallet_watch.getbalance()76self.sync_all()77tx_creation_time_higher = int(time.time())79self.log.debug("Verify that node0 and node1 have 5 transactions in their mempools")80assert_equal(len(self.nodes[0].getrawmempool()), 5)81assert_equal(len(self.nodes[1].getrawmempool()), 5)83total_fee_old = self.nodes[0].getmempoolinfo()['total_fee']85self.log.debug("Prioritize a transaction on node0")86fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']87assert_equal(fees['base'], fees['modified'])88self.nodes[0].prioritisetransaction(txid=last_txid, fee_delta=1000)89fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']90assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])92self.log.info('Check the total base fee is unchanged after prioritisetransaction')93assert_equal(total_fee_old, self.nodes[0].getmempoolinfo()['total_fee'])94assert_equal(total_fee_old, sum(v['fees']['base'] for k, v in self.nodes[0].getrawmempool(verbose=True).items()))96last_entry = self.nodes[0].getmempoolentry(txid=last_txid)97tx_creation_time = last_entry['time']98assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower)99assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time)101# disconnect nodes & make a txn that remains in the unbroadcast set.102self.disconnect_nodes(0, 1)103assert_equal(len(self.nodes[0].getpeerinfo()), 0)104assert_equal(len(self.nodes[0].p2ps), 0)105self.mini_wallet.send_self_transfer(from_node=self.nodes[0])107# Test persistence of prioritisation for transactions not in the mempool.108# Create a tx and prioritise but don't submit until after the restart.109tx_prioritised_not_submitted = self.mini_wallet.create_self_transfer()110self.nodes[0].prioritisetransaction(txid=tx_prioritised_not_submitted['txid'], fee_delta=9999)112self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")113self.stop_nodes()114# Give this node a head-start, so we can be "extra-sure" that it didn't load anything later115# Also don't store the mempool, to keep the datadir clean116self.start_node(1, extra_args=["-persistmempool=0"])117self.start_node(0)118self.start_node(2)119assert self.nodes[0].getmempoolinfo()["loaded"] # start_node is blocking on the mempool being loaded120assert self.nodes[2].getmempoolinfo()["loaded"]121assert_equal(len(self.nodes[0].getrawmempool()), 6)122assert_equal(len(self.nodes[2].getrawmempool()), 5)123# The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now:124assert_equal(len(self.nodes[1].getrawmempool()), 0)126self.log.debug('Verify prioritization is loaded correctly')127fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']128assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])130self.log.debug('Verify all fields are loaded correctly')131new_entry = self.nodes[0].getmempoolentry(txid=last_txid)132assert_equal({**last_entry, "clusterid": None}, {**new_entry, "clusterid": None})133self.nodes[0].sendrawtransaction(tx_prioritised_not_submitted['hex'])134entry_prioritised_before_restart = self.nodes[0].getmempoolentry(txid=tx_prioritised_not_submitted['txid'])135assert_equal(entry_prioritised_before_restart['fees']['base'] + Decimal('0.00009999'), entry_prioritised_before_restart['fees']['modified'])137# Verify accounting of mempool transactions after restart is correct138if self.is_wallet_compiled():139self.nodes[2].loadwallet("watch")140wallet_watch = self.nodes[2].get_wallet_rpc("watch")141self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet142assert_equal(node2_balance, wallet_watch.getbalance())144mempooldat0 = os.path.join(self.nodes[0].chain_path, 'mempool.dat')145mempooldat1 = os.path.join(self.nodes[1].chain_path, 'mempool.dat')147self.log.debug("Force -persistmempool=0 node1 to savemempool to disk via RPC")148assert not os.path.exists(mempooldat1)149result1 = self.nodes[1].savemempool()150assert os.path.isfile(mempooldat1)151assert_equal(result1['filename'], mempooldat1)152os.remove(mempooldat1)154self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.")155self.stop_nodes()156self.start_node(0, extra_args=["-persistmempool=0"])157assert self.nodes[0].getmempoolinfo()["loaded"]158assert_equal(len(self.nodes[0].getrawmempool()), 0)160self.log.debug("Import mempool at runtime to node0.")161assert_equal({}, self.nodes[0].importmempool(mempooldat0))162assert_equal(len(self.nodes[0].getrawmempool()), 7)163fees = self.nodes[0].getmempoolentry(txid=last_txid)["fees"]164assert_equal(fees["base"], fees["modified"])165assert_equal({}, self.nodes[0].importmempool(mempooldat0, {"apply_fee_delta_priority": True, "apply_unbroadcast_set": True}))166assert_equal(2, self.nodes[0].getmempoolinfo()["unbroadcastcount"])167fees = self.nodes[0].getmempoolentry(txid=last_txid)["fees"]168assert_equal(fees["base"] + Decimal("0.00001000"), fees["modified"])170self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.")171self.stop_nodes()172self.start_node(0)173assert self.nodes[0].getmempoolinfo()["loaded"]174assert_equal(len(self.nodes[0].getrawmempool()), 7)176self.log.debug("Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it")177os.remove(mempooldat0)178result0 = self.nodes[0].savemempool()179assert os.path.isfile(mempooldat0)180assert_equal(result0['filename'], mempooldat0)182self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 7 transactions")183os.rename(mempooldat0, mempooldat1)184self.stop_nodes()185self.start_node(1, extra_args=["-persistmempool"])186assert self.nodes[1].getmempoolinfo()["loaded"]187assert_equal(len(self.nodes[1].getrawmempool()), 7)189self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails")190# to test the exception we are creating a tmp folder called mempool.dat.new191# which is an implementation detail that could change and break this test192mempooldotnew1 = mempooldat1 + '.new'193os.mkdir(mempooldotnew1)194assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool)195os.rmdir(mempooldotnew1)197self.test_importmempool_union()198self.test_persist_unbroadcast()200def test_persist_unbroadcast(self):201node0 = self.nodes[0]202self.start_node(0)203self.start_node(2)205# clear out mempool206self.generate(node0, 1, sync_fun=self.no_op)208# ensure node0 doesn't have any connections209# make a transaction that will remain in the unbroadcast set210assert_equal(len(node0.getpeerinfo()), 0)211assert_equal(len(node0.p2ps), 0)212self.mini_wallet.send_self_transfer(from_node=node0)214# shutdown, then startup with wallet disabled215self.restart_node(0, extra_args=["-disablewallet"])217# check that txn gets broadcast due to unbroadcast logic218conn = node0.add_p2p_connection(P2PTxInvStore())219node0.mockscheduler(16 * 60) # 15 min + 1 for buffer220self.wait_until(lambda: len(conn.get_invs()) == 1)222def test_importmempool_union(self):223self.log.debug("Submit different transactions to node0 and node1's mempools")224self.start_node(0)225self.start_node(2)226tx_node0 = self.mini_wallet.send_self_transfer(from_node=self.nodes[0])227tx_node1 = self.mini_wallet.send_self_transfer(from_node=self.nodes[1])228tx_node01 = self.mini_wallet.create_self_transfer()229tx_node01_secret = self.mini_wallet.create_self_transfer()230self.nodes[0].prioritisetransaction(tx_node01["txid"], 0, COIN)231self.nodes[0].prioritisetransaction(tx_node01_secret["txid"], 0, 2 * COIN)232self.nodes[1].prioritisetransaction(tx_node01_secret["txid"], 0, 3 * COIN)233self.nodes[0].sendrawtransaction(tx_node01["hex"])234self.nodes[1].sendrawtransaction(tx_node01["hex"])235assert tx_node0["txid"] in self.nodes[0].getrawmempool()236assert tx_node0["txid"] not in self.nodes[1].getrawmempool()237assert tx_node1["txid"] not in self.nodes[0].getrawmempool()238assert tx_node1["txid"] in self.nodes[1].getrawmempool()239assert tx_node01["txid"] in self.nodes[0].getrawmempool()240assert tx_node01["txid"] in self.nodes[1].getrawmempool()241assert tx_node01_secret["txid"] not in self.nodes[0].getrawmempool()242assert tx_node01_secret["txid"] not in self.nodes[1].getrawmempool()244self.log.debug("Check that importmempool can add txns without replacing the entire mempool")245mempooldat0 = str(self.nodes[0].chain_path / "mempool.dat")246result0 = self.nodes[0].savemempool()247assert_equal(mempooldat0, result0["filename"])248assert_equal({}, self.nodes[1].importmempool(mempooldat0, {"apply_fee_delta_priority": True}))249# All transactions should be in node1's mempool now.250assert tx_node0["txid"] in self.nodes[1].getrawmempool()
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.