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 the avoid_reuse and setwalletflag features."""7from test_framework.test_framework import BitcoinTestFramework8from test_framework.util import (9assert_not_equal,10assert_approx,11assert_equal,12assert_raises_rpc_error,13)15def reset_balance(node, discardaddr):16'''Throw away all owned coins by the node so it gets a balance of 0.'''17balance = node.getbalance(avoid_reuse=False)18if balance > 0.5:19node.sendtoaddress(address=discardaddr, amount=balance, subtractfeefromamount=True, avoid_reuse=False)21def count_unspent(node):22'''Count the unspent outputs for the given node and return various statistics'''23r = {24"total": {25"count": 0,26"sum": 0,27},28"reused": {29"count": 0,30"sum": 0,31},32}33supports_reused = True34for utxo in node.listunspent(minconf=0):35r["total"]["count"] += 136r["total"]["sum"] += utxo["amount"]37if supports_reused and "reused" in utxo:38if utxo["reused"]:39r["reused"]["count"] += 140r["reused"]["sum"] += utxo["amount"]41else:42supports_reused = False43r["reused"]["supported"] = supports_reused44return r46def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None, margin=0.001):47'''Make assertions about a node's unspent output statistics'''48stats = count_unspent(node)49if total_count is not None:50assert_equal(stats["total"]["count"], total_count)51if total_sum is not None:52assert_approx(stats["total"]["sum"], total_sum, margin)53if reused_supported is not None:54assert_equal(stats["reused"]["supported"], reused_supported)55if reused_count is not None:56assert_equal(stats["reused"]["count"], reused_count)57if reused_sum is not None:58assert_approx(stats["reused"]["sum"], reused_sum, margin)60def assert_balances(node, mine, margin=0.001):61'''Make assertions about a node's getbalances output'''62got = node.getbalances()["mine"]63for k,v in mine.items():64assert_approx(got[k], v, margin)66class AvoidReuseTest(BitcoinTestFramework):67def set_test_params(self):68self.num_nodes = 269# whitelist peers to speed up tx relay / mempool sync70self.noban_tx_relay = True72def skip_test_if_missing_module(self):73self.skip_if_no_wallet()75def run_test(self):76'''Set up initial chain and run tests defined below'''78self.test_persistence()79self.test_immutable()81self.generate(self.nodes[0], 110)82self.test_change_remains_change(self.nodes[1])83reset_balance(self.nodes[1], self.nodes[0].getnewaddress())84self.test_sending_from_reused_address_without_avoid_reuse()85reset_balance(self.nodes[1], self.nodes[0].getnewaddress())86self.test_sending_from_reused_address_fails("legacy")87reset_balance(self.nodes[1], self.nodes[0].getnewaddress())88self.test_sending_from_reused_address_fails("p2sh-segwit")89reset_balance(self.nodes[1], self.nodes[0].getnewaddress())90self.test_sending_from_reused_address_fails("bech32")91reset_balance(self.nodes[1], self.nodes[0].getnewaddress())92self.test_getbalances_used()93reset_balance(self.nodes[1], self.nodes[0].getnewaddress())94self.test_full_destination_group_is_preferred()95reset_balance(self.nodes[1], self.nodes[0].getnewaddress())96self.test_all_destination_groups_are_used()98def test_persistence(self):99'''Test that wallet files persist the avoid_reuse flag.'''100self.log.info("Test wallet files persist avoid_reuse flag")102# Configure node 1 to use avoid_reuse103self.nodes[1].setwalletflag('avoid_reuse')105# Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true106assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)107assert_equal(sorted(self.nodes[0].getwalletinfo()["flags"]), sorted(["descriptor_wallet", "last_hardened_xpub_cached"]))108assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)109assert_equal(sorted(self.nodes[1].getwalletinfo()["flags"]), sorted(["descriptor_wallet", "last_hardened_xpub_cached", "avoid_reuse"]))111self.restart_node(1)112self.connect_nodes(0, 1)114# Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true115assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)116assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)118# Attempting to set flag to its current state should throw119assert_raises_rpc_error(-8, "Wallet flag is already set to false", self.nodes[0].setwalletflag, 'avoid_reuse', False)120assert_raises_rpc_error(-8, "Wallet flag is already set to true", self.nodes[1].setwalletflag, 'avoid_reuse', True)122assert_raises_rpc_error(-8, "Unknown wallet flag: abc", self.nodes[0].setwalletflag, 'abc', True)124# Create a wallet with avoid reuse, and test that disabling it afterwards persists125self.nodes[1].createwallet(wallet_name="avoid_reuse_persist", avoid_reuse=True)126w = self.nodes[1].get_wallet_rpc("avoid_reuse_persist")127assert_equal(w.getwalletinfo()["avoid_reuse"], True)128w.setwalletflag("avoid_reuse", False)129assert_equal(w.getwalletinfo()["avoid_reuse"], False)130w.unloadwallet()131self.nodes[1].loadwallet("avoid_reuse_persist")132assert_equal(w.getwalletinfo()["avoid_reuse"], False)133w.unloadwallet()135def test_immutable(self):136'''Test immutable wallet flags'''137self.log.info("Test immutable wallet flags")139# Attempt to set the disable_private_keys flag; this should not work140assert_raises_rpc_error(-8, "Wallet flag is immutable", self.nodes[1].setwalletflag, 'disable_private_keys')142tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat"144# Create a wallet with disable_private_keys set; this should work145self.nodes[1].createwallet(wallet_name=tempwallet, disable_private_keys=True)146w = self.nodes[1].get_wallet_rpc(tempwallet)148# Attempt to unset the disable_private_keys flag; this should not work149assert_raises_rpc_error(-8, "Wallet flag is immutable", w.setwalletflag, 'disable_private_keys', False)151# Unload temp wallet152self.nodes[1].unloadwallet(tempwallet)154def test_change_remains_change(self, node):155self.log.info("Test that change doesn't turn into non-change when spent")157reset_balance(node, node.getnewaddress())158addr = node.getnewaddress()159txid = node.sendtoaddress(addr, 1)160out = node.listunspent(minconf=0, query_options={'minimumAmount': 2})161assert_equal(len(out), 1)162assert_equal(out[0]['txid'], txid)163changeaddr = out[0]['address']165# Make sure it's starting out as change as expected166assert node.getaddressinfo(changeaddr)['ischange']167for logical_tx in node.listtransactions():168assert_not_equal(logical_tx.get('address'), changeaddr)170# Spend it171reset_balance(node, node.getnewaddress())173# It should still be change174assert node.getaddressinfo(changeaddr)['ischange']175for logical_tx in node.listtransactions():176assert_not_equal(logical_tx.get('address'), changeaddr)178def test_sending_from_reused_address_without_avoid_reuse(self):179'''180Test the same as test_sending_from_reused_address_fails, except send the 10 BTC with181the avoid_reuse flag set to false. This means the 10 BTC send should succeed,182where it fails in test_sending_from_reused_address_fails.183'''184self.log.info("Test sending from reused address with avoid_reuse=false")186fundaddr = self.nodes[1].getnewaddress()187retaddr = self.nodes[0].getnewaddress()189self.nodes[0].sendtoaddress(fundaddr, 10)190self.generate(self.nodes[0], 1)192# listunspent should show 1 single, unused 10 btc output193assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)194# getbalances should show no used, 10 btc trusted195assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10})196# node 0 should not show a used entry, as it does not enable avoid_reuse197assert "used" not in self.nodes[0].getbalances()["mine"]199self.nodes[1].sendtoaddress(retaddr, 5)200self.generate(self.nodes[0], 1)202# listunspent should show 1 single, unused 5 btc output203assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)204# getbalances should show no used, 5 btc trusted205assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})207self.nodes[0].sendtoaddress(fundaddr, 10)208self.generate(self.nodes[0], 1)210# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)211assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)212# getbalances should show 10 used, 5 btc trusted213assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5})215self.nodes[1].sendtoaddress(address=retaddr, amount=10, avoid_reuse=False)217# listunspent should show 1 total outputs (5 btc), unused218assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_count=0)219# getbalances should show no used, 5 btc trusted220assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})222# node 1 should now have about 5 btc left (for both cases)223assert_approx(self.nodes[1].getbalance(), 5, 0.001)224assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001)226def test_sending_from_reused_address_fails(self, second_addr_type):227'''228Test the simple case where [1] generates a new address A, then229[0] sends 10 BTC to A.230[1] spends 5 BTC from A. (leaving roughly 5 BTC useable)231[0] sends 10 BTC to A again.232[1] tries to spend 10 BTC (fails; dirty).233[1] tries to spend 4 BTC (succeeds; change address sufficient)234'''235self.log.info("Test sending from reused {} address fails".format(second_addr_type))237fundaddr = self.nodes[1].getnewaddress(label="", address_type="legacy")238retaddr = self.nodes[0].getnewaddress()240self.nodes[0].sendtoaddress(fundaddr, 10)241self.generate(self.nodes[0], 1)243# listunspent should show 1 single, unused 10 btc output244assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)245# getbalances should show no used, 10 btc trusted246assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10})248self.nodes[1].sendtoaddress(retaddr, 5)249self.generate(self.nodes[0], 1)
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.