Skip to content

Commit

Permalink
Unit tests for CalculateFeerateDiagramsForRBF
Browse files Browse the repository at this point in the history
  • Loading branch information
instagibbs committed Feb 20, 2024
1 parent 4a03e79 commit 253feff
Showing 1 changed file with 156 additions and 0 deletions.
156 changes: 156 additions & 0 deletions src/test/rbf_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,162 @@ BOOST_FIXTURE_TEST_CASE(improves_feerate, TestChain100Setup)
BOOST_CHECK(res3.has_value());
BOOST_CHECK(res3.value().first == DiagramCheckError::UNCALCULABLE);
BOOST_CHECK(res3.value().second == strprintf("%s has 2 descendants, max 1 allowed", tx1->GetHash().GetHex()));

}

BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
{
CTxMemPool& pool = *Assert(m_node.mempool);
LOCK2(::cs_main, pool.cs);
TestMemPoolEntryHelper entry;

const CAmount low_fee{CENT/100};
const CAmount normal_fee{CENT/10};
const CAmount high_fee{CENT};

// low -> high -> medium fee transactions that would result in two chunks together
const auto low_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx));

const auto entry_low = pool.GetIter(low_tx->GetHash()).value();
const auto low_size = entry_low->GetTxSize();

std::vector<FeeFrac> old_diagram, new_diagram;

// Replacement of size 1
const auto err_string{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low}, old_diagram, new_diagram)};
BOOST_CHECK(!err_string.has_value());
BOOST_CHECK(old_diagram.size() == 2);
BOOST_CHECK(new_diagram.size() == 2);
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee, low_size));
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(new_diagram[1] == FeeFrac(0, 1));
old_diagram.clear();
new_diagram.clear();

// Non-zero replacement fee/size
const auto err_string3{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low}, old_diagram, new_diagram)};
BOOST_CHECK(!err_string3.has_value());
BOOST_CHECK(old_diagram.size() == 2);
BOOST_CHECK(new_diagram.size() == 2);
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee, low_size));
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size));
old_diagram.clear();
new_diagram.clear();

// Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF
const auto high_tx = make_tx(/*inputs=*/ {low_tx}, /*output_values=*/ {995 * CENT});
pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx));
const auto entry_high = pool.GetIter(high_tx->GetHash()).value();
const auto high_size = entry_high->GetTxSize();

const auto err_string4{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high}, old_diagram, new_diagram)};
BOOST_CHECK(!err_string4.has_value());
BOOST_CHECK(old_diagram.size() == 2);
BOOST_CHECK(new_diagram.size() == 2);
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee + high_fee, low_size + high_size));
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size));
old_diagram.clear();
new_diagram.clear();

// Conflict with the 2nd tx, resulting in new diagram with three entries
const auto err_string5{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high}, old_diagram, new_diagram)};
BOOST_CHECK(!err_string5.has_value());
BOOST_CHECK(old_diagram.size() == 2);
BOOST_CHECK(new_diagram.size() == 3);
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(old_diagram[1] == FeeFrac(low_fee + high_fee, low_size + high_size));
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size));
BOOST_CHECK(new_diagram[2] == FeeFrac(low_fee + high_fee, low_size + low_size));
old_diagram.clear();
new_diagram.clear();

// third transaction causes the topology check to fail
const auto normal_tx = make_tx(/*inputs=*/ {high_tx}, /*output_values=*/ {995 * CENT});
pool.addUnchecked(entry.Fee(normal_fee).FromTx(normal_tx));
const auto entry_normal = pool.GetIter(normal_tx->GetHash()).value();
const auto normal_size = entry_normal->GetTxSize();

const auto err_string6{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal}, old_diagram, new_diagram)};
BOOST_CHECK(err_string6.has_value());
BOOST_CHECK(err_string6.value() == strprintf("%s has 2 descendants, max 1 allowed", low_tx->GetHash().GetHex()));
BOOST_CHECK(old_diagram.empty());
BOOST_CHECK(new_diagram.empty());

// Make a size 2 cluster that is itself two chunks; evict both txns
const auto high_tx_2 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});
pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx_2));
const auto entry_high_2 = pool.GetIter(high_tx_2->GetHash()).value();
const auto high_size_2 = entry_high_2->GetTxSize();

const auto low_tx_2 = make_tx(/*inputs=*/ {high_tx_2}, /*output_values=*/ {9 * COIN});
pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx_2));
const auto entry_low_2 = pool.GetIter(low_tx_2->GetHash()).value();
const auto low_size_2 = entry_low_2->GetTxSize();

const auto err_string7{pool.CalculateFeerateDiagramsForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high_2}, {entry_high_2, entry_low_2}, old_diagram, new_diagram)};
BOOST_CHECK(!err_string5.has_value());
BOOST_CHECK(old_diagram.size() == 3);
BOOST_CHECK(new_diagram.size() == 2);
BOOST_CHECK(old_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(old_diagram[1] == FeeFrac(high_fee, high_size_2));
BOOST_CHECK(old_diagram[2] == FeeFrac(low_fee + high_fee, low_size_2 + high_size_2));
BOOST_CHECK(new_diagram[0] == FeeFrac(0, 0));
BOOST_CHECK(new_diagram[1] == FeeFrac(high_fee, low_size_2));
old_diagram.clear();
new_diagram.clear();

// You can have more than two direct conflicts if the therea re multiple effected clusters, all of size 2 or less
const auto conflict_1 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1));
const auto conflict_1_entry = pool.GetIter(conflict_1->GetHash()).value();

const auto conflict_2 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {10 * COIN});
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_2));
const auto conflict_2_entry = pool.GetIter(conflict_2->GetHash()).value();

const auto conflict_3 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {10 * COIN});
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_3));
const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();

const auto err_string8{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}, old_diagram, new_diagram)};

BOOST_CHECK(!err_string8.has_value());
BOOST_CHECK(old_diagram.size() == 4);
BOOST_CHECK(new_diagram.size() == 2);
old_diagram.clear();
new_diagram.clear();

// Add a child transaction to conflict_1 and make it cluster size 2, still one chunk due to same feerate
const auto conflict_1_child = make_tx(/*inputs=*/{conflict_1}, /*output_values=*/ {995 * CENT});
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1_child));
const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();

const auto err_string9{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}, old_diagram, new_diagram)};

BOOST_CHECK(!err_string8.has_value());
BOOST_CHECK(old_diagram.size() == 4);
BOOST_CHECK(new_diagram.size() == 2);
old_diagram.clear();
new_diagram.clear();

// Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
const auto conflict_1_grand_child = make_tx(/*inputs=*/{conflict_1_child}, /*output_values=*/ {995 * CENT});
pool.addUnchecked(entry.Fee(high_fee).FromTx(conflict_1_grand_child));
const auto conflict_1_grand_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();

const auto err_string10{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}, old_diagram, new_diagram)};

BOOST_CHECK(err_string10.has_value());
BOOST_CHECK(err_string10.value() == strprintf("%s has 2 descendants, max 1 allowed", conflict_1->GetHash().GetHex()));
BOOST_CHECK(old_diagram.empty());
BOOST_CHECK(new_diagram.empty());
}

BOOST_AUTO_TEST_CASE(feerate_diagram_utilities)
Expand Down

0 comments on commit 253feff

Please sign in to comment.