rpc: Allow single transaction through submitpackage

And under the hood suppoert single transactions
in AcceptPackage. This simplifies user experience
and paves the way for reducing number of codepaths
for transaction acceptance in the future.

Co-Authored-By: instagibbs <gsanders87@gmail.com>
This commit is contained in:
glozow
2024-11-22 07:06:23 -05:00
committed by Greg Sanders
parent 6463117a29
commit 32fc59796f
5 changed files with 155 additions and 54 deletions

View File

@@ -283,6 +283,8 @@ BOOST_AUTO_TEST_CASE(noncontextual_package_tests)
BOOST_CHECK(GetPackageHash({tx_parent}) != GetPackageHash({tx_child}));
BOOST_CHECK(GetPackageHash({tx_child, tx_child}) != GetPackageHash({tx_child}));
BOOST_CHECK(GetPackageHash({tx_child, tx_parent}) != GetPackageHash({tx_child, tx_child}));
BOOST_CHECK(!IsChildWithParents({}));
BOOST_CHECK(!IsChildWithParentsTree({}));
}
// 24 Parents and 1 Child
@@ -492,6 +494,97 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
}
}
// Tests for packages containing a single transaction
BOOST_AUTO_TEST_CASE(package_single_tx)
{
// Mine blocks to mature coinbases.
mineBlocks(3);
LOCK(cs_main);
auto expected_pool_size{m_node.mempool->size()};
const CAmount high_fee{1000};
// No unconfirmed parents
CKey single_key = GenerateRandomKey();
CScript single_locking_script = GetScriptForDestination(PKHash(single_key.GetPubKey()));
auto mtx_single = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
/*output_destination=*/single_locking_script,
/*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
CTransactionRef tx_single = MakeTransactionRef(mtx_single);
Package package_tx_single{tx_single};
const auto result_single_tx = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_tx_single, /*test_accept=*/false, /*client_maxfeerate=*/{});
expected_pool_size += 1;
BOOST_CHECK_MESSAGE(result_single_tx.m_state.IsValid(),
"Package validation unexpectedly failed: " << result_single_tx.m_state.ToString());
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
// Parent and Child. Both submitted by themselves through the ProcessNewPackage interface.
CKey parent_key = GenerateRandomKey();
CScript parent_locking_script = GetScriptForDestination(WitnessV0KeyHash(parent_key.GetPubKey()));
auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
/*output_destination=*/parent_locking_script,
/*output_amount=*/CAmount(50 * COIN) - high_fee, /*submit=*/false);
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
Package package_just_parent{tx_parent};
const auto result_just_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_just_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_just_parent, result_just_parent, /*expect_valid=*/true, nullptr)}) {
BOOST_ERROR(err_parent_child.value());
} else {
auto it_parent = result_just_parent.m_tx_results.find(tx_parent->GetWitnessHash());
BOOST_CHECK_MESSAGE(it_parent->second.m_state.IsValid(), it_parent->second.m_state.ToString());
BOOST_CHECK(it_parent->second.m_effective_feerate.value().GetFee(GetVirtualTransactionSize(*tx_parent)) == high_fee);
BOOST_CHECK_EQUAL(it_parent->second.m_wtxids_fee_calculations.value().size(), 1);
BOOST_CHECK_EQUAL(it_parent->second.m_wtxids_fee_calculations.value().front(), tx_parent->GetWitnessHash());
}
expected_pool_size += 1;
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
CKey child_key = GenerateRandomKey();
CScript child_locking_script = GetScriptForDestination(WitnessV0KeyHash(child_key.GetPubKey()));
auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
/*input_height=*/101, /*input_signing_key=*/parent_key,
/*output_destination=*/child_locking_script,
/*output_amount=*/CAmount(50 * COIN) - 2 * high_fee, /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
Package package_just_child{tx_child};
const auto result_just_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_just_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_just_child, result_just_child, /*expect_valid=*/true, nullptr)}) {
BOOST_ERROR(err_parent_child.value());
} else {
auto it_child = result_just_child.m_tx_results.find(tx_child->GetWitnessHash());
BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(), it_child->second.m_state.ToString());
BOOST_CHECK(it_child->second.m_effective_feerate.value().GetFee(GetVirtualTransactionSize(*tx_child)) == high_fee);
BOOST_CHECK_EQUAL(it_child->second.m_wtxids_fee_calculations.value().size(), 1);
BOOST_CHECK_EQUAL(it_child->second.m_wtxids_fee_calculations.value().front(), tx_child->GetWitnessHash());
}
expected_pool_size += 1;
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
// Too-low fee to RBF tx_single
auto mtx_single_low_fee = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
/*output_destination=*/single_locking_script,
/*output_amount=*/CAmount(49 * COIN - 1), /*submit=*/false);
CTransactionRef tx_single_low_fee = MakeTransactionRef(mtx_single_low_fee);
Package package_tx_single_low_fee{tx_single_low_fee};
const auto result_single_tx_low_fee = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_tx_single_low_fee, /*test_accept=*/false, /*client_maxfeerate=*/{});
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(!result_single_tx_low_fee.m_state.IsValid());
BOOST_CHECK_EQUAL(result_single_tx_low_fee.m_state.GetResult(), PackageValidationResult::PCKG_TX);
auto it_low_fee = result_single_tx_low_fee.m_tx_results.find(tx_single_low_fee->GetWitnessHash());
BOOST_CHECK_EQUAL(it_low_fee->second.m_state.GetResult(), TxValidationResult::TX_RECONSIDERABLE);
if (auto err_single{CheckPackageMempoolAcceptResult(package_tx_single_low_fee, result_single_tx_low_fee, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_single.value());
}
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
}
// Tests for packages containing transactions that have same-txid-different-witness equivalents in
// the mempool.
BOOST_AUTO_TEST_CASE(package_witness_swap_tests)