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.
501# Create a replacement of child_1. It does not conflict with child_2.502child_1_conflict = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=ancestor_tx["new_utxos"][0], fee_rate=Decimal("0.01"))504# Ensure child_1 and child_1_conflict are different transactions505assert_not_equal(child_1_conflict["txid"], child_1["txid"])506self.check_mempool([ancestor_tx["txid"], child_1_conflict["txid"], child_2["txid"]])507assert_equal(node.getmempoolentry(ancestor_tx["txid"])["descendantcount"], 3)509@cleanup(extra_args=None)510def test_truc_sibling_eviction(self):511self.log.info("Test sibling eviction for TRUC")512node = self.nodes[0]513tx_v3_parent = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2, version=3)514# This is the sibling to replace515tx_v3_child_1 = self.wallet.send_self_transfer(516from_node=node, utxo_to_spend=tx_v3_parent["new_utxos"][0], fee_rate=DEFAULT_FEE * 2, version=3517)518assert tx_v3_child_1["txid"] in node.getrawmempool()520self.log.info("Test tx must be higher feerate than sibling to evict it")521tx_v3_child_2_rule6 = self.wallet.create_self_transfer(522utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE, version=3523)524rule6_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule6['txid']}"525assert_raises_rpc_error(-26, rule6_str, node.sendrawtransaction, tx_v3_child_2_rule6["hex"])526self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])528self.log.info("Test tx must meet absolute fee rules to evict sibling")529tx_v3_child_2_rule4 = self.wallet.create_self_transfer(530utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=2 * DEFAULT_FEE + Decimal("0.00000001"), version=3531)532rule4_str = f"insufficient fee (including sibling eviction), rejecting replacement {tx_v3_child_2_rule4['txid']}, not enough additional fees to relay"533assert_raises_rpc_error(-26, rule4_str, node.sendrawtransaction, tx_v3_child_2_rule4["hex"])534self.check_mempool([tx_v3_parent['txid'], tx_v3_child_1['txid']])536self.log.info("Test tx cannot cause more than 100 evictions including RBF and sibling eviction")537# First add 4 groups of 25 transactions.538utxos_for_conflict = []539txids_v2_100 = []540for _ in range(4):541confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)542utxos_for_conflict.append(confirmed_utxo)543# 25 is within descendant limits544chain_length = int(MAX_REPLACEMENT_CANDIDATES / 4)545chain = self.wallet.create_self_transfer_chain(chain_length=chain_length, utxo_to_spend=confirmed_utxo)546for item in chain:547txids_v2_100.append(item["txid"])548node.sendrawtransaction(item["hex"])549self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])551# Replacing 100 transactions is fine552tx_v3_replacement_only = self.wallet.create_self_transfer_multi(utxos_to_spend=utxos_for_conflict, fee_per_output=4000000)553# Override maxfeerate - it costs a lot to replace these 100 transactions.554assert node.testmempoolaccept([tx_v3_replacement_only["hex"]], maxfeerate=0)[0]["allowed"]555self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_1["txid"]])557self.log.info("Test sibling eviction is successful if it meets all RBF rules")558tx_v3_child_2 = self.wallet.create_self_transfer(559utxo_to_spend=tx_v3_parent["new_utxos"][1], fee_rate=DEFAULT_FEE*10, version=3560)561node.sendrawtransaction(tx_v3_child_2["hex"])562self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_2["txid"]])564self.log.info("Test that it's possible to do a sibling eviction and RBF at the same time")565utxo_unrelated_conflict = self.wallet.get_utxo(confirmed_only=True)566tx_unrelated_replacee = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=utxo_unrelated_conflict)567assert tx_unrelated_replacee["txid"] in node.getrawmempool()569fee_to_beat = max(int(tx_v3_child_2["fee"] * COIN), int(tx_unrelated_replacee["fee"]*COIN))571tx_v3_child_3 = self.wallet.create_self_transfer_multi(572utxos_to_spend=[tx_v3_parent["new_utxos"][0], utxo_unrelated_conflict], fee_per_output=fee_to_beat*4, version=3573)574node.sendrawtransaction(tx_v3_child_3["hex"])575self.check_mempool(txids_v2_100 + [tx_v3_parent["txid"], tx_v3_child_3["txid"]])577@cleanup(extra_args=None)578def test_reorg_sibling_eviction_1p2c(self):579node = self.nodes[0]580self.log.info("Test that sibling eviction is not allowed when multiple siblings exist")582# Prep for fork583fork_blocks = create_empty_fork(node)584tx_with_multi_children = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=3, version=3, confirmed_only=True)585self.check_mempool([tx_with_multi_children["txid"]])587self.generate(node, 1)588self.check_mempool([])590tx_with_sibling1 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][0])591tx_with_sibling2 = self.wallet.send_self_transfer(from_node=node, version=3, utxo_to_spend=tx_with_multi_children["new_utxos"][1])592self.check_mempool([tx_with_sibling1["txid"], tx_with_sibling2["txid"]])594# Create a reorg, bringing tx_with_multi_children back into the mempool with a descendant count of 3.595self.trigger_reorg(fork_blocks)596self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling1["txid"], tx_with_sibling2["txid"]])597assert_equal(node.getmempoolentry(tx_with_multi_children["txid"])["descendantcount"], 3)599# Sibling eviction is not allowed because there are two siblings600tx_with_sibling3 = self.wallet.create_self_transfer(601version=3,602utxo_to_spend=tx_with_multi_children["new_utxos"][2],603fee_rate=DEFAULT_FEE*50604)605expected_error_2siblings = f"TRUC-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"606assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])608# However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2609tx_with_sibling3_rbf = self.wallet.send_self_transfer(610from_node=node,611version=3,612utxo_to_spend=tx_with_multi_children["new_utxos"][0],613fee_rate=DEFAULT_FEE*50614)615self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])617@cleanup(extra_args=None)618def test_minrelay_in_package_combos(self):619node = self.nodes[0]620self.log.info("Test that all transaction versions can be under minrelaytxfee for various settings...")622for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):623self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")624setting_decimal = minrelay_setting / Decimal(COIN)625self.restart_node(0, extra_args=[f"-minrelaytxfee={setting_decimal:.8f}", "-persistmempool=0"])626minrelayfeerate = node.getmempoolinfo()["minrelaytxfee"]627high_feerate = minrelayfeerate * 50629tx_v3_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=3)630tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=high_feerate, version=3)631total_v3_fee = tx_v3_child["fee"] + tx_v3_0fee_parent["fee"]632total_v3_size = tx_v3_child["tx"].get_vsize() + tx_v3_0fee_parent["tx"].get_vsize()633assert_greater_than_or_equal(total_v3_fee, get_fee(total_v3_size, minrelayfeerate))634if minrelayfeerate > 0:635assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)636# Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low637assert_greater_than(total_v3_fee, 0)638# Also create a version where the child is at minrelaytxfee639tx_v3_child_minrelay = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=minrelayfeerate, version=3)640result_truc_minrelay = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child_minrelay["hex"]])641assert_equal(result_truc_minrelay["package_msg"], "transaction failed")643tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2)644tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2)645total_v2_fee = tx_v2_child["fee"] + tx_v2_0fee_parent["fee"]646total_v2_size = tx_v2_child["tx"].get_vsize() + tx_v2_0fee_parent["tx"].get_vsize()647assert_greater_than_or_equal(total_v2_fee, get_fee(total_v2_size, minrelayfeerate))648if minrelayfeerate > 0:649assert_greater_than(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)650# Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low651assert_greater_than(total_v2_fee, 0)653result_truc = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child["hex"]], maxfeerate=0)654assert_equal(result_truc["package_msg"], "success")656result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)657assert_equal(result_non_truc["package_msg"], "success")658self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])661def run_test(self):662self.log.info("Generate blocks to create UTXOs")663node = self.nodes[0]664self.wallet = MiniWallet(node)665self.generate(self.wallet, 200)666self.test_truc_max_vsize()667self.test_truc_acceptance()668self.test_truc_replacement()669self.test_truc_reorg()670self.test_nondefault_package_limits()671self.test_truc_ancestors_package()672self.test_truc_ancestors_package_and_mempool()673self.test_sibling_eviction_package()674self.test_truc_package_inheritance()675self.test_truc_in_testmempoolaccept()676self.test_reorg_2child_rbf()677self.test_truc_sibling_eviction()678self.test_reorg_sibling_eviction_1p2c()679self.test_minrelay_in_package_combos()682if __name__ == "__main__":683MempoolTRUC(__file__).main()
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.