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 the wallet backup features.7Test case is:84 nodes. 1 2 and 3 send transactions between each other,9fourth node is a miner.101 2 3 each mine a block to start, then11Miner creates 100 blocks so 1 2 3 each have 50 mature12coins to spend.13Then 5 iterations of 1/2/3 sending coins amongst14themselves to get transactions in the wallets,15and the miner mining one block.17Wallets are backed up using dumpwallet/backupwallet.18Then 5 more iterations of transactions and mining a block.20Miner then generates 101 more blocks, so any21transaction fees paid mature.23Sanity check:24Sum(1,2,3,4 balances) == 114*50261/2/3 are shutdown, and their wallets erased.27Then restore using wallet.dat backup. And28confirm 1/2/3/4 balances are same as before.30Shutdown again, restore using importwallet,31and confirm again balances are correct.32"""33from decimal import Decimal34import os35from random import randint37from test_framework.blocktools import COINBASE_MATURITY38from test_framework.test_framework import BitcoinTestFramework39from test_framework.util import (40assert_equal,41assert_raises_rpc_error,42sha256sum_file,43)46class WalletBackupTest(BitcoinTestFramework):47def set_test_params(self):48self.num_nodes = 449self.setup_clean_chain = True50# whitelist peers to speed up tx relay / mempool sync51self.noban_tx_relay = True52# nodes 1, 2, 3 are spenders, let's give them a keypool=10053self.extra_args = [54["-keypool=100"],55["-keypool=100"],56["-keypool=100"],57[],58]59self.rpc_timeout = 12061def skip_test_if_missing_module(self):62self.skip_if_no_wallet()64def setup_network(self):65self.setup_nodes()66self.connect_nodes(0, 3)67self.connect_nodes(1, 3)68self.connect_nodes(2, 3)69self.connect_nodes(2, 0)70self.sync_all()72def one_send(self, from_node, to_address):73if (randint(1,2) == 1):74amount = Decimal(randint(1,10)) / Decimal(10)75self.nodes[from_node].sendtoaddress(to_address, amount)77def do_one_round(self):78a0 = self.nodes[0].getnewaddress()79a1 = self.nodes[1].getnewaddress()80a2 = self.nodes[2].getnewaddress()82self.one_send(0, a1)83self.one_send(0, a2)84self.one_send(1, a0)85self.one_send(1, a2)86self.one_send(2, a0)87self.one_send(2, a1)89# Have the miner (node3) mine a block.90# Must sync mempools before mining.91self.sync_mempools()92self.generate(self.nodes[3], 1)94def restore_invalid_wallet(self):95node = self.nodes[3]96invalid_wallet_file = self.nodes[0].datadir_path / 'invalid_wallet_file.bak'97open(invalid_wallet_file, "a").write("invalid_wallet_content")98wallet_name = "res0"99not_created_wallet_file = node.wallets_path / wallet_name100error_message = "Wallet file verification failed. Failed to load database path '{}'. Data is not in recognized format.".format(not_created_wallet_file)101assert_raises_rpc_error(-18, error_message, node.restorewallet, wallet_name, invalid_wallet_file)102assert not not_created_wallet_file.exists()104def restore_nonexistent_wallet(self):105node = self.nodes[3]106nonexistent_wallet_file = self.nodes[0].datadir_path / 'nonexistent_wallet.bak'107wallet_name = "res0"108assert_raises_rpc_error(-8, "Backup file does not exist", node.restorewallet, wallet_name, nonexistent_wallet_file)109not_created_wallet_file = node.wallets_path / wallet_name110assert not not_created_wallet_file.exists()112def restore_wallet_existent_name(self):113node = self.nodes[3]114backup_file = self.nodes[0].datadir_path / 'wallet.bak'115wallet_name = "res0"116wallet_file = node.wallets_path / wallet_name117error_message = "Failed to restore wallet. Database file exists in '{}'.".format(wallet_file / "wallet.dat")118assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)119assert wallet_file.exists()121def test_restore_existent_dir(self):122self.log.info("Test restore on an existent empty directory")123node = self.nodes[3]124backup_file = self.nodes[0].datadir_path / 'wallet.bak'125wallet_name = "restored_wallet"126wallet_dir = node.wallets_path / wallet_name127os.mkdir(wallet_dir)128res = node.restorewallet(wallet_name, backup_file)129assert_equal(res['name'], wallet_name)130node.unloadwallet(wallet_name)132self.log.info("Test restore succeeds when the target directory contains non-wallet files")133wallet_file = node.wallets_path / wallet_name / "wallet.dat"134os.remove(wallet_file)135extra_file = node.wallets_path / wallet_name / "not_a_wallet.txt"136extra_file.touch()137res = node.restorewallet(wallet_name, backup_file)138assert_equal(res['name'], wallet_name)139assert extra_file.exists() # extra file was not removed by mistake140node.unloadwallet(wallet_name)142self.log.info("Test restore failure due to existing db file in the destination directory")143original_shasum = sha256sum_file(wallet_file)144error_message = "Failed to restore wallet. Database file exists in '{}'.".format(wallet_dir / "wallet.dat")145assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)146# Ensure the wallet file remains untouched147assert wallet_dir.exists()148assert_equal(original_shasum, sha256sum_file(wallet_file))150self.log.info("Test restore succeeds when the .dat file in the destination has a different name")151second_wallet = wallet_dir / "hidden_storage.dat"152os.rename(wallet_dir / "wallet.dat", second_wallet)153original_shasum = sha256sum_file(second_wallet)154res = node.restorewallet(wallet_name, backup_file)155assert_equal(res['name'], wallet_name)156assert (wallet_dir / "hidden_storage.dat").exists()157assert_equal(original_shasum, sha256sum_file(second_wallet))158node.unloadwallet(wallet_name)160# Clean for follow-up tests161os.remove(wallet_file)163def test_restore_into_unnamed_wallet(self):164self.log.info("Test restore into a default unnamed wallet")165# This is also useful to test the migration recovery after failure logic166node = self.nodes[3]167backup_file = self.nodes[0].datadir_path / 'wallet.bak'168assert_raises_rpc_error(-8, "Wallet name cannot be empty", node.restorewallet, "", backup_file)169assert not (node.wallets_path / "wallet.dat").exists()171def test_pruned_wallet_backup(self):172self.log.info("Test loading backup on a pruned node when the backup was created close to the prune height of the restoring node")173node = self.nodes[3]174self.restart_node(3, ["-prune=1", "-fastprune=1"])175# Ensure the chain tip is at height 214, because this test assumes it is.176assert_equal(node.getchaintips()[0]["height"], 214)177# We need a few more blocks so we can actually get above an realistic178# minimal prune height179self.generate(node, 50, sync_fun=self.no_op)180# Backup created at block height 264181node.backupwallet(node.datadir_path / 'wallet_pruned.bak')182# Generate more blocks so we can actually prune the older blocks183self.generate(node, 300, sync_fun=self.no_op)184# This gives us an actual prune height roughly in the range of 220 - 240185node.pruneblockchain(250)186# The backup should be updated with the latest height (locator) for187# the backup to load successfully this close to the prune height188node.restorewallet('pruned', node.datadir_path / 'wallet_pruned.bak')190self.log.info("Test restore on a pruned node when the backup was beyond the pruning point")191backup_file = self.nodes[0].datadir_path / 'wallet.bak'192error_message = "Wallet loading failed. Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)"193assert_raises_rpc_error(-4, error_message, node.restorewallet, "restore_pruned", backup_file)194assert node.wallets_path.exists() # ensure the wallets dir exists196def run_test(self):197self.log.info("Generating initial blockchain")198self.generate(self.nodes[0], 1)199self.generate(self.nodes[1], 1)200self.generate(self.nodes[2], 1)201self.generate(self.nodes[3], COINBASE_MATURITY)203assert_equal(self.nodes[0].getbalance(), 50)204assert_equal(self.nodes[1].getbalance(), 50)205assert_equal(self.nodes[2].getbalance(), 50)206assert_equal(self.nodes[3].getbalance(), 0)208self.log.info("Creating transactions")209# Five rounds of sending each other transactions.210for _ in range(5):211self.do_one_round()213self.log.info("Backing up")215for node_num in range(3):216self.nodes[node_num].backupwallet(self.nodes[node_num].datadir_path / 'wallet.bak')218self.log.info("More transactions")219for _ in range(5):220self.do_one_round()222# Generate 101 more blocks, so any fees paid mature223self.generate(self.nodes[3], COINBASE_MATURITY + 1)225balance0 = self.nodes[0].getbalance()226balance1 = self.nodes[1].getbalance()227balance2 = self.nodes[2].getbalance()228balance3 = self.nodes[3].getbalance()229total = balance0 + balance1 + balance2 + balance3231# At this point, there are 214 blocks (103 for setup, then 10 rounds, then 101.)232# 114 are mature, so the sum of all wallets should be 114 * 50 = 5700.233assert_equal(total, 5700)235##236# Test restoring spender wallets from backups237##238self.log.info("Restoring wallets on node 3 using backup files")240self.restore_invalid_wallet()241self.restore_nonexistent_wallet()243backup_files = []244for node_num in range(3):245backup_files.append(self.nodes[node_num].datadir_path / 'wallet.bak')247for idx, backup_file in enumerate(backup_files):248self.nodes[3].restorewallet(f'res{idx}', backup_file)249assert (self.nodes[3].wallets_path / f'res{idx}').exists()
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.