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) 2015-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 prioritisetransaction mining RPC."""7from decimal import Decimal8import time10from test_framework.blocktools import NORMAL_GBT_REQUEST_PARAMS11from test_framework.messages import (12COIN,13MAX_BLOCK_WEIGHT,14)15from test_framework.test_framework import BitcoinTestFramework16from test_framework.util import (17assert_not_equal,18assert_equal,19assert_raises_rpc_error,20create_lots_of_big_transactions,21gen_return_txouts,22)23from test_framework.wallet import MiniWallet26class PrioritiseTransactionTest(BitcoinTestFramework):27def set_test_params(self):28self.num_nodes = 129self.extra_args = [[30"-printpriority=1",31]] * self.num_nodes32self.supports_cli = False34def clear_prioritisation(self, node):35for txid, info in node.getprioritisedtransactions().items():36delta = info["fee_delta"]37node.prioritisetransaction(txid, 0, -delta)38assert_equal(node.getprioritisedtransactions(), {})40def test_large_fee_bump(self):41self.log.info("Test that a large fee delta is honoured")42tx = self.wallet.create_self_transfer()43txid = tx["txid"]44fee_delta = int(86 * COIN) # large enough to not fit into (u)int32_t45self.nodes[0].prioritisetransaction(txid=txid, fee_delta=fee_delta)46assert_equal(47self.nodes[0].getprioritisedtransactions(),48{49txid: {50"fee_delta": fee_delta,51"in_mempool": False,52},53},54)55self.nodes[0].sendrawtransaction(tx["hex"])56expected_modified_fee = tx["fee"] + Decimal(fee_delta) / COIN57assert_equal(58self.nodes[0].getprioritisedtransactions(),59{60txid: {61"fee_delta": fee_delta,62"in_mempool": True,63"modified_fee": int(expected_modified_fee * COIN),64},65},66)67# This transaction forms its own chunk.68mempool_entry = self.nodes[0].getrawmempool(verbose=True)[txid]69assert_equal(mempool_entry["fees"]["base"], tx["fee"])70assert_equal(mempool_entry["fees"]["modified"], expected_modified_fee)71assert_equal(mempool_entry["fees"]["ancestor"], expected_modified_fee)72assert_equal(mempool_entry["fees"]["descendant"], expected_modified_fee)73assert_equal(mempool_entry["fees"]["chunk"], expected_modified_fee)74assert_equal(mempool_entry["chunkweight"], mempool_entry["weight"])75append_chunk_info = self.nodes[0].getmempoolcluster(txid)76assert_equal(77append_chunk_info,78{79"clusterweight": mempool_entry["weight"],80"txcount": 1,81"chunks": [{82"chunkfee": expected_modified_fee,83"chunkweight": mempool_entry["weight"],84"txs": [txid],85}],86},87)88self.generate(self.nodes[0], 1)89assert_equal(self.nodes[0].getprioritisedtransactions(), {})91def test_replacement(self):92self.log.info("Test tx prioritisation stays after a tx is replaced")93conflicting_input = self.wallet.get_utxo()94tx_replacee = self.wallet.create_self_transfer(utxo_to_spend=conflicting_input, fee_rate=Decimal("0.0001"))95tx_replacement = self.wallet.create_self_transfer(utxo_to_spend=conflicting_input, fee_rate=Decimal("0.005"))96# Add 1 satoshi fee delta to replacee97self.nodes[0].prioritisetransaction(tx_replacee["txid"], 0, 100)98assert_equal(self.nodes[0].getprioritisedtransactions(), { tx_replacee["txid"] : { "fee_delta" : 100, "in_mempool" : False}})99self.nodes[0].sendrawtransaction(tx_replacee["hex"])100assert_equal(self.nodes[0].getprioritisedtransactions(), { tx_replacee["txid"] : { "fee_delta" : 100, "in_mempool" : True, "modified_fee": int(tx_replacee["fee"] * COIN + 100)}})101self.nodes[0].sendrawtransaction(tx_replacement["hex"])102assert tx_replacee["txid"] not in self.nodes[0].getrawmempool()103assert_equal(self.nodes[0].getprioritisedtransactions(), { tx_replacee["txid"] : { "fee_delta" : 100, "in_mempool" : False}})105# PrioritiseTransaction is additive106self.nodes[0].prioritisetransaction(tx_replacee["txid"], 0, COIN)107self.nodes[0].sendrawtransaction(tx_replacee["hex"])108assert_equal(self.nodes[0].getprioritisedtransactions(), { tx_replacee["txid"] : { "fee_delta" : COIN + 100, "in_mempool" : True, "modified_fee": int(tx_replacee["fee"] * COIN + COIN + 100)}})109self.generate(self.nodes[0], 1)110assert_equal(self.nodes[0].getprioritisedtransactions(), {})112def test_diamond(self):113self.log.info("Test diamond-shape package with priority")114mock_time = int(time.time())115self.nodes[0].setmocktime(mock_time)117# tx_a118# / \119# / \120# tx_b tx_c121# \ /122# \ /123# tx_d125tx_o_a = self.wallet.send_self_transfer_multi(126from_node=self.nodes[0],127num_outputs=2,128)129txid_a = tx_o_a["txid"]131tx_o_b, tx_o_c = [self.wallet.send_self_transfer(132from_node=self.nodes[0],133utxo_to_spend=u,134) for u in tx_o_a["new_utxos"]]135txid_b = tx_o_b["txid"]136txid_c = tx_o_c["txid"]138tx_o_d = self.wallet.send_self_transfer_multi(139from_node=self.nodes[0],140utxos_to_spend=[141self.wallet.get_utxo(txid=txid_b),142self.wallet.get_utxo(txid=txid_c),143],144)145txid_d = tx_o_d["txid"]147self.log.info("Test priority while txs are in mempool")148raw_before = self.nodes[0].getrawmempool(verbose=True)149fee_delta_b = Decimal(9999) / COIN150fee_delta_c_1 = Decimal(-1234) / COIN151fee_delta_c_2 = Decimal(8888) / COIN152self.nodes[0].prioritisetransaction(txid=txid_b, fee_delta=int(fee_delta_b * COIN))153self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN))154self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN))155raw_before[txid_a]["fees"]["descendant"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2156# We expect tx_a to have a chunk fee that includes tx_b and tx_c.157raw_before[txid_a]["fees"]["chunk"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2158raw_before[txid_b]["fees"]["modified"] += fee_delta_b159raw_before[txid_b]["fees"]["ancestor"] += fee_delta_b160raw_before[txid_b]["fees"]["descendant"] += fee_delta_b161# We also expect tx_b and tx_c to have their chunk fees modified too,162# since they chunk together.163raw_before[txid_b]["fees"]["chunk"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2164raw_before[txid_c]["fees"]["modified"] += fee_delta_c_1 + fee_delta_c_2165raw_before[txid_c]["fees"]["ancestor"] += fee_delta_c_1 + fee_delta_c_2166raw_before[txid_c]["fees"]["descendant"] += fee_delta_c_1 + fee_delta_c_2167raw_before[txid_c]["fees"]["chunk"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2168raw_before[txid_d]["fees"]["ancestor"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2169raw_after = self.nodes[0].getrawmempool(verbose=True)170# Don't bother comparing cluster ids, which are not meant to be stable.171assert_equal(raw_before[txid_a], raw_after[txid_a])172assert_equal(raw_before, raw_after)173assert_equal(self.nodes[0].getprioritisedtransactions(), {txid_b: {"fee_delta" : fee_delta_b*COIN, "in_mempool" : True, "modified_fee": int(fee_delta_b*COIN + COIN * tx_o_b["fee"])}, txid_c: {"fee_delta" : (fee_delta_c_1 + fee_delta_c_2)*COIN, "in_mempool" : True, "modified_fee": int((fee_delta_c_1 + fee_delta_c_2 ) * COIN + COIN * tx_o_c["fee"])}})174# Clear prioritisation, otherwise the transactions' fee deltas are persisted to mempool.dat and loaded again when the node175# is restarted at the end of this subtest. Deltas are removed when a transaction is mined, but only at that time. We do176# not check whether mapDeltas transactions were mined when loading from mempool.dat.177self.clear_prioritisation(node=self.nodes[0])179self.log.info("Test priority while txs are not in mempool")180self.restart_node(0, extra_args=["-nopersistmempool"])181self.nodes[0].setmocktime(mock_time)182assert_equal(self.nodes[0].getmempoolinfo()["size"], 0)183self.nodes[0].prioritisetransaction(txid=txid_b, fee_delta=int(fee_delta_b * COIN))184self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN))185self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN))186assert_equal(self.nodes[0].getprioritisedtransactions(), {txid_b: {"fee_delta" : fee_delta_b*COIN, "in_mempool" : False}, txid_c: {"fee_delta" : (fee_delta_c_1 + fee_delta_c_2)*COIN, "in_mempool" : False}})187for t in [tx_o_a["hex"], tx_o_b["hex"], tx_o_c["hex"], tx_o_d["hex"]]:188self.nodes[0].sendrawtransaction(t)189raw_after = self.nodes[0].getrawmempool(verbose=True)190assert_equal(raw_before[txid_a], raw_after[txid_a])191assert_equal(raw_before, raw_after)192assert_equal(self.nodes[0].getprioritisedtransactions(), {txid_b: {"fee_delta" : fee_delta_b*COIN, "in_mempool" : True, "modified_fee": int(fee_delta_b*COIN + COIN * tx_o_b["fee"])}, txid_c: {"fee_delta" : (fee_delta_c_1 + fee_delta_c_2)*COIN, "in_mempool" : True, "modified_fee": int((fee_delta_c_1 + fee_delta_c_2 ) * COIN + COIN * tx_o_c["fee"])}})194# Clear mempool195self.generate(self.nodes[0], 1)196# Prioritisation for transactions is automatically deleted after they are mined.197assert_equal(self.nodes[0].getprioritisedtransactions(), {})199# Use default extra_args200self.restart_node(0)201assert_equal(self.nodes[0].getprioritisedtransactions(), {})203def run_test(self):204self.wallet = MiniWallet(self.nodes[0])206# Test `prioritisetransaction` required parameters207assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction)208assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '')209assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '', 0)211# Test `prioritisetransaction` invalid extra parameters212assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '', 0, 0, 0)214# Test `getprioritisedtransactions` invalid parameters215assert_raises_rpc_error(-1, "getprioritisedtransactions",216self.nodes[0].getprioritisedtransactions, True)218# Test `prioritisetransaction` invalid `txid`219assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].prioritisetransaction, txid='foo', fee_delta=0)220assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'Zd1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000')", self.nodes[0].prioritisetransaction, txid='Zd1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000', fee_delta=0)222# Test `prioritisetransaction` invalid `dummy`223txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000'224assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0)225assert_raises_rpc_error(-8, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.", self.nodes[0].prioritisetransaction, txid, 1, 0)227# Test `prioritisetransaction` invalid `fee_delta`228assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')230self.test_large_fee_bump()231self.test_replacement()232self.test_diamond()234self.txouts = gen_return_txouts()235self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']237utxo_count = 90238utxos = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=utxo_count)['new_utxos']239self.generate(self.wallet, 1)240assert_equal(len(self.nodes[0].getrawmempool()), 0)242base_fee = self.relayfee*100 # our transactions are smaller than 100kb243txids = []245# Create 3 batches of transactions at 3 different fee rate levels246range_size = utxo_count // 3247for i in range(3):248txids.append([])249start_range = i * range_size250end_range = start_range + range_size
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.