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) 2020-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 send RPC command."""7from decimal import Decimal, getcontext8from itertools import product10from test_framework.descriptors import descsum_create11from test_framework.extendedkey import ExtendedPrivateKey12from test_framework.test_framework import BitcoinTestFramework13from test_framework.util import (14assert_not_equal,15assert_equal,16assert_fee_amount,17assert_greater_than,18assert_greater_than_or_equal,19assert_raises_rpc_error,20count_bytes,21JSONRPCException,22)23from test_framework.wallet_util import (24calculate_input_weight,25generate_keypair,26)29class WalletSendTest(BitcoinTestFramework):30def set_test_params(self):31self.num_nodes = 232# whitelist peers to speed up tx relay / mempool sync33self.noban_tx_relay = True34self.supports_cli = False35self.extra_args = [["-datacarriersize=16"]] * self.num_nodes36getcontext().prec = 8 # Satoshi precision for Decimal38def skip_test_if_missing_module(self):39self.skip_if_no_wallet()41def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,42arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,43conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,44inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None, change_type=None,45locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,46expect_error=None, solving_data=None, minconf=None, nonmempool=False):47assert_not_equal((amount is None), (data is None))49from_balance_before = from_wallet.getbalances()["mine"]["trusted"]50if include_unsafe:51from_balance_before += from_wallet.getbalances()["mine"]["untrusted_pending"]53if to_wallet is None:54assert amount is None55else:56to_untrusted_pending_before = to_wallet.getbalances()["mine"]["untrusted_pending"]58if amount:59dest = to_wallet.getnewaddress()60outputs = {dest: amount}61else:62outputs = {"data": data}64# Construct options dictionary65options = {}66if add_to_wallet is not None:67options["add_to_wallet"] = add_to_wallet68else:69if psbt:70add_to_wallet = False71else:72add_to_wallet = from_wallet.getwalletinfo()["private_keys_enabled"] # Default value73if psbt is not None:74options["psbt"] = psbt75if conf_target is not None:76options["conf_target"] = conf_target77if estimate_mode is not None:78options["estimate_mode"] = estimate_mode79if fee_rate is not None:80options["fee_rate"] = fee_rate81if inputs is not None:82options["inputs"] = inputs83if add_inputs is not None:84options["add_inputs"] = add_inputs85if include_unsafe is not None:86options["include_unsafe"] = include_unsafe87if change_address is not None:88options["change_address"] = change_address89if change_position is not None:90options["change_position"] = change_position91if change_type is not None:92options["change_type"] = change_type93if locktime is not None:94options["locktime"] = locktime95if lock_unspents is not None:96options["lock_unspents"] = lock_unspents97if replaceable is None:98replaceable = True # default99else:100options["replaceable"] = replaceable101if subtract_fee_from_outputs is not None:102options["subtract_fee_from_outputs"] = subtract_fee_from_outputs103if solving_data is not None:104options["solving_data"] = solving_data105if minconf is not None:106options["minconf"] = minconf108if len(options.keys()) == 0:109options = None111expect_sign = from_wallet.getwalletinfo()["private_keys_enabled"]112expect_sign = expect_sign and solving_data is None113if inputs is not None:114expect_sign = expect_sign and all(["weight" not in i for i in inputs])116if expect_error is None:117res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)118else:119try:120assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,121outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)122except AssertionError:123# Provide debug info if the test fails124self.log.error("Unexpected successful result:")125self.log.error(arg_conf_target)126self.log.error(arg_estimate_mode)127self.log.error(arg_fee_rate)128self.log.error(options)129res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)130self.log.error(res)131if "txid" in res and add_to_wallet:132self.log.error("Transaction details:")133try:134tx = from_wallet.gettransaction(res["txid"])135self.log.error(tx)136self.log.error("testmempoolaccept (transaction may already be in mempool):")137self.log.error(from_wallet.testmempoolaccept([tx["hex"]]))138except JSONRPCException as exc:139self.log.error(exc)141raise143return145if locktime:146assert_equal(from_wallet.gettransaction(txid=res["txid"], verbose=True)["decoded"]["locktime"], locktime)147return res148else:149if add_to_wallet:150decoded_tx = from_wallet.gettransaction(txid=res["txid"], verbose=True)["decoded"]151# the locktime should be within 100 blocks of the152# block height153assert_greater_than_or_equal(decoded_tx["locktime"], from_wallet.getblockcount() - 100)155if expect_sign:156assert_equal(res["complete"], True)157assert "txid" in res158else:159assert_equal(res["complete"], False)160assert "txid" not in res161assert "psbt" in res163from_balance = from_wallet.getbalances()["mine"]["trusted"]164if include_unsafe:165from_balance += from_wallet.getbalances()["mine"]["untrusted_pending"]167if add_to_wallet:168# Ensure transaction exists in the wallet:169tx = from_wallet.gettransaction(res["txid"])170assert tx171if nonmempool:172assert_raises_rpc_error(-5, "No such mempool transaction", from_wallet.getrawtransaction, res["txid"])173assert from_wallet.getbalances()["mine"]["nonmempool"] < 0174else:175# Ensure transaction exists in the mempool:176tx = from_wallet.getrawtransaction(res["txid"], True)177assert tx178if amount:179if subtract_fee_from_outputs:180assert_equal(from_balance_before - from_balance, amount)181else:182assert_greater_than(from_balance_before - from_balance, amount)183else:184assert next((out for out in tx["vout"] if out["scriptPubKey"]["asm"] == "OP_RETURN 35"), None)185else:186assert_equal(from_balance_before, from_balance)188if to_wallet:189self.sync_mempools()190if add_to_wallet:191if not subtract_fee_from_outputs:192assert_equal(to_wallet.getbalances()["mine"]["untrusted_pending"], to_untrusted_pending_before + Decimal(amount if amount else 0))193else:194assert_equal(to_wallet.getbalances()["mine"]["untrusted_pending"], to_untrusted_pending_before)196return res198def run_test(self):199self.log.info("Setup wallets...")200# w0 is a wallet with coinbase rewards201w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)202# w1 is a regular wallet203self.nodes[1].createwallet(wallet_name="w1")204w1 = self.nodes[1].get_wallet_rpc("w1")205# w2 contains the private keys for w3206self.nodes[1].createwallet(wallet_name="w2", blank=True)207w2 = self.nodes[1].get_wallet_rpc("w2")208extendedkey = ExtendedPrivateKey.generate()209xpriv = extendedkey.to_string()210xpub = extendedkey.pubkey().to_string()211w2.importdescriptors([{212"desc": descsum_create("wpkh(" + xpriv + "/0/0/*)"),213"timestamp": "now",214"range": [0, 100],215"active": True216},{217"desc": descsum_create("wpkh(" + xpriv + "/0/1/*)"),218"timestamp": "now",219"range": [0, 100],220"active": True,221"internal": True222}])224# w3 is a watch-only wallet, based on w2225self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True)226w3 = self.nodes[1].get_wallet_rpc("w3")227# Match the privkeys in w2 for descriptors228res = w3.importdescriptors([{229"desc": descsum_create("wpkh(" + xpub + "/0/0/*)"),230"timestamp": "now",231"range": [0, 100],232"active": True,233},{234"desc": descsum_create("wpkh(" + xpub + "/0/1/*)"),235"timestamp": "now",236"range": [0, 100],237"active": True,238"internal": True,239}])240assert_equal(res, [{"success": True}, {"success": True}])242for _ in range(3):243a2_receive = w2.getnewaddress()244w0.sendtoaddress(a2_receive, 10) # fund w3245self.generate(self.nodes[0], 1)247self.log.info("Send to address...")248self.test_send(from_wallet=w0, to_wallet=w1, amount=1)249self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True)
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.