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."""6from decimal import Decimal7from itertools import product9from test_framework.blocktools import COINBASE_MATURITY10from test_framework.descriptors import descsum_create11from test_framework.messages import (12COIN,13DEFAULT_ANCESTOR_LIMIT,14)15from test_framework.test_framework import BitcoinTestFramework16from test_framework.util import (17assert_array_result,18assert_equal,19assert_fee_amount,20assert_raises_rpc_error,21)22from test_framework.wallet_util import test_address23from test_framework.wallet import MiniWallet25NOT_A_NUMBER_OR_STRING = "Amount is not a number or string"26OUT_OF_RANGE = "Amount out of range"29class WalletTest(BitcoinTestFramework):30def set_test_params(self):31self.num_nodes = 432# whitelist peers to speed up tx relay / mempool sync33self.noban_tx_relay = True34self.extra_args = [[35"-dustrelayfee=0", "-walletrejectlongchains=0"36]] * self.num_nodes37self.setup_clean_chain = True38self.supports_cli = False40def skip_test_if_missing_module(self):41self.skip_if_no_wallet()43def setup_network(self):44self.setup_nodes()45# Only need nodes 0-2 running at start of test46self.stop_node(3)47self.connect_nodes(0, 1)48self.connect_nodes(1, 2)49self.connect_nodes(0, 2)50self.sync_all(self.nodes[0:3])52def check_fee_amount(self, curr_balance, balance_with_fee, fee_per_byte, tx_size):53"""Return curr_balance after asserting the fee was in range"""54fee = balance_with_fee - curr_balance55assert_fee_amount(fee, tx_size, fee_per_byte * 1000)56return curr_balance58def get_vsize(self, txn):59return self.nodes[0].decoderawtransaction(txn)['vsize']61def run_test(self):63# Check that there's no UTXO on none of the nodes64assert_equal(len(self.nodes[0].listunspent()), 0)65assert_equal(len(self.nodes[1].listunspent()), 0)66assert_equal(len(self.nodes[2].listunspent()), 0)68self.log.info("Mining blocks...")70self.generate(self.nodes[0], 1, sync_fun=self.no_op)72balances = self.nodes[0].getbalances()73assert_equal(balances["mine"]["immature"], 50)74assert_equal(balances["mine"]["trusted"], 0)76self.sync_all(self.nodes[0:3])77self.generate(self.nodes[1], COINBASE_MATURITY + 1, sync_fun=lambda: self.sync_all(self.nodes[0:3]))79assert_equal(self.nodes[0].getbalance(), 50)80assert_equal(self.nodes[1].getbalance(), 50)81assert_equal(self.nodes[2].getbalance(), 0)83# Check that only first and second nodes have UTXOs84utxos = self.nodes[0].listunspent()85assert_equal(len(utxos), 1)86assert_equal(len(self.nodes[1].listunspent()), 1)87assert_equal(len(self.nodes[2].listunspent()), 0)89self.log.info("Test gettxout")90confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]91# First, outputs that are unspent both in the chain and in the92# mempool should appear with or without include_mempool93txout = self.nodes[0].gettxout(txid=confirmed_txid, n=confirmed_index, include_mempool=False)94assert_equal(txout['value'], 50)95txout = self.nodes[0].gettxout(txid=confirmed_txid, n=confirmed_index, include_mempool=True)96assert_equal(txout['value'], 50)98# Send 21 BTC from 0 to 2 using sendtoaddress call.99self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)100mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)102self.log.info("Test gettxout (second part)")103# utxo spent in mempool should be visible if you exclude mempool104# but invisible if you include mempool105txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)106assert_equal(txout['value'], 50)107txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index) # by default include_mempool=True108assert txout is None109txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, True)110assert txout is None111# new utxo from mempool should be invisible if you exclude mempool112# but visible if you include mempool113txout = self.nodes[0].gettxout(mempool_txid, 0, False)114assert txout is None115txout1 = self.nodes[0].gettxout(mempool_txid, 0, True)116txout2 = self.nodes[0].gettxout(mempool_txid, 1, True)117# note the mempool tx will have randomly assigned indices118# but 10 will go to node2 and the rest will go to node0119balance = self.nodes[0].getbalance()120assert_equal(set([txout1['value'], txout2['value']]), set([10, balance]))121assert_equal(self.nodes[0].getbalances()["mine"]["immature"], 0)123# Have node0 mine a block, thus it will collect its own fee.124self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_all(self.nodes[0:3]))126# Exercise locking of unspent outputs127unspent_0 = self.nodes[2].listunspent()[0]128unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}129# Trying to unlock an output which isn't locked should error130assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])132# Locking an already-locked output should error133self.nodes[2].lockunspent(False, [unspent_0])134assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])136# Restarting the node should clear the lock137self.restart_node(2)138self.nodes[2].lockunspent(False, [unspent_0])140# Unloading and reloating the wallet should clear the lock141assert_equal(self.nodes[0].listwallets(), [self.default_wallet_name])142self.nodes[2].unloadwallet(self.default_wallet_name)143self.nodes[2].loadwallet(self.default_wallet_name)144assert_equal(len(self.nodes[2].listlockunspent()), 0)146# Locking non-persistently, then re-locking persistently, is allowed147self.nodes[2].lockunspent(False, [unspent_0])148self.nodes[2].lockunspent(False, [unspent_0], True)150# Restarting the node with the lock written to the wallet should keep the lock151self.restart_node(2, ["-walletrejectlongchains=0"])152assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])154# Unloading and reloading the wallet with a persistent lock should keep the lock155self.nodes[2].unloadwallet(self.default_wallet_name)156self.nodes[2].loadwallet(self.default_wallet_name)157assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])159# Locked outputs should not be used, even if they are the only available funds160assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)161assert_equal([unspent_0], self.nodes[2].listlockunspent())163# Unlocking should remove the persistent lock164self.nodes[2].lockunspent(True, [unspent_0])165self.restart_node(2)166assert_equal(len(self.nodes[2].listlockunspent()), 0)168# Reconnect node 2 after restarts169self.connect_nodes(1, 2)170self.connect_nodes(0, 2)172assert_raises_rpc_error(-8, "txid must be of length 64 (not 34, for '0000000000000000000000000000000000')",173self.nodes[2].lockunspent, False,174[{"txid": "0000000000000000000000000000000000", "vout": 0}])175assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')",176self.nodes[2].lockunspent, False,177[{"txid": "ZZZ0000000000000000000000000000000000000000000000000000000000000", "vout": 0}])178assert_raises_rpc_error(-8, "Invalid parameter, unknown transaction",179self.nodes[2].lockunspent, False,180[{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0}])181assert_raises_rpc_error(-8, "Invalid parameter, vout index out of bounds",182self.nodes[2].lockunspent, False,183[{"txid": unspent_0["txid"], "vout": 999}])185# The lock on a manually selected output is ignored186unspent_0 = self.nodes[1].listunspent()[0]187self.nodes[1].lockunspent(False, [unspent_0])188tx = self.nodes[1].createrawtransaction([unspent_0], { self.nodes[1].getnewaddress() : 1 })189self.nodes[1].fundrawtransaction(tx,{"lockUnspents": True})191# fundrawtransaction can lock an input192self.nodes[1].lockunspent(True, [unspent_0])193assert_equal(len(self.nodes[1].listlockunspent()), 0)194tx = self.nodes[1].fundrawtransaction(tx,{"lockUnspents": True})['hex']195assert_equal(len(self.nodes[1].listlockunspent()), 1)197# Send transaction198tx = self.nodes[1].signrawtransactionwithwallet(tx)["hex"]199self.nodes[1].sendrawtransaction(tx)200assert_equal(len(self.nodes[1].listlockunspent()), 0)202# Have node1 generate 100 blocks (so node0 can recover the fee)203self.generate(self.nodes[1], COINBASE_MATURITY, sync_fun=lambda: self.sync_all(self.nodes[0:3]))205# node0 should end up with 100 btc in block rewards plus fees, but206# minus the 21 plus fees sent to node2207assert_equal(self.nodes[0].getbalance(), 100 - 21)208assert_equal(self.nodes[2].getbalance(), 21)210# Node0 should have two unspent outputs.211# Create a couple of transactions to send them to node2, submit them through212# node1, and make sure both node0 and node2 pick them up properly:213node0utxos = self.nodes[0].listunspent(1)214assert_equal(len(node0utxos), 2)216# create both transactions217txns_to_send = []218for utxo in node0utxos:219inputs = []220outputs = {}221inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]})222outputs[self.nodes[2].getnewaddress()] = utxo["amount"] - 3223raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)224txns_to_send.append(self.nodes[0].signrawtransactionwithwallet(raw_tx))226# Have node 1 (miner) send the transactions227self.nodes[1].sendrawtransaction(hexstring=txns_to_send[0]["hex"], maxfeerate=0)228self.nodes[1].sendrawtransaction(hexstring=txns_to_send[1]["hex"], maxfeerate=0)230# Have node1 mine a block to confirm transactions:231self.generate(self.nodes[1], 1, sync_fun=lambda: self.sync_all(self.nodes[0:3]))233assert_equal(self.nodes[0].getbalance(), 0)234assert_equal(self.nodes[2].getbalance(), 94)236# Verify that a spent output cannot be locked anymore237spent_0 = {"txid": node0utxos[0]["txid"], "vout": node0utxos[0]["vout"]}238assert_raises_rpc_error(-8, "Invalid parameter, expected unspent output", self.nodes[0].lockunspent, False, [spent_0])240# Send 10 BTC normal241address = self.nodes[0].getnewaddress("test")242fee_per_byte = Decimal('0.001') / 1000243fee_rate_sat_vb = fee_per_byte * Decimal(1e8)244txid = self.nodes[2].sendtoaddress(address, 10, "", "", False, fee_rate=fee_rate_sat_vb)245self.generate(self.nodes[2], 1, sync_fun=lambda: self.sync_all(self.nodes[0:3]))246node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))247assert_equal(self.nodes[0].getbalance(), Decimal('10'))249# Send 10 BTC with subtract fee from amount250txid = self.nodes[2].sendtoaddress(address, 10, "", "", True, fee_rate=fee_rate_sat_vb)
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.