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.
501assert_greater_than(len(final_input_list), 1)502# Original input is in final set503assert [txin for txin in final_input_list504if txin["txid"] == original_txin["txid"]505and txin["vout"] == original_txin["vout"]]507self.generatetoaddress(rbf_node, 1, rbf_node.getnewaddress())508assert_equal(rbf_node.gettransaction(rbfid)["confirmations"], 1)509self.clear_mempool()512def test_dust_to_fee(self, rbf_node, dest_address):513self.log.info('Test that bumped output that is dust is dropped to fee')514rbfid = spend_one_input(rbf_node, dest_address, change_size=Decimal("0.00030000"), dest_amount=Decimal("0.00064730"))515fulltx = rbf_node.getrawtransaction(rbfid, 1)516# The DER formatting used by Bitcoin to serialize ECDSA signatures means that signatures can have a517# variable size of 70-72 bytes (or possibly even less), with most being 71 or 72 bytes. The signature518# in the witness is divided by 4 for the vsize, so this variance can take the weight across a 4-byte519# boundary. Thus expected transaction size (p2wpkh, 1 input, 2 outputs) is 140-141 vbytes, usually 141.520if not 140 <= fulltx["vsize"] <= 141:521raise AssertionError("Invalid tx vsize of {} (140-141 expected), full tx: {}".format(fulltx["vsize"], fulltx))522# Bump with fee_rate of 250 sat/vB. The leftover change is below the dust523# threshold, so it is dropped and folded into the fee.524# Target fee is 141 vbytes * 0.00250000 BTC / 1000 vbytes = 0.00035250 BTC (20 sat change left),525# or occasionally 140 vbytes * 0.00250000 BTC / 1000 vbytes = 0.00035000 BTC (270 sat change left).526# Either way the sub-dust change is added to the fee, giving 0.00035270 BTC.527bumped_tx = rbf_node.bumpfee(rbfid, fee_rate=250)528full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)529assert_equal(bumped_tx["fee"], Decimal("0.00035270"))530assert_equal(len(fulltx["vout"]), 2)531assert_equal(len(full_bumped_tx["vout"]), 1) # change output is eliminated532assert_equal(full_bumped_tx["vout"][0]['value'], Decimal("0.00064730"))533self.clear_mempool()535def test_maxtxfee_fails(self, rbf_node, dest_address):536self.log.info('Test that bumpfee fails when it hits -maxtxfee')537# size of bumped transaction (p2wpkh, 1 input, 2 outputs): 141 vbytes538# expected bump fee of 141 vbytes * 0.00200000 BTC / 1000 vbytes = 0.00002820 BTC539# which exceeds maxtxfee and is expected to raise540self.restart_node(1, ['-maxtxfee=0.000025'] + self.extra_args[1])541rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)542rbfid = spend_one_input(rbf_node, dest_address)543assert_raises_rpc_error(-4, "Unable to create transaction. Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", rbf_node.bumpfee, rbfid)544self.restart_node(1, self.extra_args[1])545rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)546self.connect_nodes(1, 0)547self.clear_mempool()550def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):551self.log.info('Test that PSBT is returned for bumpfee in watchonly wallets')552priv_rec_desc = descsum_create(f"wpkh([00000001/84'/1'/0']{ExtendedPrivateKey.generate().to_string()}/0/*)")553pub_rec_desc = rbf_node.getdescriptorinfo(priv_rec_desc)["descriptor"]554priv_change_desc = descsum_create(f"wpkh([00000001/84'/1'/0']{ExtendedPrivateKey.generate().to_string()}/1/*)")555pub_change_desc = rbf_node.getdescriptorinfo(priv_change_desc)["descriptor"]556# Create a wallet with private keys that can sign PSBTs557rbf_node.createwallet(wallet_name="signer", disable_private_keys=False, blank=True)558signer = rbf_node.get_wallet_rpc("signer")559assert signer.getwalletinfo()['private_keys_enabled']560reqs = [{561"desc": priv_rec_desc,562"timestamp": 0,563"range": [0,1],564"internal": False,565"keypool": False # Keys can only be imported to the keypool when private keys are disabled566},567{568"desc": priv_change_desc,569"timestamp": 0,570"range": [0, 0],571"internal": True,572"keypool": False573}]574result = signer.importdescriptors(reqs)575assert_equal(result, [{'success': True}, {'success': True}])577# Create another wallet with just the public keys, which creates PSBTs578rbf_node.createwallet(wallet_name="watcher", disable_private_keys=True, blank=True)579watcher = rbf_node.get_wallet_rpc("watcher")580assert not watcher.getwalletinfo()['private_keys_enabled']582reqs = [{583"desc": pub_rec_desc,584"timestamp": 0,585"range": [0, 10],586"internal": False,587"keypool": True,588"watchonly": True,589"active": True,590}, {591"desc": pub_change_desc,592"timestamp": 0,593"range": [0, 10],594"internal": True,595"keypool": True,596"watchonly": True,597"active": True,598}]599result = watcher.importdescriptors(reqs)600assert_equal(result, [{'success': True}, {'success': True}])602funding_address1 = watcher.getnewaddress(address_type='bech32')603funding_address2 = watcher.getnewaddress(address_type='bech32')604peer_node.sendmany("", {funding_address1: 0.001, funding_address2: 0.001})605self.generate(peer_node, 1)607# Create single-input PSBT for transaction to be bumped608# Ensure the payment amount + change can be fully funded using one of the 0.001BTC inputs.609psbt = watcher.walletcreatefundedpsbt([watcher.listunspent()[0]], {dest_address: 0.0005}, 0,610{"fee_rate": 1, "add_inputs": False}, True)['psbt']611psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True)612original_txid = watcher.sendrawtransaction(psbt_signed["hex"])613assert_equal(len(watcher.decodepsbt(psbt)["inputs"]), 1)615# bumpfee can't be used on watchonly wallets616assert_raises_rpc_error(-4, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.", watcher.bumpfee, original_txid)618# Bump fee, obnoxiously high to add additional watchonly input619bumped_psbt = watcher.psbtbumpfee(original_txid, fee_rate=HIGH)620assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["inputs"]), 1)621assert "txid" not in bumped_psbt622assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"])623assert not watcher.finalizepsbt(bumped_psbt["psbt"])["complete"]625# Sign bumped transaction626bumped_psbt_signed = signer.walletprocesspsbt(psbt=bumped_psbt["psbt"], sign=True, sighashtype="ALL", bip32derivs=True)627assert bumped_psbt_signed["complete"]629# Broadcast bumped transaction630bumped_txid = watcher.sendrawtransaction(bumped_psbt_signed["hex"])631assert bumped_txid in rbf_node.getrawmempool()632assert original_txid not in rbf_node.getrawmempool()634rbf_node.unloadwallet("watcher")635rbf_node.unloadwallet("signer")636self.clear_mempool()639def test_rebumping(self, rbf_node, dest_address):640self.log.info('Test that re-bumping the original tx fails, but bumping successor works')641rbfid = spend_one_input(rbf_node, dest_address)642bumped = rbf_node.bumpfee(rbfid, fee_rate=ECONOMICAL)643assert_raises_rpc_error(-4, f"Cannot bump transaction {rbfid} which was already bumped by transaction {bumped['txid']}",644rbf_node.bumpfee, rbfid, fee_rate=NORMAL)645rbf_node.bumpfee(bumped["txid"], fee_rate=NORMAL)646self.clear_mempool()649def test_rebumping_not_replaceable(self, rbf_node, dest_address):650self.log.info("Test that re-bumping non-replaceable passes")651rbfid = spend_one_input(rbf_node, dest_address)653def check_sequence(tx, seq_in):654tx = rbf_node.getrawtransaction(tx["txid"])655tx = rbf_node.decoderawtransaction(tx)656seq = [i["sequence"] for i in tx["vin"]]657assert_equal(seq, [seq_in])659bumped = rbf_node.bumpfee(rbfid, fee_rate=ECONOMICAL, replaceable=False)660check_sequence(bumped, MAX_SEQUENCE_NONFINAL)661bumped = rbf_node.bumpfee(bumped["txid"], {"fee_rate": NORMAL})662check_sequence(bumped, MAX_BIP125_RBF_SEQUENCE)664self.clear_mempool()667def test_bumpfee_already_spent(self, rbf_node, dest_address):668self.log.info('Test that bumping tx with already spent coin fails')669txid = spend_one_input(rbf_node, dest_address)670self.generate(rbf_node, 1) # spend coin simply by mining block with tx671spent_input = rbf_node.gettransaction(txid=txid, verbose=True)['decoded']['vin'][0]672assert_raises_rpc_error(-1, f"{spent_input['txid']}:{spent_input['vout']} is already spent",673rbf_node.bumpfee, txid, fee_rate=NORMAL)676def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address):677self.log.info('Test that unconfirmed outputs from bumped txns are not spendable')678rbfid = spend_one_input(rbf_node, rbf_node_address)679rbftx = rbf_node.gettransaction(rbfid)["hex"]680assert rbfid in rbf_node.getrawmempool()681bumpid = rbf_node.bumpfee(rbfid)["txid"]682assert bumpid in rbf_node.getrawmempool()683assert rbfid not in rbf_node.getrawmempool()685# check that outputs from the bump transaction are not spendable686# due to the replaces_txid check in CWallet::AvailableCoins687assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == bumpid], [])689# submit a block with the rbf tx to clear the bump tx out of the mempool,690# then invalidate the block so the rbf tx will be put back in the mempool.691# This makes it possible to check whether the rbf tx outputs are692# spendable before the rbf tx is confirmed.693block = self.generateblock(rbf_node, output="raw(51)", transactions=[rbftx])694# Can not abandon conflicted tx695assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: rbf_node.abandontransaction(txid=bumpid))696rbf_node.invalidateblock(block["hash"])697# Call abandon to make sure the wallet doesn't attempt to resubmit698# the bump tx and hope the wallet does not rebroadcast before we call.699rbf_node.abandontransaction(bumpid)701tx_bump_abandoned = rbf_node.gettransaction(bumpid)702for tx in tx_bump_abandoned['details']:703assert_equal(tx['abandoned'], True)705assert bumpid not in rbf_node.getrawmempool()706assert rbfid in rbf_node.getrawmempool()708# check that outputs from the rbf tx are not spendable before the709# transaction is confirmed, due to the replaced_by_txid check in710# CWallet::AvailableCoins711assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == rbfid], [])713# check that the main output from the rbf tx is spendable after confirmed714self.generate(rbf_node, 1, sync_fun=self.no_op)715assert_equal(716sum(1 for t in rbf_node.listunspent(minconf=0, include_unsafe=False)717if t["txid"] == rbfid and t["address"] == rbf_node_address and t["spendable"]), 1)718self.clear_mempool()721def test_bumpfee_metadata(self, rbf_node, dest_address):722self.log.info('Test that bumped txn metadata persists to new txn record')723assert rbf_node.getbalance() < 49724self.generatetoaddress(rbf_node, 101, rbf_node.getnewaddress())725rbfid = rbf_node.sendtoaddress(dest_address, 49, "comment value", "to value")726bumped_tx = rbf_node.bumpfee(rbfid)727bumped_wtx = rbf_node.gettransaction(bumped_tx["txid"])728assert_equal(bumped_wtx["comment"], "comment value")729assert_equal(bumped_wtx["to"], "to value")730self.clear_mempool()733def test_locked_wallet_fails(self, rbf_node, dest_address):734self.log.info('Test that locked wallet cannot bump txn')735rbfid = spend_one_input(rbf_node, dest_address)736rbf_node.walletlock()737assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.",738rbf_node.bumpfee, rbfid)739rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)740self.clear_mempool()743def test_change_script_match(self, rbf_node, dest_address):744self.log.info('Test that the same change addresses is used for the replacement transaction when possible')746# Check that there is only one change output747rbfid = spend_one_input(rbf_node, dest_address)748change_addresses = get_change_address(rbfid, rbf_node)749assert_equal(len(change_addresses), 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.