Skip to content

Commit 7295986

Browse files
committed
Unit tests for CalculateFeerateDiagramsForRBF
1 parent b767e6b commit 7295986

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

src/test/rbf_tests.cpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,164 @@ BOOST_FIXTURE_TEST_CASE(improves_feerate, TestChain100Setup)
334334
BOOST_CHECK(res3.has_value());
335335
BOOST_CHECK(res3.value().first == DiagramCheckError::UNCALCULABLE);
336336
BOOST_CHECK(res3.value().second == strprintf("%s has 2 descendants, max 1 allowed", tx1->GetHash().GetHex()));
337+
338+
}
339+
340+
BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
341+
{
342+
CTxMemPool& pool = *Assert(m_node.mempool);
343+
LOCK2(::cs_main, pool.cs);
344+
TestMemPoolEntryHelper entry;
345+
346+
const CAmount low_fee{CENT/100};
347+
const CAmount normal_fee{CENT/10};
348+
const CAmount high_fee{CENT};
349+
350+
// low -> high -> medium fee transactions that would result in two chunks together
351+
const auto low_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
352+
pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx));
353+
354+
const auto entry_low = pool.GetIter(low_tx->GetHash()).value();
355+
const auto low_size = entry_low->GetTxSize();
356+
357+
std::vector<FeeFrac> old_diagram, new_diagram;
358+
359+
// Replacement of size 1
360+
const auto replace_one{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low})};
361+
BOOST_CHECK(replace_one.has_value());
362+
old_diagram = replace_one->first;
363+
new_diagram = replace_one->second;
364+
BOOST_CHECK(old_diagram.size() == 2);
365+
BOOST_CHECK(new_diagram.size() == 2);
366+
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
367+
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee, low_size));
368+
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
369+
BOOST_CHECK(new_diagram[1] == FeeFrac(0, 1));
370+
371+
// Non-zero replacement fee/size
372+
const auto replace_one_fee{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low})};
373+
BOOST_CHECK(replace_one_fee.has_value());
374+
old_diagram = replace_one_fee->first;
375+
new_diagram = replace_one_fee->second;
376+
BOOST_CHECK(old_diagram.size() == 2);
377+
BOOST_CHECK(new_diagram.size() == 2);
378+
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
379+
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee, low_size));
380+
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
381+
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size));
382+
383+
// Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF
384+
const auto high_tx = make_tx(/*inputs=*/ {low_tx}, /*output_values=*/ {995 * CENT});
385+
pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx));
386+
const auto entry_high = pool.GetIter(high_tx->GetHash()).value();
387+
const auto high_size = entry_high->GetTxSize();
388+
389+
const auto replace_single_chunk{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high})};
390+
BOOST_CHECK(replace_single_chunk.has_value());
391+
old_diagram = replace_single_chunk->first;
392+
new_diagram = replace_single_chunk->second;
393+
BOOST_CHECK(old_diagram.size() == 2);
394+
BOOST_CHECK(new_diagram.size() == 2);
395+
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
396+
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee + high_fee, low_size + high_size));
397+
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
398+
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size));
399+
400+
// Conflict with the 2nd tx, resulting in new diagram with three entries
401+
const auto replace_cpfp_child{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high})};
402+
BOOST_CHECK(replace_cpfp_child.has_value());
403+
old_diagram = replace_cpfp_child->first;
404+
new_diagram = replace_cpfp_child->second;
405+
BOOST_CHECK(old_diagram.size() == 2);
406+
BOOST_CHECK(new_diagram.size() == 3);
407+
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
408+
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee + high_fee, low_size + high_size));
409+
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
410+
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size));
411+
BOOST_CHECK(new_diagram[2] == FeeFrac(low_fee + high_fee, low_size + low_size));
412+
413+
// third transaction causes the topology check to fail
414+
const auto normal_tx = make_tx(/*inputs=*/ {high_tx}, /*output_values=*/ {995 * CENT});
415+
pool.addUnchecked(entry.Fee(normal_fee).FromTx(normal_tx));
416+
const auto entry_normal = pool.GetIter(normal_tx->GetHash()).value();
417+
const auto normal_size = entry_normal->GetTxSize();
418+
419+
const auto replace_too_large{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
420+
BOOST_CHECK(!replace_too_large.has_value());
421+
BOOST_CHECK_EQUAL(util::ErrorString(replace_too_large).original, strprintf("%s has 2 descendants, max 1 allowed", low_tx->GetHash().GetHex()));
422+
old_diagram.clear();
423+
new_diagram.clear();
424+
425+
// Make a size 2 cluster that is itself two chunks; evict both txns
426+
const auto high_tx_2 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});
427+
pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx_2));
428+
const auto entry_high_2 = pool.GetIter(high_tx_2->GetHash()).value();
429+
const auto high_size_2 = entry_high_2->GetTxSize();
430+
431+
const auto low_tx_2 = make_tx(/*inputs=*/ {high_tx_2}, /*output_values=*/ {9 * COIN});
432+
pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx_2));
433+
const auto entry_low_2 = pool.GetIter(low_tx_2->GetHash()).value();
434+
const auto low_size_2 = entry_low_2->GetTxSize();
435+
436+
const auto replace_two_chunks_single_cluster{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high_2}, {entry_high_2, entry_low_2})};
437+
BOOST_CHECK(replace_two_chunks_single_cluster.has_value());
438+
old_diagram = replace_two_chunks_single_cluster->first;
439+
new_diagram = replace_two_chunks_single_cluster->second;
440+
BOOST_CHECK(old_diagram.size() == 3);
441+
BOOST_CHECK(new_diagram.size() == 2);
442+
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
443+
BOOST_CHECK(old_diagram[1] == FeeFrac(high_fee, high_size_2));
444+
BOOST_CHECK(old_diagram[2] == FeeFrac(low_fee + high_fee, low_size_2 + high_size_2));
445+
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
446+
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size_2));
447+
448+
// You can have more than two direct conflicts if the there are multiple effected clusters, all of size 2 or less
449+
const auto conflict_1 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});
450+
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1));
451+
const auto conflict_1_entry = pool.GetIter(conflict_1->GetHash()).value();
452+
453+
const auto conflict_2 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {10 * COIN});
454+
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_2));
455+
const auto conflict_2_entry = pool.GetIter(conflict_2->GetHash()).value();
456+
457+
const auto conflict_3 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {10 * COIN});
458+
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_3));
459+
const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();
460+
461+
const auto replace_multiple_clusters{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry})};
462+
463+
BOOST_CHECK(replace_multiple_clusters.has_value());
464+
old_diagram = replace_multiple_clusters->first;
465+
new_diagram = replace_multiple_clusters->second;
466+
BOOST_CHECK(old_diagram.size() == 4);
467+
BOOST_CHECK(new_diagram.size() == 2);
468+
469+
// Add a child transaction to conflict_1 and make it cluster size 2, still one chunk due to same feerate
470+
const auto conflict_1_child = make_tx(/*inputs=*/{conflict_1}, /*output_values=*/ {995 * CENT});
471+
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1_child));
472+
const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
473+
474+
const auto replace_multiple_clusters_2{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry})};
475+
476+
BOOST_CHECK(replace_multiple_clusters_2.has_value());
477+
old_diagram = replace_multiple_clusters_2->first;
478+
new_diagram = replace_multiple_clusters_2->second;
479+
BOOST_CHECK(old_diagram.size() == 4);
480+
BOOST_CHECK(new_diagram.size() == 2);
481+
old_diagram.clear();
482+
new_diagram.clear();
483+
484+
// Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
485+
const auto conflict_1_grand_child = make_tx(/*inputs=*/{conflict_1_child}, /*output_values=*/ {995 * CENT});
486+
pool.addUnchecked(entry.Fee(high_fee).FromTx(conflict_1_grand_child));
487+
const auto conflict_1_grand_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
488+
489+
const auto replace_cluster_size_3{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry, conflict_1_grand_child_entry})};
490+
491+
BOOST_CHECK(!replace_cluster_size_3.has_value());
492+
BOOST_CHECK_EQUAL(util::ErrorString(replace_cluster_size_3).original, strprintf("%s has 2 descendants, max 1 allowed", conflict_1->GetHash().GetHex()));
493+
BOOST_CHECK(old_diagram.empty());
494+
BOOST_CHECK(new_diagram.empty());
337495
}
338496

339497
BOOST_AUTO_TEST_CASE(feerate_diagram_utilities)

0 commit comments

Comments
 (0)