Merge bitcoin/bitcoin#31096: Package validation: accept packages of size 1

32fc59796f rpc: Allow single transaction through submitpackage (glozow)

Pull request description:

  There's no particular reason to restrict single transaction submissions with submitpackage. This change relaxes the RPC checks as enables the `AcceptPackage` flow to accept packages of a single transaction.

  Resolves #31085

ACKs for top commit:
  naumenkogs:
    ACK 32fc59796f
  achow101:
    ACK 32fc59796f
  glozow:
    ACK 32fc59796f

Tree-SHA512: ffed353bfdca610ffcfd53b40b76da05ffc26df6bac4b0421492e067bede930380e03399d2e2d1d17f0e88fb91cd8eb376e3aabebbabcc724590bf068d09807c
This commit is contained in:
Ava Chow
2024-12-03 17:46:23 -05:00
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)