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) 2025-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 how the wallet deals with TRUC transactions"""7from decimal import Decimal, getcontext9from test_framework.messages import (10COIN,11CTransaction,12CTxOut,13)14from test_framework.script import (15CScript,16OP_RETURN17)19from test_framework.script_util import bulk_vout21from test_framework.blocktools import (22create_empty_fork,23)25from test_framework.test_framework import BitcoinTestFramework26from test_framework.util import (27assert_equal,28assert_greater_than,29assert_raises_rpc_error,30JSONRPCException,31)33from test_framework.mempool_util import (34TRUC_MAX_VSIZE,35TRUC_CHILD_MAX_VSIZE,36)38# sweep alice and bob's wallets and clear the mempool39def cleanup(func):40def wrapper(self, *args):41self.generate(self.nodes[0], 1)42func(self, *args)44self.generate(self.nodes[0], 1)45for wallet in [self.alice, self.bob]:46txs = set(tx["txid"] for tx in wallet.listtransactions("*", 1000) if tx["confirmations"] == 0 and not tx["abandoned"])47for tx in txs:48wallet.abandontransaction(tx)49try:50wallet.sendall([self.charlie.getnewaddress()])51except JSONRPCException as e:52assert "Total value of UTXO pool too low to pay for transaction" in e.error['message']53self.generate(self.nodes[0], 1)55for wallet in [self.alice, self.bob]:56balance = wallet.getbalances()["mine"]57for balance_type in ["untrusted_pending", "trusted", "immature", "nonmempool"]:58assert_equal(balance[balance_type], 0)60assert_equal(self.alice.getrawmempool(), [])61assert_equal(self.bob.getrawmempool(), [])63return wrapper65class WalletV3Test(BitcoinTestFramework):66def skip_test_if_missing_module(self):67self.skip_if_no_wallet()69def set_test_params(self):70getcontext().prec=1071self.num_nodes = 172self.setup_clean_chain = True74def send_tx(self, from_wallet, inputs, outputs, version):75raw_tx = from_wallet.createrawtransaction(inputs=inputs, outputs=outputs, version=version)76if inputs == []:77raw_tx = from_wallet.fundrawtransaction(raw_tx, {'include_unsafe' : True})["hex"]78raw_tx = from_wallet.signrawtransactionwithwallet(raw_tx)["hex"]79txid = from_wallet.sendrawtransaction(raw_tx)80return txid82def bulk_tx(self, tx, amount, target_vsize):83tx.vout.append(CTxOut(nValue=(amount * COIN), scriptPubKey=CScript([OP_RETURN])))84bulk_vout(tx, target_vsize)86def run_test_with_swapped_versions(self, test_func):87test_func(2, 3)88test_func(3, 2)90def trigger_reorg(self, fork_blocks):91"""Trigger reorg of the fork blocks."""92for block in fork_blocks:93self.nodes[0].submitblock(block.serialize().hex())94assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)96def run_test(self):97self.nodes[0].createwallet("alice")98self.alice = self.nodes[0].get_wallet_rpc("alice")100self.nodes[0].createwallet("bob")101self.bob = self.nodes[0].get_wallet_rpc("bob")103self.nodes[0].createwallet("charlie")104self.charlie = self.nodes[0].get_wallet_rpc("charlie")106self.generatetoaddress(self.nodes[0], 100, self.charlie.getnewaddress())108self.run_test_with_swapped_versions(self.tx_spends_unconfirmed_tx_with_wrong_version)109self.run_test_with_swapped_versions(self.va_tx_spends_confirmed_vb_tx)110self.run_test_with_swapped_versions(self.spend_inputs_with_different_versions)111self.spend_inputs_with_different_versions_default_version()112self.v3_utxos_appear_in_listunspent()113self.truc_tx_with_conflicting_sibling()114self.truc_tx_with_conflicting_sibling_change()115self.v3_tx_evicted_from_mempool_by_sibling()116self.v3_conflict_removed_from_mempool()117self.mempool_conflicts_removed_when_v3_conflict_removed()118self.max_tx_weight()119self.max_tx_child_weight()120self.user_input_weight_not_overwritten()121self.user_input_weight_not_overwritten_v3_child()122self.createpsbt_v3()123self.send_v3()124self.sendall_v3()125self.sendall_with_unconfirmed_v3()126self.walletcreatefundedpsbt_v3()127self.sendall_truc_weight_limit()128self.sendall_truc_child_weight_limit()129self.mix_non_truc_versions()130self.cant_spend_multiple_unconfirmed_truc_outputs()131self.test_spend_third_generation()132self.test_coins_availability_reorg()134@cleanup135def tx_spends_unconfirmed_tx_with_wrong_version(self, version_a, version_b):136self.log.info(f"Test unavailable funds when v{version_b} tx spends unconfirmed v{version_a} tx")138outputs = {self.bob.getnewaddress() : 2.0}139self.send_tx(self.charlie, [], outputs, version_a)141assert_equal(self.bob.getbalances()["mine"]["trusted"], 0)142assert_greater_than(self.bob.getbalances()["mine"]["untrusted_pending"], 0)144outputs = {self.alice.getnewaddress() : 1.0}146raw_tx_v2 = self.bob.createrawtransaction(inputs=[], outputs=outputs, version=version_b)148assert_raises_rpc_error(149-4,150"Insufficient funds",151self.bob.fundrawtransaction,152raw_tx_v2, {'include_unsafe': True}153)155@cleanup156def va_tx_spends_confirmed_vb_tx(self, version_a, version_b):157self.log.info(f"Test available funds when v{version_b} tx spends confirmed v{version_a} tx")159outputs = {self.bob.getnewaddress() : 2.0}160self.send_tx(self.charlie, [], outputs, version_a)162assert_equal(self.bob.getbalances()["mine"]["trusted"], 0)163assert_greater_than(self.bob.getbalances()["mine"]["untrusted_pending"], 0)165outputs = {self.alice.getnewaddress() : 1.0}167self.generate(self.nodes[0], 1)169self.send_tx(self.bob, [], outputs, version_b)171@cleanup172def v3_utxos_appear_in_listunspent(self):173self.log.info("Test that unconfirmed v3 utxos still appear in listunspent")175outputs = {self.alice.getnewaddress() : 2.0}176parent_txid = self.send_tx(self.charlie, [], outputs, 3)177assert_equal(self.alice.listunspent(minconf=0)[0]["txid"], parent_txid)179@cleanup180def truc_tx_with_conflicting_sibling(self):181self.log.info("Test v3 transaction with conflicting sibling")183# unconfirmed v3 tx to alice & bob184outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}185self.send_tx(self.charlie, [], outputs, 3)187# alice spends her output with a v3 transaction188alice_unspent = self.alice.listunspent(minconf=0)[0]189outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - Decimal(0.00000120)}190self.send_tx(self.alice, [alice_unspent], outputs, 3)192# bob tries to spend money193outputs = {self.bob.getnewaddress() : 1.999}194bob_tx = self.bob.createrawtransaction(inputs=[], outputs=outputs, version=3)196assert_raises_rpc_error(197-4,198"Insufficient funds",199self.bob.fundrawtransaction,200bob_tx, {'include_unsafe': True}201)203@cleanup204def truc_tx_with_conflicting_sibling_change(self):205self.log.info("Test v3 transaction with conflicting sibling change")207outputs = {self.alice.getnewaddress() : 8.0}208self.send_tx(self.charlie, [], outputs, 3)210self.generate(self.nodes[0], 1)212# unconfirmed v3 tx to alice & bob213outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}214self.send_tx(self.alice, [], outputs, 3)216# bob spends his output with a v3 transaction217bob_unspent = self.bob.listunspent(minconf=0)[0]218outputs = {self.bob.getnewaddress() : bob_unspent['amount'] - Decimal(0.00000120)}219self.send_tx(self.bob, [bob_unspent], outputs, 3)221# alice tries to spend money222outputs = {self.alice.getnewaddress() : 1.999}223alice_tx = self.alice.createrawtransaction(inputs=[], outputs=outputs, version=3)225assert_raises_rpc_error(226-4,227"Insufficient funds",228self.alice.fundrawtransaction,229alice_tx, {'include_unsafe': True}230)232@cleanup233def spend_inputs_with_different_versions(self, version_a, version_b):234self.log.info(f"Test spending a pre-selected v{version_a} input with a v{version_b} transaction")236outputs = {self.alice.getnewaddress() : 2.0}237self.send_tx(self.charlie, [], outputs, version_a)239# alice spends her output240alice_unspent = self.alice.listunspent(minconf=0)[0]241outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - Decimal(0.00000120)}242alice_tx = self.alice.createrawtransaction(inputs=[alice_unspent], outputs=outputs, version=version_b)244assert_raises_rpc_error(245-4,246f"Can't spend unconfirmed version {version_a} pre-selected input with a version {version_b} tx",247self.alice.fundrawtransaction,248alice_tx249)
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.