LCOV - code coverage report
Current view: top level - src/test - txpackage_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 368 368 100.0 %
Date: 2026-06-25 07:23:43 Functions: 42 42 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2021 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <consensus/validation.h>
       6             : #include <key_io.h>
       7             : #include <policy/packages.h>
       8             : #include <policy/policy.h>
       9             : #include <policy/settings.h>
      10             : #include <primitives/transaction.h>
      11             : #include <script/script.h>
      12             : #include <script/standard.h>
      13             : #include <test/util/random.h>
      14             : #include <test/util/setup_common.h>
      15             : #include <validation.h>
      16             : 
      17             : #include <boost/test/unit_test.hpp>
      18             : 
      19             : struct TestChain100NoDIP0001Setup : public TestChain100Setup {
      20           4 :     TestChain100NoDIP0001Setup()
      21           4 :         : TestChain100Setup{CBaseChainParams::REGTEST, {"-testactivationheight=dip0001@2000"}} {}
      22             : };
      23             : 
      24         146 : BOOST_AUTO_TEST_SUITE(txpackage_tests)
      25             : // A fee amount that is above 1sat/vB but below 5sat/vB for most transactions created within these
      26             : // unit tests. Dash transactions are larger than Bitcoin's (no SegWit discount), so this needs to
      27             : // be higher than Bitcoin's 200 sat to ensure it exceeds minRelayTxFee for ~160-byte P2PKH txns.
      28             : static const CAmount low_fee_amt{500};
      29             : 
      30             : // Create placeholder transactions that have no meaning.
      31          28 : inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outputs)
      32             : {
      33          28 :     CMutableTransaction mtx = CMutableTransaction();
      34          28 :     mtx.vin.resize(num_inputs);
      35          28 :     mtx.vout.resize(num_outputs);
      36          28 :     auto random_script = CScript() << ToByteVector(InsecureRand256()) << ToByteVector(InsecureRand256());
      37        1203 :     for (size_t i{0}; i < num_inputs; ++i) {
      38        1175 :         mtx.vin[i].prevout.hash = InsecureRand256();
      39        1175 :         mtx.vin[i].prevout.n = 0;
      40        1175 :         mtx.vin[i].scriptSig = random_script;
      41        1175 :     }
      42        1203 :     for (size_t o{0}; o < num_outputs; ++o) {
      43        1175 :         mtx.vout[o].nValue = 1 * CENT;
      44        1175 :         mtx.vout[o].scriptPubKey = random_script;
      45        1175 :     }
      46          28 :     return MakeTransactionRef(mtx);
      47          28 : }
      48             : 
      49         148 : BOOST_FIXTURE_TEST_CASE(package_sanitization_tests, TestChain100NoDIP0001Setup)
      50             : {
      51             :     // Packages can't have more than 25 transactions.
      52           1 :     Package package_too_many;
      53           1 :     package_too_many.reserve(MAX_PACKAGE_COUNT + 1);
      54          27 :     for (size_t i{0}; i < MAX_PACKAGE_COUNT + 1; ++i) {
      55          26 :         package_too_many.emplace_back(create_placeholder_tx(1, 1));
      56          26 :     }
      57           1 :     PackageValidationState state_too_many;
      58           1 :     BOOST_CHECK(!CheckPackage(package_too_many, state_too_many));
      59           1 :     BOOST_CHECK_EQUAL(state_too_many.GetResult(), PackageValidationResult::PCKG_POLICY);
      60           1 :     BOOST_CHECK_EQUAL(state_too_many.GetRejectReason(), "package-too-many-transactions");
      61             : 
      62             :     // Packages can't have a total size of more than 101KvB.
      63           1 :     CTransactionRef large_ptx = create_placeholder_tx(150, 150);
      64           1 :     Package package_too_large;
      65           1 :     auto size_large = GetVirtualTransactionSize(*large_ptx);
      66           1 :     size_t total_size{0};
      67           5 :     while (total_size <= MAX_PACKAGE_SIZE * 1000) {
      68           4 :         package_too_large.push_back(large_ptx);
      69           4 :         total_size += size_large;
      70             :     }
      71           1 :     BOOST_CHECK(package_too_large.size() <= MAX_PACKAGE_COUNT);
      72           1 :     PackageValidationState state_too_large;
      73           1 :     BOOST_CHECK(!CheckPackage(package_too_large, state_too_large));
      74           1 :     BOOST_CHECK_EQUAL(state_too_large.GetResult(), PackageValidationResult::PCKG_POLICY);
      75           1 :     BOOST_CHECK_EQUAL(state_too_large.GetRejectReason(), "package-too-large");
      76           1 : }
      77             : 
      78         148 : BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100NoDIP0001Setup)
      79             : {
      80           1 :     LOCK(cs_main);
      81           1 :     unsigned int initialPoolSize = m_node.mempool->size();
      82             : 
      83             :     // Parent and Child Package
      84           1 :     CKey parent_key = GenerateRandomKey();
      85           1 :     CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
      86           2 :     auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
      87           1 :                                                     /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
      88           1 :                                                     /*output_destination=*/parent_locking_script,
      89             :                                                     /*output_amount=*/CAmount(499 * COIN), /*submit=*/false);
      90           1 :     CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
      91             : 
      92           1 :     CKey child_key = GenerateRandomKey();
      93           1 :     CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
      94           2 :     auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
      95           1 :                                                    /*input_height=*/101, /*input_signing_key=*/parent_key,
      96           1 :                                                    /*output_destination=*/child_locking_script,
      97             :                                                    /*output_amount=*/CAmount(498 * COIN), /*submit=*/false);
      98           1 :     CTransactionRef tx_child = MakeTransactionRef(mtx_child);
      99           1 :     const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /*test_accept=*/true);
     100           1 :     BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(),
     101             :                         "Package validation unexpectedly failed: " << result_parent_child.m_state.GetRejectReason());
     102           1 :     auto it_parent = result_parent_child.m_tx_results.find(tx_parent->GetHash());
     103           1 :     auto it_child = result_parent_child.m_tx_results.find(tx_child->GetHash());
     104           1 :     BOOST_CHECK(it_parent != result_parent_child.m_tx_results.end());
     105           1 :     BOOST_CHECK_MESSAGE(it_parent->second.m_state.IsValid(),
     106             :                         "Package validation unexpectedly failed: " << it_parent->second.m_state.GetRejectReason());
     107           1 :     BOOST_CHECK(it_child != result_parent_child.m_tx_results.end());
     108           1 :     BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(),
     109             :                         "Package validation unexpectedly failed: " << it_child->second.m_state.GetRejectReason());
     110           1 :     BOOST_CHECK(result_parent_child.m_package_feerate.has_value());
     111           1 :     BOOST_CHECK(result_parent_child.m_package_feerate.value() ==
     112             :                 CFeeRate(2 * COIN, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child)));
     113             : 
     114             :     // A single, giant transaction submitted through ProcessNewPackage fails on single tx policy.
     115           1 :     CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
     116           1 :     BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > MAX_PACKAGE_SIZE * 1000);
     117           1 :     auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {giant_ptx}, /*test_accept=*/true);
     118           1 :     BOOST_CHECK(result_single_large.m_state.IsInvalid());
     119           1 :     BOOST_CHECK_EQUAL(result_single_large.m_state.GetResult(), PackageValidationResult::PCKG_TX);
     120           1 :     BOOST_CHECK_EQUAL(result_single_large.m_state.GetRejectReason(), "transaction failed");
     121           1 :     auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetHash());
     122           1 :     BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end());
     123           1 :     BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size");
     124           1 :     BOOST_CHECK(result_single_large.m_package_feerate == std::nullopt);
     125             : 
     126             :     // Check that mempool size hasn't changed.
     127           1 :     BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
     128           1 : }
     129             : 
     130         148 : BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100NoDIP0001Setup)
     131             : {
     132             :     // The signatures won't be verified so we can just use a placeholder
     133           1 :     CKey placeholder_key = GenerateRandomKey();
     134           1 :     CScript spk = GetScriptForDestination(PKHash(placeholder_key.GetPubKey()));
     135           1 :     CKey placeholder_key_2 = GenerateRandomKey();
     136           1 :     CScript spk2 = GetScriptForDestination(PKHash(placeholder_key_2.GetPubKey()));
     137             : 
     138             :     // Parent and Child Package
     139             :     {
     140           1 :         auto mtx_parent = CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, spk,
     141             :                                                         CAmount(49 * COIN), /*submit=*/false);
     142           1 :         CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
     143             : 
     144           1 :         auto mtx_child = CreateValidMempoolTransaction(tx_parent, 0, 101, placeholder_key, spk2,
     145             :                                                        CAmount(48 * COIN), /*submit=*/false);
     146           1 :         CTransactionRef tx_child = MakeTransactionRef(mtx_child);
     147             : 
     148           1 :         PackageValidationState state;
     149           1 :         BOOST_CHECK(CheckPackage({tx_parent, tx_child}, state));
     150           1 :         BOOST_CHECK(!CheckPackage({tx_child, tx_parent}, state));
     151           1 :         BOOST_CHECK_EQUAL(state.GetResult(), PackageValidationResult::PCKG_POLICY);
     152           1 :         BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
     153           1 :         BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
     154           1 :     }
     155             : 
     156             :     // 24 Parents and 1 Child
     157             :     {
     158           1 :         Package package;
     159           1 :         CMutableTransaction child;
     160          25 :         for (int i{0}; i < 24; ++i) {
     161          48 :             auto parent = MakeTransactionRef(CreateValidMempoolTransaction(m_coinbase_txns[i + 1],
     162          24 :                                              0, 0, coinbaseKey, spk, CAmount(48 * COIN), false));
     163          24 :             package.emplace_back(parent);
     164          24 :             child.vin.emplace_back(COutPoint(parent->GetHash(), 0));
     165          24 :         }
     166           1 :         child.vout.emplace_back(47 * COIN, spk2);
     167             : 
     168             :         // The child must be in the package.
     169           1 :         BOOST_CHECK(!IsChildWithParents(package));
     170             : 
     171             :         // The parents can be in any order.
     172           1 :         FastRandomContext rng;
     173           1 :         Shuffle(package.begin(), package.end(), rng);
     174           1 :         package.push_back(MakeTransactionRef(child));
     175             : 
     176           1 :         PackageValidationState state;
     177           1 :         BOOST_CHECK(CheckPackage(package, state));
     178           1 :         BOOST_CHECK(IsChildWithParents(package));
     179             : 
     180           1 :         package.erase(package.begin());
     181           1 :         BOOST_CHECK(IsChildWithParents(package));
     182             : 
     183             :         // The package cannot have unrelated transactions.
     184           1 :         package.insert(package.begin(), m_coinbase_txns[0]);
     185           1 :         BOOST_CHECK(!IsChildWithParents(package));
     186           1 :     }
     187             : 
     188             :     // 2 Parents and 1 Child where one parent depends on the other.
     189             :     {
     190           1 :         CMutableTransaction mtx_parent;
     191           1 :         mtx_parent.vin.emplace_back(COutPoint(m_coinbase_txns[0]->GetHash(), 0));
     192           1 :         mtx_parent.vout.emplace_back(20 * COIN, spk);
     193           1 :         mtx_parent.vout.emplace_back(20 * COIN, spk2);
     194           1 :         CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
     195             : 
     196           1 :         CMutableTransaction mtx_parent_also_child;
     197           1 :         mtx_parent_also_child.vin.emplace_back(COutPoint(tx_parent->GetHash(), 0));
     198           1 :         mtx_parent_also_child.vout.emplace_back(20 * COIN, spk);
     199           1 :         CTransactionRef tx_parent_also_child = MakeTransactionRef(mtx_parent_also_child);
     200             : 
     201           1 :         CMutableTransaction mtx_child;
     202           1 :         mtx_child.vin.emplace_back(COutPoint(tx_parent->GetHash(), 1));
     203           1 :         mtx_child.vin.emplace_back(COutPoint(tx_parent_also_child->GetHash(), 0));
     204           1 :         mtx_child.vout.emplace_back(39 * COIN, spk);
     205           1 :         CTransactionRef tx_child = MakeTransactionRef(mtx_child);
     206             : 
     207           1 :         PackageValidationState state;
     208           1 :         BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child}));
     209           1 :         BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
     210           1 :         BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child, tx_child}));
     211             :         // IsChildWithParents does not detect unsorted parents.
     212           1 :         BOOST_CHECK(IsChildWithParents({tx_parent_also_child, tx_parent, tx_child}));
     213           1 :         BOOST_CHECK(CheckPackage({tx_parent, tx_parent_also_child, tx_child}, state));
     214           1 :         BOOST_CHECK(!CheckPackage({tx_parent_also_child, tx_parent, tx_child}, state));
     215           1 :         BOOST_CHECK_EQUAL(state.GetResult(), PackageValidationResult::PCKG_POLICY);
     216           1 :         BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
     217           1 :     }
     218           1 : }
     219             : 
     220         148 : BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100NoDIP0001Setup)
     221             : {
     222           1 :     LOCK(cs_main);
     223           1 :     unsigned int expected_pool_size = m_node.mempool->size();
     224           1 :     CKey parent_key = GenerateRandomKey();
     225           1 :     CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
     226             : 
     227             :     // Unrelated transactions are not allowed in package submission.
     228           1 :     Package package_unrelated;
     229          11 :     for (size_t i{0}; i < 10; ++i) {
     230          20 :         auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[i + 25], /*input_vout=*/0,
     231          10 :                                                  /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
     232          10 :                                                  /*output_destination=*/parent_locking_script,
     233             :                                                  /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
     234          10 :         package_unrelated.emplace_back(MakeTransactionRef(mtx));
     235          10 :     }
     236           1 :     auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     237             :                                                      package_unrelated, /*test_accept=*/false);
     238           1 :     BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
     239           1 :     BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
     240           1 :     BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
     241           1 :     BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     242           1 :     BOOST_CHECK(result_unrelated_submit.m_package_feerate == std::nullopt);
     243             : 
     244             :     // Parent and Child (and Grandchild) Package
     245           1 :     Package package_parent_child;
     246           1 :     Package package_3gen;
     247           2 :     auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
     248           1 :                                                     /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
     249           1 :                                                     /*output_destination=*/parent_locking_script,
     250             :                                                     /*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
     251           1 :     CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
     252           1 :     package_parent_child.push_back(tx_parent);
     253           1 :     package_3gen.push_back(tx_parent);
     254             : 
     255           1 :     CKey child_key = GenerateRandomKey();
     256           1 :     CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
     257           2 :     auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
     258           1 :                                                    /*input_height=*/101, /*input_signing_key=*/parent_key,
     259           1 :                                                    /*output_destination=*/child_locking_script,
     260             :                                                    /*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
     261           1 :     CTransactionRef tx_child = MakeTransactionRef(mtx_child);
     262           1 :     package_parent_child.push_back(tx_child);
     263           1 :     package_3gen.push_back(tx_child);
     264             : 
     265           1 :     CKey grandchild_key = GenerateRandomKey();
     266           1 :     CScript grandchild_locking_script = GetScriptForDestination(PKHash(grandchild_key.GetPubKey()));
     267           2 :     auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/tx_child, /*input_vout=*/0,
     268           1 :                                                        /*input_height=*/101, /*input_signing_key=*/child_key,
     269           1 :                                                        /*output_destination=*/grandchild_locking_script,
     270             :                                                        /*output_amount=*/CAmount(47 * COIN), /*submit=*/false);
     271           1 :     CTransactionRef tx_grandchild = MakeTransactionRef(mtx_grandchild);
     272           1 :     package_3gen.push_back(tx_grandchild);
     273             : 
     274             :     // 3 Generations is not allowed.
     275             :     {
     276           1 :         auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     277             :                                                     package_3gen, /*test_accept=*/false);
     278           1 :         BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
     279           1 :         BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
     280           1 :         BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
     281           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     282           1 :         BOOST_CHECK(result_3gen_submit.m_package_feerate == std::nullopt);
     283           1 :     }
     284             : 
     285             :     // Child with missing parent.
     286           1 :     mtx_child.vin.emplace_back(COutPoint(package_unrelated[0]->GetHash(), 0));
     287           1 :     Package package_missing_parent;
     288           1 :     package_missing_parent.push_back(tx_parent);
     289           1 :     package_missing_parent.push_back(MakeTransactionRef(mtx_child));
     290             :     {
     291           1 :         const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     292             :                                                              package_missing_parent, /*test_accept=*/false);
     293           1 :         BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
     294           1 :         BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
     295           1 :         BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
     296           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     297             : 
     298           1 :         BOOST_CHECK(result_missing_parent.m_package_feerate == std::nullopt);
     299           1 :     }
     300             : 
     301             :     // Submit package with parent + child.
     302             :     {
     303           1 :         const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     304             :                                                            package_parent_child, /*test_accept=*/false);
     305           1 :         expected_pool_size += 2;
     306           1 :         BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
     307             :                             "Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
     308           1 :         auto it_parent = submit_parent_child.m_tx_results.find(tx_parent->GetHash());
     309           1 :         auto it_child = submit_parent_child.m_tx_results.find(tx_child->GetHash());
     310           1 :         BOOST_CHECK(it_parent != submit_parent_child.m_tx_results.end());
     311           1 :         BOOST_CHECK(it_parent->second.m_state.IsValid());
     312           1 :         BOOST_CHECK(it_child != submit_parent_child.m_tx_results.end());
     313           1 :         BOOST_CHECK(it_child->second.m_state.IsValid());
     314             : 
     315           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     316           1 :         BOOST_CHECK(m_node.mempool->exists(tx_parent->GetHash()));
     317           1 :         BOOST_CHECK(m_node.mempool->exists(tx_child->GetHash()));
     318             : 
     319             :         // Since both transactions have high feerates, they each passed validation individually.
     320             :         // Package validation was unnecessary, so there is no package feerate.
     321           1 :         BOOST_CHECK(submit_parent_child.m_package_feerate == std::nullopt);
     322           1 :     }
     323             : 
     324             :     // Already-in-mempool transactions should be detected and de-duplicated.
     325             :     {
     326           1 :         const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     327             :                                                       package_parent_child, /*test_accept=*/false);
     328           1 :         BOOST_CHECK_MESSAGE(submit_deduped.m_state.IsValid(),
     329             :                             "Package validation unexpectedly failed: " << submit_deduped.m_state.GetRejectReason());
     330           1 :         auto it_parent_deduped = submit_deduped.m_tx_results.find(tx_parent->GetHash());
     331           1 :         auto it_child_deduped = submit_deduped.m_tx_results.find(tx_child->GetHash());
     332           1 :         BOOST_CHECK(it_parent_deduped != submit_deduped.m_tx_results.end());
     333           1 :         BOOST_CHECK(it_parent_deduped->second.m_state.IsValid());
     334           1 :         BOOST_CHECK(it_parent_deduped->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
     335           1 :         BOOST_CHECK(it_child_deduped != submit_deduped.m_tx_results.end());
     336           1 :         BOOST_CHECK(it_child_deduped->second.m_state.IsValid());
     337           1 :         BOOST_CHECK(it_child_deduped->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
     338             : 
     339           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     340           1 :         BOOST_CHECK(m_node.mempool->exists(tx_parent->GetHash()));
     341           1 :         BOOST_CHECK(m_node.mempool->exists(tx_child->GetHash()));
     342             : 
     343           1 :         BOOST_CHECK(submit_deduped.m_package_feerate == std::nullopt);
     344           1 :     }
     345           1 : }
     346             : 
     347         149 : BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
     348             : {
     349           1 :     mineBlocks(5);
     350           1 :     MockMempoolMinFee(CFeeRate(5000));
     351           1 :     LOCK(::cs_main);
     352           1 :     size_t expected_pool_size = m_node.mempool->size();
     353           1 :     CKey child_key = GenerateRandomKey();
     354           1 :     CScript parent_spk = GetScriptForDestination(PKHash(child_key.GetPubKey()));
     355           1 :     CKey grandchild_key = GenerateRandomKey();
     356           1 :     CScript child_spk = GetScriptForDestination(PKHash(grandchild_key.GetPubKey()));
     357             : 
     358             :     // low-fee parent and high-fee child package
     359           1 :     const CAmount coinbase_value{500 * COIN};
     360           1 :     const CAmount parent_value{coinbase_value - low_fee_amt};
     361           1 :     const CAmount child_value{parent_value - COIN};
     362             : 
     363           1 :     Package package_cpfp;
     364           2 :     auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
     365           1 :                                                     /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
     366           1 :                                                     /*output_destination=*/parent_spk,
     367             :                                                     /*output_amount=*/parent_value, /*submit=*/false);
     368           1 :     CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
     369           1 :     package_cpfp.push_back(tx_parent);
     370             : 
     371           2 :     auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
     372           1 :                                                    /*input_height=*/101, /*input_signing_key=*/child_key,
     373           1 :                                                    /*output_destination=*/child_spk,
     374             :                                                    /*output_amount=*/child_value, /*submit=*/false);
     375           1 :     CTransactionRef tx_child = MakeTransactionRef(mtx_child);
     376           1 :     package_cpfp.push_back(tx_child);
     377             : 
     378             :     // Verify that the low-fee parent individually meets the min relay fee requirement.
     379             :     // This is important because Dash transactions are larger than Bitcoin's (no SegWit),
     380             :     // so we need a higher low_fee_amt to ensure the parent's fee exceeds minRelayTxFee.
     381           1 :     BOOST_CHECK_MESSAGE(::minRelayTxFee.GetFee(GetVirtualTransactionSize(*tx_parent)) <= low_fee_amt,
     382             :                         strprintf("low_fee_amt %d is below minRelayTxFee %d for parent vsize %d",
     383             :                                   low_fee_amt, ::minRelayTxFee.GetFee(GetVirtualTransactionSize(*tx_parent)),
     384             :                                   GetVirtualTransactionSize(*tx_parent)));
     385             :     // But the parent's fee should be below the mempool minimum feerate.
     386           1 :     BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_parent)) > low_fee_amt);
     387             : 
     388             :     // Package feerate is calculated using modified fees, and prioritisetransaction accepts negative
     389             :     // fee deltas. This should be taken into account. De-prioritise the parent transaction
     390             :     // to bring the package feerate to 0.
     391           1 :     m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), child_value - coinbase_value);
     392             :     {
     393           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     394           1 :         const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     395             :                                                    package_cpfp, /*test_accept=*/ false);
     396           1 :         BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_TX);
     397           1 :         BOOST_CHECK(submit_cpfp_deprio.m_state.IsInvalid());
     398           1 :         BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetHash())->second.m_state.GetResult(),
     399             :                           TxValidationResult::TX_MEMPOOL_POLICY);
     400           1 :         BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_child->GetHash()) == submit_cpfp_deprio.m_tx_results.end());
     401           1 :         BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetHash())->second.m_state.GetRejectReason() == "min relay fee not met");
     402           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     403           1 :     }
     404             : 
     405             :     // Clear the prioritisation of the parent transaction.
     406           2 :     WITH_LOCK(m_node.mempool->cs, m_node.mempool->ClearPrioritisation(tx_parent->GetHash()));
     407             : 
     408             :     // Package CPFP: Even though the parent's feerate is below the mempool minimum feerate, the
     409             :     // child pays enough for the package feerate to meet the threshold.
     410             :     {
     411           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     412           1 :         const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     413             :                                                    package_cpfp, /*test_accept=*/ false);
     414           1 :         expected_pool_size += 2;
     415           1 :         BOOST_CHECK_MESSAGE(submit_cpfp.m_state.IsValid(),
     416             :                             "Package validation unexpectedly failed: " << submit_cpfp.m_state.GetRejectReason());
     417           1 :         BOOST_CHECK_EQUAL(submit_cpfp.m_tx_results.size(), package_cpfp.size());
     418           1 :         auto it_parent = submit_cpfp.m_tx_results.find(tx_parent->GetHash());
     419           1 :         auto it_child = submit_cpfp.m_tx_results.find(tx_child->GetHash());
     420           1 :         BOOST_CHECK(it_parent != submit_cpfp.m_tx_results.end());
     421           1 :         BOOST_CHECK_MESSAGE(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID,
     422             :                             strprintf("Parent tx failed: %s", it_parent->second.m_state.GetRejectReason()));
     423           1 :         BOOST_CHECK(it_parent->second.m_base_fees.value() == coinbase_value - parent_value);
     424           1 :         BOOST_CHECK(it_child != submit_cpfp.m_tx_results.end());
     425           1 :         BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
     426           1 :         BOOST_CHECK(it_child->second.m_base_fees.value() == COIN);
     427             : 
     428           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     429           1 :         BOOST_CHECK(m_node.mempool->exists(tx_parent->GetHash()));
     430           1 :         BOOST_CHECK(m_node.mempool->exists(tx_child->GetHash()));
     431             : 
     432           2 :         const CFeeRate expected_feerate(coinbase_value - child_value,
     433           1 :                                         GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child));
     434           1 :         BOOST_CHECK(expected_feerate.GetFeePerK() > 1000);
     435           1 :         BOOST_CHECK(submit_cpfp.m_package_feerate.has_value());
     436           1 :         BOOST_CHECK_MESSAGE(submit_cpfp.m_package_feerate.value() == expected_feerate,
     437             :                             strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
     438             :                                       submit_cpfp.m_package_feerate.value().ToString()));
     439           1 :     }
     440             : 
     441             :     // Just because we allow low-fee parents doesn't mean we allow low-feerate packages.
     442             :     // The mempool minimum feerate is 5sat/vB, but this package just pays 1700 satoshis total.
     443             :     // The child fees would be able to pay for itself, but isn't enough for the entire package.
     444             :     // Note: Dash transactions are larger than Bitcoin's (no SegWit discount, ~225 bytes for P2PKH),
     445             :     // so fees are higher than Bitcoin's test values to ensure they exceed minRelayTxFee.
     446           1 :     Package package_still_too_low;
     447           1 :     const CAmount parent_fee{500};
     448           1 :     const CAmount child_fee{1200};
     449           2 :     auto mtx_parent_cheap = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
     450           1 :                                                           /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
     451           1 :                                                           /*output_destination=*/parent_spk,
     452             :                                                           /*output_amount=*/coinbase_value - parent_fee, /*submit=*/false);
     453           1 :     CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap);
     454           1 :     package_still_too_low.push_back(tx_parent_cheap);
     455           1 :     BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) > parent_fee);
     456           1 :     BOOST_CHECK(::minRelayTxFee.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee);
     457             : 
     458           2 :     auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0,
     459           1 :                                                          /*input_height=*/101, /*input_signing_key=*/child_key,
     460           1 :                                                          /*output_destination=*/child_spk,
     461             :                                                          /*output_amount=*/coinbase_value - parent_fee - child_fee, /*submit=*/false);
     462           1 :     CTransactionRef tx_child_cheap = MakeTransactionRef(mtx_child_cheap);
     463           1 :     package_still_too_low.push_back(tx_child_cheap);
     464           1 :     BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_child_cheap)) <= child_fee);
     465           1 :     BOOST_CHECK(m_node.mempool->GetMinFee(0).GetFee(GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)) > parent_fee + child_fee);
     466             : 
     467             :     // Cheap package should fail with package-fee-too-low.
     468             :     {
     469           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     470           1 :         const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     471             :                                                    package_still_too_low, /*test_accept=*/false);
     472           1 :         BOOST_CHECK_MESSAGE(submit_package_too_low.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
     473           1 :         BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
     474           1 :         BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low");
     475           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     476           2 :         const CFeeRate expected_feerate(parent_fee + child_fee,
     477           1 :             GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
     478           1 :         BOOST_CHECK(submit_package_too_low.m_package_feerate.has_value());
     479           1 :         BOOST_CHECK_MESSAGE(submit_package_too_low.m_package_feerate.value() == expected_feerate,
     480             :                             strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
     481             :                                       submit_package_too_low.m_package_feerate.value().ToString()));
     482           1 :     }
     483             : 
     484             :     // Package feerate includes the modified fees of the transactions.
     485             :     // This means a child with its fee delta from prioritisetransaction can pay for a parent.
     486           1 :     m_node.mempool->PrioritiseTransaction(tx_child_cheap->GetHash(), 1 * COIN);
     487             :     // Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed.
     488             :     {
     489           1 :         const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     490             :                                                                   package_still_too_low, /*test_accept=*/false);
     491           1 :         expected_pool_size += 2;
     492           1 :         BOOST_CHECK_MESSAGE(submit_prioritised_package.m_state.IsValid(),
     493             :                 "Package validation unexpectedly failed" << submit_prioritised_package.m_state.GetRejectReason());
     494           2 :         const CFeeRate expected_feerate(1 * COIN + parent_fee + child_fee,
     495           1 :             GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
     496           1 :         BOOST_CHECK(submit_prioritised_package.m_package_feerate.has_value());
     497           1 :         BOOST_CHECK_MESSAGE(submit_prioritised_package.m_package_feerate.value() == expected_feerate,
     498             :                             strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(),
     499             :                                       submit_prioritised_package.m_package_feerate.value().ToString()));
     500           1 :         BOOST_CHECK_EQUAL(submit_prioritised_package.m_tx_results.size(), package_still_too_low.size());
     501           1 :         auto it_parent = submit_prioritised_package.m_tx_results.find(tx_parent_cheap->GetHash());
     502           1 :         auto it_child = submit_prioritised_package.m_tx_results.find(tx_child_cheap->GetHash());
     503           1 :         BOOST_CHECK(it_parent != submit_prioritised_package.m_tx_results.end());
     504           1 :         BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
     505           1 :         BOOST_CHECK(it_parent->second.m_base_fees.value() == parent_fee);
     506           1 :         BOOST_CHECK(it_child != submit_prioritised_package.m_tx_results.end());
     507           1 :         BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
     508           1 :         BOOST_CHECK(it_child->second.m_base_fees.value() == child_fee);
     509           1 :     }
     510             : 
     511             :     // Package feerate is calculated without topology in mind; it's just aggregating fees and sizes.
     512             :     // However, this should not allow parents to pay for children. Each transaction should be
     513             :     // validated individually first, eliminating sufficient-feerate parents before they are unfairly
     514             :     // included in the package feerate. It's also important that the low-fee child doesn't prevent
     515             :     // the parent from being accepted.
     516           1 :     Package package_rich_parent;
     517           1 :     const CAmount high_parent_fee{1 * COIN};
     518           2 :     auto mtx_parent_rich = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0,
     519           1 :                                                          /*input_height=*/0, /*input_signing_key=*/coinbaseKey,
     520           1 :                                                          /*output_destination=*/parent_spk,
     521             :                                                          /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false);
     522           1 :     CTransactionRef tx_parent_rich = MakeTransactionRef(mtx_parent_rich);
     523           1 :     package_rich_parent.push_back(tx_parent_rich);
     524             : 
     525           2 :     auto mtx_child_poor = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_rich, /*input_vout=*/0,
     526           1 :                                                         /*input_height=*/101, /*input_signing_key=*/child_key,
     527           1 :                                                         /*output_destination=*/child_spk,
     528             :                                                         /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false);
     529           1 :     CTransactionRef tx_child_poor = MakeTransactionRef(mtx_child_poor);
     530           1 :     package_rich_parent.push_back(tx_child_poor);
     531             : 
     532             :     // Parent pays 1 BTC and child pays none. The parent should be accepted without the child.
     533             :     {
     534           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     535           1 :         const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
     536             :                                                           package_rich_parent, /*test_accept=*/false);
     537           1 :         expected_pool_size += 1;
     538           1 :         BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
     539             : 
     540             :         // The child would have been validated on its own and failed, then submitted as a "package" of 1.
     541           1 :         BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
     542           1 :         BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "transaction failed");
     543             : 
     544           1 :         auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetHash());
     545           1 :         BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end());
     546           1 :         BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
     547           1 :         BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == "");
     548           1 :         BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee,
     549             :                 strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
     550           1 :         auto it_child = submit_rich_parent.m_tx_results.find(tx_child_poor->GetHash());
     551           1 :         BOOST_CHECK(it_child != submit_rich_parent.m_tx_results.end());
     552           1 :         BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
     553           1 :         BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);
     554           1 :         BOOST_CHECK(it_child->second.m_state.GetRejectReason() == "min relay fee not met");
     555             : 
     556           1 :         BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
     557           1 :         BOOST_CHECK(m_node.mempool->exists(tx_parent_rich->GetHash()));
     558           1 :         BOOST_CHECK(!m_node.mempool->exists(tx_child_poor->GetHash()));
     559           1 :     }
     560           1 : }
     561         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16