Skip to content

Commit

Permalink
[test] package rbf
Browse files Browse the repository at this point in the history
  • Loading branch information
glozow authored and instagibbs committed Dec 1, 2023
1 parent e26a348 commit 38ce2cb
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 0 deletions.
135 changes: 135 additions & 0 deletions src/test/txpackage_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <key_io.h>
#include <policy/packages.h>
#include <policy/policy.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <test/util/random.h>
Expand Down Expand Up @@ -854,4 +855,138 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
}
}

BOOST_FIXTURE_TEST_CASE(package_rbf_tests, TestChain100Setup)
{
mineBlocks(5);
LOCK(::cs_main);
size_t expected_pool_size = m_node.mempool->size();
CKey child_key;
child_key.MakeNewKey(true);
CScript parent_spk = GetScriptForDestination(WitnessV0KeyHash(child_key.GetPubKey()));
CKey grandchild_key;
grandchild_key.MakeNewKey(true);
CScript child_spk = GetScriptForDestination(WitnessV0KeyHash(grandchild_key.GetPubKey()));

const CAmount coinbase_value{50 * COIN};
// Test that de-duplication works. This is not actually package rbf.
{
// 1 parent paying 200sat, 1 child paying 300sat
Package package1;
// 1 parent paying 200sat, 1 child paying 500sat
Package package2;
// Package1 and package2 have the same parent. The children conflict.
auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
/*output_destination=*/parent_spk,
/*output_amount=*/coinbase_value - 200, /*submit=*/false, /*version=*/2);
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
package1.push_back(tx_parent);
package2.push_back(tx_parent);

CTransactionRef tx_child_1 = MakeTransactionRef(CreateValidMempoolTransaction(tx_parent, 0, 101, child_key, child_spk, coinbase_value - 500, false, 2));
package1.push_back(tx_child_1);
CTransactionRef tx_child_2 = MakeTransactionRef(CreateValidMempoolTransaction(tx_parent, 0, 101, child_key, child_spk, coinbase_value - 700, false, 2));
package2.push_back(tx_child_2);

LOCK(m_node.mempool->cs);
const auto submit1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, /*test_accept=*/false);
BOOST_CHECK_MESSAGE(submit1.m_state.IsValid(), "Package validation unexpectedly failed: " << submit1.m_state.GetRejectReason());
auto it_parent_1 = submit1.m_tx_results.find(tx_parent->GetWitnessHash());
auto it_child_1 = submit1.m_tx_results.find(tx_child_1->GetWitnessHash());
BOOST_CHECK(it_parent_1 != submit1.m_tx_results.end());
BOOST_CHECK(it_child_1 != submit1.m_tx_results.end());
BOOST_CHECK_EQUAL(it_parent_1->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(it_child_1->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
expected_pool_size += 2;
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child_1->GetHash())));

const auto submit2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package2, /*test_accept=*/false);
auto it_parent_2 = submit2.m_tx_results.find(tx_parent->GetWitnessHash());
auto it_child_2 = submit2.m_tx_results.find(tx_child_2->GetWitnessHash());
BOOST_CHECK(it_parent_2 != submit2.m_tx_results.end());
BOOST_CHECK(it_child_2 != submit2.m_tx_results.end());
BOOST_CHECK_MESSAGE(submit2.m_state.IsValid(), "Package validation unexpectedly failed" << submit2.m_state.GetRejectReason());
BOOST_CHECK_EQUAL(it_parent_2->second.m_result_type, MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
BOOST_CHECK_EQUAL(it_child_2->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child_2->GetHash())));
BOOST_CHECK(!m_node.mempool->exists(GenTxid::Txid(tx_child_1->GetHash())));
}

// Test package rbf.
{
CTransactionRef tx_parent_1 = MakeTransactionRef(CreateValidMempoolTransaction(
m_coinbase_txns[1], /*input_vout=*/0, /*input_height=*/0,
coinbaseKey, parent_spk, coinbase_value - 200, /*submit=*/false, /*version=*/2));
CTransactionRef tx_child_1 = MakeTransactionRef(CreateValidMempoolTransaction(
tx_parent_1, /*input_vout=*/0, /*input_height=*/101,
child_key, child_spk, coinbase_value - 400, /*submit=*/false, /*version=*/2));

CTransactionRef tx_parent_2 = MakeTransactionRef(CreateValidMempoolTransaction(
m_coinbase_txns[1], /*input_vout=*/0, /*input_height=*/0,
coinbaseKey, parent_spk, coinbase_value - 800, /*submit=*/false, /*version=*/2));
CTransactionRef tx_child_2 = MakeTransactionRef(CreateValidMempoolTransaction(
tx_parent_2, /*input_vout=*/0, /*input_height=*/101,
child_key, child_spk, coinbase_value - 800 - 200, /*submit=*/false, /*version=*/2));

CTransactionRef tx_parent_3 = MakeTransactionRef(CreateValidMempoolTransaction(
m_coinbase_txns[1], /*input_vout=*/0, /*input_height=*/0,
coinbaseKey, parent_spk, coinbase_value - 200, /*submit=*/false, /*version=*/2));
CTransactionRef tx_child_3 = MakeTransactionRef(CreateValidMempoolTransaction(
tx_parent_3, /*input_vout=*/0, /*input_height=*/101,
child_key, child_spk, coinbase_value - 200 - 1300, /*submit=*/false, /*version=*/2));

// 1 parent paying 200sat, 1 child paying 200sat. Both v2.
Package package1{tx_parent_1, tx_child_1};
// 1 parent paying 800sat, 1 child paying 200sat. Both v2.
Package package2{tx_parent_2, tx_child_2};
// 1 parent paying 200sat, 1 child paying 1300. Both v2.
Package package3{tx_parent_3, tx_child_3};
// In all packages, the parents conflict with each other

const auto submit1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false);
BOOST_CHECK_MESSAGE(submit1.m_state.IsValid(), "Package validation unexpectedly failed" << submit1.m_state.GetRejectReason());
auto it_parent_1 = submit1.m_tx_results.find(tx_parent_1->GetWitnessHash());
auto it_child_1 = submit1.m_tx_results.find(tx_child_1->GetWitnessHash());
BOOST_CHECK(it_parent_1 != submit1.m_tx_results.end());
BOOST_CHECK(it_child_1 != submit1.m_tx_results.end());
BOOST_CHECK_EQUAL(it_parent_1->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(it_child_1->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
expected_pool_size += 2;
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_1->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child_1->GetHash())));

// This replacement is actually not package rbf; the parent carries enough fees
// to replace the entire package on its own.
const auto submit2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package2, false);
BOOST_CHECK_MESSAGE(submit2.m_state.IsValid(), "Package validation unexpectedly failed" << submit2.m_state.GetRejectReason());
auto it_parent_2 = submit2.m_tx_results.find(tx_parent_2->GetWitnessHash());
auto it_child_2 = submit2.m_tx_results.find(tx_child_2->GetWitnessHash());
BOOST_CHECK(it_parent_2 != submit2.m_tx_results.end());
BOOST_CHECK(it_child_2 != submit2.m_tx_results.end());
BOOST_CHECK_EQUAL(it_parent_2->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(it_child_2->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_2->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child_2->GetHash())));

// Package RBF, in which the replacement transaction's child sponsors the fees to meet RBF feerate rules
const auto submit3 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package3, false);
BOOST_CHECK_MESSAGE(submit3.m_state.IsValid(), "Package validation unexpectedly failed" << submit3.m_state.GetRejectReason());
auto it_parent_3 = submit3.m_tx_results.find(tx_parent_3->GetWitnessHash());
auto it_child_3 = submit3.m_tx_results.find(tx_child_3->GetWitnessHash());
BOOST_CHECK(it_parent_3 != submit3.m_tx_results.end());
BOOST_CHECK(it_child_3 != submit3.m_tx_results.end());
BOOST_CHECK_EQUAL(it_parent_3->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(it_child_3->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_3->GetHash())));
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child_3->GetHash())));
}

}
BOOST_AUTO_TEST_SUITE_END()
1 change: 1 addition & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ bool MemPoolAccept::PackageMempoolChecks(const ATMPArgs& args,
}
}

// TODO check if feerate of child is higher than feerate of parent
// Check if it's economically rational to mine this package rather than the ones it replaces.
if (const auto err_string{CheckMinerScores(m_total_modified_fees, m_total_vsize, direct_conflict_iters,
m_all_conflicts)}) {
Expand Down

0 comments on commit 38ce2cb

Please sign in to comment.