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) 2018-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 bitcoin-wallet."""7import os8import stat9import subprocess10import textwrap12from collections import OrderedDict14from test_framework.test_framework import BitcoinTestFramework15from test_framework.util import (16assert_equal,17assert_greater_than,18sha256sum_file,19)22class ToolWalletTest(BitcoinTestFramework):23def set_test_params(self):24self.num_nodes = 125self.setup_clean_chain = True26self.rpc_timeout = 12028def skip_test_if_missing_module(self):29self.skip_if_no_wallet()30self.skip_if_no_wallet_tool()32def bitcoin_wallet_process(self, *args):33default_args = ['-datadir={}'.format(self.nodes[0].datadir_path), '-chain=%s' % self.chain]35return subprocess.Popen(self.get_binaries().wallet_argv() + default_args + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)37def assert_raises_tool_error(self, error, *args):38p = self.bitcoin_wallet_process(*args)39stdout, stderr = p.communicate()40assert_equal(stdout, '')41if isinstance(error, tuple):42assert_equal(p.poll(), error[0])43assert error[1] in stderr.strip()44else:45assert_equal(p.poll(), 1)46assert error in stderr.strip()48def assert_tool_output(self, output, *args):49p = self.bitcoin_wallet_process(*args)50stdout, stderr = p.communicate()51assert_equal(stderr, '')52assert_equal(stdout, output)53assert_equal(p.poll(), 0)55def wallet_shasum(self):56return sha256sum_file(self.wallet_path).hex()58def wallet_timestamp(self):59return os.path.getmtime(self.wallet_path)61def wallet_permissions(self):62return oct(os.lstat(self.wallet_path).st_mode)[-3:]64def log_wallet_timestamp_comparison(self, old, new):65result = 'unchanged' if new == old else 'increased!'66self.log.debug('Wallet file timestamp {}'.format(result))68def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0, imported_privs=0):69wallet_name = self.default_wallet_name if name == "" else name70output_types = 4 # p2pkh, p2sh, segwit, bech32m71return textwrap.dedent('''\72Wallet info73===========74Name: %s75Format: sqlite76Descriptors: yes77Encrypted: no78HD (hd seed available): yes79Keypool Size: %d80Transactions: %d81Address Book: %d82''' % (wallet_name, keypool * output_types, transactions, imported_privs * 3 + address))84def read_dump(self, filename):85dump = OrderedDict()86with open(filename, "r") as f:87for row in f:88row = row.strip()89key, value = row.split(',')90dump[key] = value91return dump93def assert_is_sqlite(self, filename):94with open(filename, 'rb') as f:95file_magic = f.read(16)96assert_equal(file_magic, b'SQLite format 3\x00')98def write_dump(self, dump, filename, magic=None, skip_checksum=False):99if magic is None:100magic = "BITCOIN_CORE_WALLET_DUMP"101with open(filename, "w") as f:102row = ",".join([magic, dump[magic]]) + "\n"103f.write(row)104for k, v in dump.items():105if k == magic or k == "checksum":106continue107row = ",".join([k, v]) + "\n"108f.write(row)109if not skip_checksum:110row = ",".join(["checksum", dump["checksum"]]) + "\n"111f.write(row)113def do_tool_createfromdump(self, wallet_name, dumpfile):114dumppath = self.nodes[0].datadir_path / dumpfile115rt_dumppath = self.nodes[0].datadir_path / "rt-{}.dump".format(wallet_name)117args = ["-wallet={}".format(wallet_name),118"-dumpfile={}".format(dumppath)]119args.append("createfromdump")121load_output = ""122self.assert_tool_output(load_output, *args)123assert (self.nodes[0].wallets_path / wallet_name).is_dir()125self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", '-wallet={}'.format(wallet_name), '-dumpfile={}'.format(rt_dumppath), 'dump')127wallet_dat = self.nodes[0].wallets_path / wallet_name / "wallet.dat"128self.assert_is_sqlite(wallet_dat)130def test_invalid_tool_commands_and_args(self):131self.log.info('Testing that various invalid commands raise with specific error messages')132self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'foo'", 'foo')133# `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`.134self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'help'", 'help')135self.assert_raises_tool_error('Error: Additional arguments provided (create). Methods do not take arguments. Please refer to `-help`.', 'info', 'create')136self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')137self.assert_raises_tool_error('No method provided. Run `bitcoin-wallet -help` for valid methods.')138self.assert_raises_tool_error('Wallet name must be provided when creating a new wallet.', 'create')139self.assert_raises_tool_error('Wallet name must be provided when creating a new wallet.', 'createfromdump')140error = f"SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another instance of {self.config['environment']['CLIENT_NAME']}?"141self.assert_raises_tool_error(142error,143'-wallet=' + self.default_wallet_name,144'info',145)146path = self.nodes[0].wallets_path / "nonexistent.dat"147self.assert_raises_tool_error("Failed to load database path '{}'. Path does not exist.".format(path), '-wallet=nonexistent.dat', 'info')149def test_tool_wallet_info(self):150# Stop the node to close the wallet to call the info command.151self.stop_node(0)152self.log.info('Calling wallet tool info, testing output')153#154# TODO: Wallet tool info should work with wallet file permissions set to155# read-only without raising:156# "Error loading wallet.dat. Is wallet being used by another process?"157# The following lines should be uncommented and the tests still succeed:158#159# self.log.debug('Setting wallet file permissions to 400 (read-only)')160# os.chmod(self.wallet_path, stat.S_IRUSR)161# assert self.wallet_permissions() in ['400', '666'] # Sanity check. 666 on Windows.162# shasum_before = self.wallet_shasum()163timestamp_before = self.wallet_timestamp()164self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))165out = self.get_expected_info_output(imported_privs=1)166self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')167timestamp_after = self.wallet_timestamp()168self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))169self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)170self.log.debug('Setting wallet file permissions back to 600 (read/write)')171os.chmod(self.wallet_path, stat.S_IRUSR | stat.S_IWUSR)172assert self.wallet_permissions() in ['600', '666'] # Sanity check. 666 on Windows.173#174# TODO: Wallet tool info should not write to the wallet file.175# The following lines should be uncommented and the tests still succeed:176#177# assert_equal(timestamp_before, timestamp_after)178# shasum_after = self.wallet_shasum()179# assert_equal(shasum_before, shasum_after)180# self.log.debug('Wallet file shasum unchanged\n')182def test_tool_wallet_info_after_transaction(self):183"""184Mutate the wallet with a transaction to verify that the info command185output changes accordingly.186"""187self.start_node(0)188self.log.info('Generating transaction to mutate wallet')189self.generate(self.nodes[0], 1)190self.stop_node(0)192self.log.info('Calling wallet tool info after generating a transaction, testing output')193shasum_before = self.wallet_shasum()194timestamp_before = self.wallet_timestamp()195self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))196out = self.get_expected_info_output(transactions=1, imported_privs=1)197self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')198shasum_after = self.wallet_shasum()199timestamp_after = self.wallet_timestamp()200self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))201self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)202#203# TODO: Wallet tool info should not write to the wallet file.204# This assertion should be uncommented and succeed:205# assert_equal(timestamp_before, timestamp_after)206assert_equal(shasum_before, shasum_after)207self.log.debug('Wallet file shasum unchanged\n')209def test_tool_wallet_create_on_existing_wallet(self):210self.log.info('Calling wallet tool create on an existing wallet, testing output')211shasum_before = self.wallet_shasum()212timestamp_before = self.wallet_timestamp()213self.log.debug('Wallet file timestamp before calling create: {}'.format(timestamp_before))214out = "Topping up keypool...\n" + self.get_expected_info_output(name="foo", keypool=2000)215self.assert_tool_output(out, '-wallet=foo', 'create')216shasum_after = self.wallet_shasum()217timestamp_after = self.wallet_timestamp()218self.log.debug('Wallet file timestamp after calling create: {}'.format(timestamp_after))219self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)220assert_equal(timestamp_before, timestamp_after)221assert_equal(shasum_before, shasum_after)222self.log.debug('Wallet file shasum unchanged\n')224def test_getwalletinfo_on_different_wallet(self):225self.log.info('Starting node with arg -wallet=foo')226self.start_node(0, ['-nowallet', '-wallet=foo'])228self.log.info('Calling getwalletinfo on a different wallet ("foo"), testing output')229shasum_before = self.wallet_shasum()230timestamp_before = self.wallet_timestamp()231self.log.debug('Wallet file timestamp before calling getwalletinfo: {}'.format(timestamp_before))232out = self.nodes[0].getwalletinfo()233self.stop_node(0)235shasum_after = self.wallet_shasum()236timestamp_after = self.wallet_timestamp()237self.log.debug('Wallet file timestamp after calling getwalletinfo: {}'.format(timestamp_after))239assert_equal(0, out['txcount'])240assert_equal(4000, out['keypoolsize'])241assert_equal(4000, out['keypoolsize_hd_internal'])243self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)244assert_equal(timestamp_before, timestamp_after)245assert_equal(shasum_after, shasum_before)246self.log.debug('Wallet file shasum unchanged\n')248def test_dump_createfromdump(self):249self.start_node(0)250self.nodes[0].createwallet("todump")
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.