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 fundrawtransaction RPC."""8from decimal import Decimal9from itertools import product10from math import ceil11from test_framework.address import address_to_scriptpubkey13from test_framework.descriptors import descsum_create14from test_framework.extendedkey import ExtendedPrivateKey15from test_framework.messages import (16COIN,17CTransaction,18CTxOut,19)20from test_framework.test_framework import BitcoinTestFramework21from test_framework.util import (22assert_not_equal,23assert_approx,24assert_equal,25assert_fee_amount,26assert_greater_than,27assert_greater_than_or_equal,28assert_raises_rpc_error,29count_bytes,30get_fee,31)32from test_framework.wallet_util import generate_keypair, WalletUnlock34ERR_NOT_ENOUGH_PRESET_INPUTS = "The preselected coins total amount does not cover the transaction target. " \35"Please allow other inputs to be automatically selected or include more coins manually"37def get_unspent(listunspent, amount):38for utx in listunspent:39if utx['amount'] == amount:40return utx41raise AssertionError('Could not find unspent with amount={}'.format(amount))43class RawTransactionsTest(BitcoinTestFramework):44def set_test_params(self):45self.num_nodes = 446self.extra_args = [[47"-minrelaytxfee=0.00001000",48] for i in range(self.num_nodes)]50self.setup_clean_chain = True51# whitelist peers to speed up tx relay / mempool sync52self.noban_tx_relay = True53self.rpc_timeout = 90 # to prevent timeouts in `test_transaction_too_large`54self.supports_cli = False56def skip_test_if_missing_module(self):57self.skip_if_no_wallet()59def setup_network(self):60self.setup_nodes()62self.connect_nodes(0, 1)63self.connect_nodes(1, 2)64self.connect_nodes(0, 2)65self.connect_nodes(0, 3)67def lock_outputs_type(self, wallet, outputtype):68"""69Only allow UTXOs of the given type70"""71if outputtype in ["legacy", "p2pkh", "pkh"]:72prefixes = ["pkh(", "sh(multi("]73elif outputtype in ["p2sh-segwit", "sh_wpkh"]:74prefixes = ["sh(wpkh(", "sh(wsh("]75elif outputtype in ["bech32", "wpkh"]:76prefixes = ["wpkh(", "wsh("]77else:78assert False, f"Unknown output type {outputtype}"80to_lock = []81for utxo in wallet.listunspent():82if "desc" in utxo:83for prefix in prefixes:84if utxo["desc"].startswith(prefix):85to_lock.append({"txid": utxo["txid"], "vout": utxo["vout"]})86wallet.lockunspent(False, to_lock)88def unlock_utxos(self, wallet):89"""90Unlock all UTXOs except the watchonly one91"""92to_keep = []93if self.watchonly_utxo is not None:94to_keep.append(self.watchonly_utxo)95wallet.lockunspent(True)96wallet.lockunspent(False, to_keep)98def run_test(self):99self.watchonly_utxo = None100self.log.info("Connect nodes, set fees, generate blocks, and sync")101self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee']102# This test is not meant to test fee estimation and we'd like103# to be sure all txs are sent at a consistent desired feerate104self.fee_rate_sats_per_vb = self.min_relay_tx_fee * Decimal(1e8) / 1000106# if the fee's positive delta is higher than this value tests will fail,107# neg. delta always fail the tests.108# The size of the signature of every input may be at most 2 bytes larger109# than a minimum sized signature.111# = 2 bytes * minRelayTxFeePerByte112self.fee_tolerance = 2 * self.min_relay_tx_fee / 1000114self.generate(self.nodes[2], 1)115self.generate(self.nodes[0], 121)117self.test_add_inputs_default_value()118self.test_preset_inputs_selection()119self.test_weight_calculation()120self.test_weight_limits()121self.test_change_position()122self.test_simple()123self.test_simple_two_coins()124self.test_simple_two_outputs()125self.test_change()126self.test_no_change()127self.test_invalid_option()128self.test_invalid_change_address()129self.test_valid_change_address()130self.test_change_type()131self.test_coin_selection()132self.test_two_vin()133self.test_two_vin_two_vout()134self.test_invalid_input()135self.test_fee_p2pkh()136self.test_fee_p2pkh_multi_out()137self.test_fee_p2sh()138self.test_fee_4of5()139self.test_spend_2of2()140self.test_locked_wallet()141self.test_many_inputs_fee()142self.test_many_inputs_send()143self.test_op_return()144self.test_watchonly()145self.test_all_watched_funds()146self.test_option_feerate()147self.test_address_reuse()148self.test_option_subtract_fee_from_outputs()149self.test_subtract_fee_with_presets()150self.test_transaction_too_large()151self.test_include_unsafe()152self.test_external_inputs()153self.test_22670()154self.test_feerate_rounding()155self.test_input_confs_control()156self.test_duplicate_outputs()157self.test_watchonly_cannot_grind_r()158self.test_cannot_cover_fees()160def test_duplicate_outputs(self):161self.log.info("Test deserializing and funding a transaction with duplicate outputs")162self.nodes[1].createwallet("fundtx_duplicate_outputs")163w = self.nodes[1].get_wallet_rpc("fundtx_duplicate_outputs")165addr = w.getnewaddress(address_type="bech32")166self.nodes[0].sendtoaddress(addr, 5, fee_rate=self.fee_rate_sats_per_vb)167self.generate(self.nodes[0], 1)169address = self.nodes[0].getnewaddress("bech32")170tx = CTransaction()171tx.vin = []172tx.vout = [CTxOut(1 * COIN, bytearray(address_to_scriptpubkey(address)))] * 2173tx.nLockTime = 0174tx_hex = tx.serialize().hex()175res = w.fundrawtransaction(tx_hex, add_inputs=True, fee_rate=self.fee_rate_sats_per_vb)176signed_res = w.signrawtransactionwithwallet(res["hex"])177txid = w.sendrawtransaction(signed_res["hex"])178assert self.nodes[1].getrawtransaction(txid)180self.log.info("Test SFFO with duplicate outputs")182res_sffo = w.fundrawtransaction(tx_hex, add_inputs=True, subtractFeeFromOutputs=[0,1], fee_rate=self.fee_rate_sats_per_vb)183signed_res_sffo = w.signrawtransactionwithwallet(res_sffo["hex"])184txid_sffo = w.sendrawtransaction(signed_res_sffo["hex"])185assert self.nodes[1].getrawtransaction(txid_sffo)187def test_change_position(self):188"""Ensure setting changePosition in fundraw with an exact match is handled properly."""189self.log.info("Test fundrawtxn changePosition option")190rawmatch = self.nodes[2].createrawtransaction([], {self.nodes[2].getnewaddress():50})191rawmatch = self.nodes[2].fundrawtransaction(rawmatch, changePosition=1, subtractFeeFromOutputs=[0], fee_rate=self.fee_rate_sats_per_vb)192assert_equal(rawmatch["changepos"], -1)194self.nodes[3].createwallet(wallet_name="wwatch", disable_private_keys=True)195wwatch = self.nodes[3].get_wallet_rpc('wwatch')196watchonly_address = self.nodes[0].getnewaddress()197self.watchonly_amount = Decimal(200)198import_res = wwatch.importdescriptors([{"desc": self.nodes[0].getaddressinfo(watchonly_address)["desc"], "timestamp": "now"}])199assert_equal(import_res[0]["success"], True)200self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0]202# Lock UTXO so nodes[0] doesn't accidentally spend it203self.nodes[0].lockunspent(False, [self.watchonly_utxo])205self.nodes[0].sendtoaddress(self.nodes[3].get_wallet_rpc(self.default_wallet_name).getnewaddress(), self.watchonly_amount / 10, fee_rate=self.fee_rate_sats_per_vb)207self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5, fee_rate=self.fee_rate_sats_per_vb)208self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0, fee_rate=self.fee_rate_sats_per_vb)209self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5.0, fee_rate=self.fee_rate_sats_per_vb)211self.generate(self.nodes[0], 1)213wwatch.unloadwallet()215def test_simple(self):216self.log.info("Test fundrawtxn")217inputs = [ ]218outputs = { self.nodes[0].getnewaddress() : 1.0 }219rawtx = self.nodes[2].createrawtransaction(inputs, outputs)220rawtxfund = self.nodes[2].fundrawtransaction(rawtx, fee_rate=self.fee_rate_sats_per_vb)221dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])222assert len(dec_tx['vin']) > 0 #test that we have enough inputs224def test_simple_two_coins(self):225self.log.info("Test fundrawtxn with 2 coins")226inputs = [ ]227outputs = { self.nodes[0].getnewaddress() : 2.2 }228rawtx = self.nodes[2].createrawtransaction(inputs, outputs)229rawtxfund = self.nodes[2].fundrawtransaction(rawtx, fee_rate=self.fee_rate_sats_per_vb)230dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])231assert len(dec_tx['vin']) > 0 #test if we have enough inputs232assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')234def test_simple_two_outputs(self):235self.log.info("Test fundrawtxn with 2 outputs")237inputs = [ ]238outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 }239rawtx = self.nodes[2].createrawtransaction(inputs, outputs)241rawtxfund = self.nodes[2].fundrawtransaction(rawtx, fee_rate=self.fee_rate_sats_per_vb)242dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])244assert len(dec_tx['vin']) > 0245assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')247def test_change(self):248self.log.info("Test fundrawtxn with a vin > required amount")249utx = get_unspent(self.nodes[2].listunspent(), 5)
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.