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 wallet group functionality."""7from test_framework.blocktools import COINBASE_MATURITY8from test_framework.test_framework import BitcoinTestFramework9from test_framework.messages import (10tx_from_hex,11)12from test_framework.util import (13assert_approx,14assert_equal,15)18class WalletGroupTest(BitcoinTestFramework):19def set_test_params(self):20self.setup_clean_chain = True21self.num_nodes = 522# whitelist peers to speed up tx relay / mempool sync23self.noban_tx_relay = True24self.extra_args = [25[],26[],27["-avoidpartialspends"],28["-maxapsfee=0.00002719"],29["-maxapsfee=0.00002720"],30]32self.fee_rate = 20 # apply feerate of 20 sats/vB across all nodes34self.rpc_timeout = 48036def skip_test_if_missing_module(self):37self.skip_if_no_wallet()39def run_test(self):40self.log.info("Setting up")41# Mine some coins42self.generate(self.nodes[0], COINBASE_MATURITY + 1)44# Get some addresses from the two nodes45addr1 = [self.nodes[1].getnewaddress() for _ in range(3)]46addr2 = [self.nodes[2].getnewaddress() for _ in range(3)]47addrs = addr1 + addr249# Send 1 + 0.5 coin to each address50[self.nodes[0].sendtoaddress(addr, 1.0, fee_rate=self.fee_rate) for addr in addrs]51[self.nodes[0].sendtoaddress(addr, 0.5, fee_rate=self.fee_rate) for addr in addrs]53self.generate(self.nodes[0], 1)55# For each node, send 0.2 coins back to 0;56# - node[1] should pick one 0.5 UTXO and leave the rest57# - node[2] should pick one (1.0 + 0.5) UTXO group corresponding to a58# given address, and leave the rest59self.log.info("Test sending transactions picks one UTXO group and leaves the rest")60txid1 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 0.2, fee_rate=self.fee_rate)61tx1 = self.nodes[1].getrawtransaction(txid1, True)62# txid1 should have 1 input and 2 outputs63assert_equal(1, len(tx1["vin"]))64assert_equal(2, len(tx1["vout"]))65# one output should be 0.2, the other should be ~0.366v = [vout["value"] for vout in tx1["vout"]]67v.sort()68assert_approx(v[0], vexp=0.2, vspan=0.0001)69assert_approx(v[1], vexp=0.3, vspan=0.0001)71txid2 = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 0.2, fee_rate=self.fee_rate)72tx2 = self.nodes[2].getrawtransaction(txid2, True)73# txid2 should have 2 inputs and 2 outputs74assert_equal(2, len(tx2["vin"]))75assert_equal(2, len(tx2["vout"]))76# one output should be 0.2, the other should be ~1.377v = [vout["value"] for vout in tx2["vout"]]78v.sort()79assert_approx(v[0], vexp=0.2, vspan=0.0001)80assert_approx(v[1], vexp=1.3, vspan=0.0001)82self.log.info("Test avoiding partial spends if warranted, even if avoidpartialspends is disabled")83self.sync_all()84self.generate(self.nodes[0], 1)85# Nodes 1-2 now have confirmed UTXOs (letters denote destinations):86# Node #1: Node #2:87# - A 1.0 - D0 1.088# - B0 1.0 - D1 0.589# - B1 0.5 - E0 1.090# - C0 1.0 - E1 0.591# - C1 0.5 - F ~1.392# - D ~0.393assert_approx(self.nodes[1].getbalance(), vexp=4.3, vspan=0.0001)94assert_approx(self.nodes[2].getbalance(), vexp=4.3, vspan=0.0001)95# Sending 1.4 btc should pick one 1.0 + one more. For node #1,96# this could be (A / B0 / C0) + (B1 / C1 / D). We ensure that it is97# B0 + B1 or C0 + C1, because this avoids partial spends while not being98# detrimental to transaction cost99txid3 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.4, fee_rate=self.fee_rate)100tx3 = self.nodes[1].getrawtransaction(txid3, True)101# tx3 should have 2 inputs and 2 outputs102assert_equal(2, len(tx3["vin"]))103assert_equal(2, len(tx3["vout"]))104# the accumulated value should be 1.5, so the outputs should be105# ~0.1 and 1.4 and should come from the same destination106values = [vout["value"] for vout in tx3["vout"]]107values.sort()108assert_approx(values[0], vexp=0.1, vspan=0.0001)109assert_approx(values[1], vexp=1.4, vspan=0.0001)111input_txids = [vin["txid"] for vin in tx3["vin"]]112input_addrs = [self.nodes[1].gettransaction(txid)['details'][0]['address'] for txid in input_txids]113assert_equal(input_addrs[0], input_addrs[1])114# Node 2 enforces avoidpartialspends so needs no checking here116tx4_ungrouped_fee = 2820117tx4_grouped_fee = 4160118tx5_6_ungrouped_fee = 5520119tx5_6_grouped_fee = 8240121self.log.info("Test wallet option maxapsfee")122addr_aps = self.nodes[3].getnewaddress()123self.nodes[0].sendtoaddress(addr_aps, 1.0, fee_rate=self.fee_rate)124self.nodes[0].sendtoaddress(addr_aps, 1.0, fee_rate=self.fee_rate)125self.generate(self.nodes[0], 1)126with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx4_ungrouped_fee}, grouped = {tx4_grouped_fee}, using grouped']):127txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1, fee_rate=self.fee_rate)128tx4 = self.nodes[3].getrawtransaction(txid4, True)129# tx4 should have 2 inputs and 2 outputs although one output would130# have been enough and the transaction caused higher fees131assert_equal(2, len(tx4["vin"]))132assert_equal(2, len(tx4["vout"]))134addr_aps2 = self.nodes[3].getnewaddress()135[self.nodes[0].sendtoaddress(addr_aps2, 1.0, fee_rate=self.fee_rate) for _ in range(5)]136self.generate(self.nodes[0], 1)137with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using non-grouped']):138txid5 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 2.95, fee_rate=self.fee_rate)139tx5 = self.nodes[3].getrawtransaction(txid5, True)140# tx5 should have 3 inputs (1.0, 1.0, 1.0) and 2 outputs141assert_equal(3, len(tx5["vin"]))142assert_equal(2, len(tx5["vout"]))144# Test wallet option maxapsfee with node 4, which sets maxapsfee145# 1 sat higher, crossing the threshold from non-grouped to grouped.146self.log.info("Test wallet option maxapsfee threshold from non-grouped to grouped")147addr_aps3 = self.nodes[4].getnewaddress()148[self.nodes[0].sendtoaddress(addr_aps3, 1.0, fee_rate=self.fee_rate) for _ in range(5)]149self.generate(self.nodes[0], 1)150with self.nodes[4].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using grouped']):151txid6 = self.nodes[4].sendtoaddress(self.nodes[0].getnewaddress(), 2.95, fee_rate=self.fee_rate)152tx6 = self.nodes[4].getrawtransaction(txid6, True)153# tx6 should have 5 inputs and 2 outputs154assert_equal(5, len(tx6["vin"]))155assert_equal(2, len(tx6["vout"]))157# Empty out node2's wallet158self.nodes[2].sendall(recipients=[self.nodes[0].getnewaddress()])159self.sync_all()160self.generate(self.nodes[0], 1)162self.log.info("Fill a wallet with 10,000 outputs corresponding to the same scriptPubKey")163for _ in range(5):164raw_tx = self.nodes[0].createrawtransaction([{"txid":"0"*64, "vout":0}], [{addr2[0]: 0.05}])165tx = tx_from_hex(raw_tx)166tx.vin = []167tx.vout = [tx.vout[0]] * 2000168funded_tx = self.nodes[0].fundrawtransaction(tx.serialize().hex(), options={'fee_rate': self.fee_rate})169signed_tx = self.nodes[0].signrawtransactionwithwallet(funded_tx['hex'])170self.nodes[0].sendrawtransaction(signed_tx['hex'])171self.generate(self.nodes[0], 1)173# Check that we can create a transaction that only requires ~100 of our174# utxos, without pulling in all outputs and creating a transaction that175# is way too big.176self.log.info("Test creating txn that only requires ~100 of our UTXOs without pulling in all outputs")177assert self.nodes[2].sendtoaddress(address=addr2[0], amount=5, fee_rate=self.fee_rate)180if __name__ == '__main__':181WalletGroupTest(__file__).main()
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.