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.
251const CAmount normal_fee{CENT/10};253// low feerate parent with normal feerate child254const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0], m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});255TryAddToMempool(pool, entry.Fee(low_fee).FromTx(tx1));256const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});257TryAddToMempool(pool, entry.Fee(normal_fee).FromTx(tx2));259const auto entry1 = pool.GetIter(tx1->GetHash()).value();260const auto tx1_fee = entry1->GetModifiedFee();261const auto entry2 = pool.GetIter(tx2->GetHash()).value();262const auto tx2_fee = entry2->GetModifiedFee();264// conflicting transactions265const auto tx1_conflict = make_tx(/*inputs=*/ {m_coinbase_txns[0], m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});266const auto tx3 = make_tx(/*inputs=*/ {tx1_conflict}, /*output_values=*/ {995 * CENT});267auto entry3 = entry.FromTx(tx3);269// Now test ImprovesFeerateDiagram with various levels of "package rbf" feerates271// It doesn't improve itself272auto changeset = pool.GetChangeSet();273changeset->StageRemoval(entry1);274changeset->StageRemoval(entry2);275changeset->StageAddition(tx1_conflict, tx1_fee, 0, 1, 0, false, 4, LockPoints());276changeset->StageAddition(tx3, tx2_fee, 0, 1, 0, false, 4, LockPoints());277const auto res1 = ImprovesFeerateDiagram(*changeset);278BOOST_CHECK(res1.has_value());279BOOST_CHECK(res1.value().first == DiagramCheckError::FAILURE);280BOOST_CHECK(res1.value().second == "insufficient feerate: does not improve feerate diagram");282// With one more satoshi it does283changeset.reset();284changeset = pool.GetChangeSet();285changeset->StageRemoval(entry1);286changeset->StageRemoval(entry2);287changeset->StageAddition(tx1_conflict, tx1_fee+1, 0, 1, 0, false, 4, LockPoints());288changeset->StageAddition(tx3, tx2_fee, 0, 1, 0, false, 4, LockPoints());289BOOST_CHECK(ImprovesFeerateDiagram(*changeset) == std::nullopt);291changeset.reset();292// With prioritisation of in-mempool conflicts, it affects the results of the comparison using the same args as just above293pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/1);294changeset = pool.GetChangeSet();295changeset->StageRemoval(entry1);296changeset->StageRemoval(entry2);297changeset->StageAddition(tx1_conflict, tx1_fee+1, 0, 1, 0, false, 4, LockPoints());298changeset->StageAddition(tx3, tx2_fee, 0, 1, 0, false, 4, LockPoints());299const auto res2 = ImprovesFeerateDiagram(*changeset);300BOOST_CHECK(res2.has_value());301BOOST_CHECK(res2.value().first == DiagramCheckError::FAILURE);302BOOST_CHECK(res2.value().second == "insufficient feerate: does not improve feerate diagram");303changeset.reset();305pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/-1);307// With fewer vbytes it does308CMutableTransaction tx4{entry3.GetTx()};309tx4.vin[0].scriptWitness = CScriptWitness(); // Clear out the witness, to reduce size310auto entry4 = entry.FromTx(MakeTransactionRef(tx4));311changeset = pool.GetChangeSet();312changeset->StageRemoval(entry1);313changeset->StageRemoval(entry2);314changeset->StageAddition(tx1_conflict, tx1_fee, 0, 1, 0, false, 4, LockPoints());315changeset->StageAddition(entry4.GetSharedTx(), tx2_fee, 0, 1, 0, false, 4, LockPoints());316BOOST_CHECK(ImprovesFeerateDiagram(*changeset) == std::nullopt);317changeset.reset();319// Adding a grandchild makes the cluster size 3, which is also calculable320const auto tx5 = make_tx(/*inputs=*/ {tx2}, /*output_values=*/ {995 * CENT});321TryAddToMempool(pool, entry.Fee(normal_fee).FromTx(tx5));322const auto entry5 = pool.GetIter(tx5->GetHash()).value();324changeset = pool.GetChangeSet();325changeset->StageRemoval(entry1);326changeset->StageRemoval(entry2);327changeset->StageRemoval(entry5);328changeset->StageAddition(tx1_conflict, tx1_fee, 0, 1, 0, false, 4, LockPoints());329changeset->StageAddition(entry4.GetSharedTx(), tx2_fee + entry5->GetModifiedFee() + 1, 0, 1, 0, false, 4, LockPoints());330const auto res3 = ImprovesFeerateDiagram(*changeset);331BOOST_CHECK(res3 == std::nullopt);332}334BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)335{336CTxMemPool& pool = *Assert(m_node.mempool);337LOCK2(::cs_main, pool.cs);338TestMemPoolEntryHelper entry;340const CAmount low_fee{CENT/100};341const CAmount high_fee{CENT};343// low -> high -> medium fee transactions that would result in two chunks together since they344// are all same size345const auto low_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});346TryAddToMempool(pool, entry.Fee(low_fee).FromTx(low_tx));348const auto entry_low = pool.GetIter(low_tx->GetHash()).value();349const auto low_size = entry_low->GetAdjustedWeight();351const auto replacement_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {9 * COIN});352auto entry_replacement = entry.FromTx(replacement_tx);354// Replacement of size 1355{356auto changeset = pool.GetChangeSet();357changeset->StageRemoval(entry_low);358changeset->StageAddition(replacement_tx, 0, 0, 1, 0, false, 4, LockPoints());359const auto replace_one{changeset->CalculateChunksForRBF()};360BOOST_CHECK(replace_one.has_value());361std::vector<FeeFrac> expected_old_chunks{{low_fee, low_size}};362BOOST_CHECK(replace_one->first == expected_old_chunks);363std::vector<FeeFrac> expected_new_chunks{{0, entry_replacement.GetAdjustedWeight()}};364BOOST_CHECK(replace_one->second == expected_new_chunks);365}367// Non-zero replacement fee/size368{369auto changeset = pool.GetChangeSet();370changeset->StageRemoval(entry_low);371changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());372const auto replace_one_fee{changeset->CalculateChunksForRBF()};373BOOST_CHECK(replace_one_fee.has_value());374std::vector<FeeFrac> expected_old_diagram{{low_fee, low_size}};375BOOST_CHECK(replace_one_fee->first == expected_old_diagram);376std::vector<FeeFrac> expected_new_diagram{{high_fee, entry_replacement.GetAdjustedWeight()}};377BOOST_CHECK(replace_one_fee->second == expected_new_diagram);378}380// Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF381const auto high_tx = make_tx(/*inputs=*/ {low_tx}, /*output_values=*/ {995 * CENT});382TryAddToMempool(pool, entry.Fee(high_fee).FromTx(high_tx));383const auto entry_high = pool.GetIter(high_tx->GetHash()).value();384const auto high_size = entry_high->GetAdjustedWeight();386{387auto changeset = pool.GetChangeSet();388changeset->StageRemoval(entry_low);389changeset->StageRemoval(entry_high);390changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());391const auto replace_single_chunk{changeset->CalculateChunksForRBF()};392BOOST_CHECK(replace_single_chunk.has_value());393std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};394BOOST_CHECK(replace_single_chunk->first == expected_old_chunks);395std::vector<FeeFrac> expected_new_chunks{{high_fee, entry_replacement.GetAdjustedWeight()}};396BOOST_CHECK(replace_single_chunk->second == expected_new_chunks);397}399// Conflict with the 2nd tx, resulting in new diagram with three entries400{401auto changeset = pool.GetChangeSet();402changeset->StageRemoval(entry_high);403changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());404const auto replace_cpfp_child{changeset->CalculateChunksForRBF()};405BOOST_CHECK(replace_cpfp_child.has_value());406std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};407BOOST_CHECK(replace_cpfp_child->first == expected_old_chunks);408std::vector<FeeFrac> expected_new_chunks{{high_fee, entry_replacement.GetAdjustedWeight()}, {low_fee, low_size}};409BOOST_CHECK(replace_cpfp_child->second == expected_new_chunks);410}412// Make a size 2 cluster that is itself two chunks; evict both txns413const auto high_tx_2 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});414TryAddToMempool(pool, entry.Fee(high_fee).FromTx(high_tx_2));415const auto entry_high_2 = pool.GetIter(high_tx_2->GetHash()).value();416const auto high_size_2 = entry_high_2->GetAdjustedWeight();418const auto low_tx_2 = make_tx(/*inputs=*/ {high_tx_2}, /*output_values=*/ {9 * COIN});419TryAddToMempool(pool, entry.Fee(low_fee).FromTx(low_tx_2));420const auto entry_low_2 = pool.GetIter(low_tx_2->GetHash()).value();421const auto low_size_2 = entry_low_2->GetAdjustedWeight();423{424auto changeset = pool.GetChangeSet();425changeset->StageRemoval(entry_high_2);426changeset->StageRemoval(entry_low_2);427changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());428const auto replace_two_chunks_single_cluster{changeset->CalculateChunksForRBF()};429BOOST_CHECK(replace_two_chunks_single_cluster.has_value());430std::vector<FeeFrac> expected_old_chunks{{high_fee, high_size_2}, {low_fee, low_size_2}};431BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_chunks);432std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size_2}};433BOOST_CHECK(replace_two_chunks_single_cluster->second == expected_new_chunks);434}436// You can have more than two direct conflicts437const auto conflict_1 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});438TryAddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_1));439const auto conflict_1_entry = pool.GetIter(conflict_1->GetHash()).value();441const auto conflict_2 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {10 * COIN});442TryAddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_2));443const auto conflict_2_entry = pool.GetIter(conflict_2->GetHash()).value();445const auto conflict_3 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {10 * COIN});446TryAddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_3));447const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();449{450auto changeset = pool.GetChangeSet();451changeset->StageRemoval(conflict_1_entry);452changeset->StageRemoval(conflict_2_entry);453changeset->StageRemoval(conflict_3_entry);454changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());455const auto replace_multiple_clusters{changeset->CalculateChunksForRBF()};456BOOST_CHECK(replace_multiple_clusters.has_value());457BOOST_CHECK(replace_multiple_clusters->first.size() == 3);458BOOST_CHECK(replace_multiple_clusters->second.size() == 1);459}461// Add a child transaction to conflict_1 and make it cluster size 2, two chunks due to same feerate462const auto conflict_1_child = make_tx(/*inputs=*/{conflict_1}, /*output_values=*/ {995 * CENT});463TryAddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_1_child));464const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();466{467auto changeset = pool.GetChangeSet();468changeset->StageRemoval(conflict_1_entry);469changeset->StageRemoval(conflict_2_entry);470changeset->StageRemoval(conflict_3_entry);471changeset->StageRemoval(conflict_1_child_entry);472changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());473const auto replace_multiple_clusters_2{changeset->CalculateChunksForRBF()};475BOOST_CHECK(replace_multiple_clusters_2.has_value());476BOOST_CHECK(replace_multiple_clusters_2->first.size() == 4);477BOOST_CHECK(replace_multiple_clusters_2->second.size() == 1);478}479}481BOOST_AUTO_TEST_CASE(feerate_chunks_utilities)482{483// Sanity check the correctness of the feerate chunks comparison.485// A strictly better case.486std::vector<FeeFrac> old_chunks{{{950, 300}, {100, 100}}};487std::vector<FeeFrac> new_chunks{{{1000, 300}, {50, 100}}};489BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));490BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));492// Incomparable diagrams493old_chunks = {{950, 300}, {100, 100}};494new_chunks = {{1000, 300}, {0, 100}};496BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);497BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);499// Strictly better but smaller size.500old_chunks = {{950, 300}, {100, 100}};
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.