LCOV - code coverage report
Current view: top level - src/test - miniminer_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 99.8 % 446 445
Test Date: 2024-11-04 05:10:19 Functions: 100.0 % 16 16
Branches: 51.6 % 1928 994

             Branch data     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                 :             : #include <node/mini_miner.h>
       5                 :             : #include <random.h>
       6                 :             : #include <txmempool.h>
       7                 :             : #include <util/time.h>
       8                 :             : 
       9                 :             : #include <test/util/setup_common.h>
      10                 :             : #include <test/util/txmempool.h>
      11                 :             : 
      12                 :             : #include <boost/test/unit_test.hpp>
      13                 :             : #include <optional>
      14                 :             : #include <vector>
      15                 :             : 
      16                 :             : BOOST_FIXTURE_TEST_SUITE(miniminer_tests, TestingSetup)
      17                 :             : 
      18                 :             : const CAmount low_fee{CENT/2000}; // 500 ṩ
      19                 :             : const CAmount med_fee{CENT/200}; // 5000 ṩ
      20                 :             : const CAmount high_fee{CENT/10}; // 100_000 ṩ
      21                 :             : 
      22                 :             : 
      23                 :         624 : static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, size_t num_outputs)
      24                 :             : {
      25                 :         624 :     CMutableTransaction tx = CMutableTransaction();
      26         [ +  - ]:         624 :     tx.vin.resize(inputs.size());
      27         [ +  - ]:         624 :     tx.vout.resize(num_outputs);
      28         [ +  + ]:        1301 :     for (size_t i = 0; i < inputs.size(); ++i) {
      29                 :         677 :         tx.vin[i].prevout = inputs[i];
      30                 :             :     }
      31         [ +  + ]:        1312 :     for (size_t i = 0; i < num_outputs; ++i) {
      32   [ +  -  +  - ]:         688 :         tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
      33                 :             :         // The actual input and output values of these transactions don't really
      34                 :             :         // matter, since all accounting will use the entries' cached fees.
      35                 :         688 :         tx.vout[i].nValue = COIN;
      36                 :             :     }
      37         [ +  - ]:        1248 :     return MakeTransactionRef(tx);
      38                 :         624 : }
      39                 :             : 
      40                 :          51 : static inline bool sanity_check(const std::vector<CTransactionRef>& transactions,
      41                 :             :                                 const std::map<COutPoint, CAmount>& bumpfees)
      42                 :             : {
      43                 :             :     // No negative bumpfees.
      44   [ +  -  +  + ]:         345 :     for (const auto& [outpoint, fee] : bumpfees) {
      45         [ +  - ]:         294 :         if (fee < 0) return false;
      46         [ +  + ]:         294 :         if (fee == 0) continue;
      47                 :          66 :         auto outpoint_ = outpoint; // structured bindings can't be captured in C++17, so we need to use a variable
      48   [ +  +  +  - ]:         357 :         const bool found = std::any_of(transactions.cbegin(), transactions.cend(), [&](const auto& tx) {
      49   [ +  +  -  + ]:         291 :             return outpoint_.hash == tx->GetHash() && outpoint_.n < tx->vout.size();
      50                 :             :         });
      51         [ +  - ]:          66 :         if (!found) return false;
      52                 :             :     }
      53         [ +  + ]:         459 :     for (const auto& tx : transactions) {
      54                 :             :         // If tx has multiple outputs, they must all have the same bumpfee (if they exist).
      55         [ +  + ]:         408 :         if (tx->vout.size() > 1) {
      56                 :         216 :             std::set<CAmount> distinct_bumpfees;
      57         [ +  + ]:         654 :             for (size_t i{0}; i < tx->vout.size(); ++i) {
      58                 :         438 :                 const auto bumpfee = bumpfees.find(COutPoint{tx->GetHash(), static_cast<uint32_t>(i)});
      59   [ +  +  +  - ]:         438 :                 if (bumpfee != bumpfees.end()) distinct_bumpfees.insert(bumpfee->second);
      60                 :             :             }
      61         [ -  + ]:         216 :             if (distinct_bumpfees.size() > 1) return false;
      62                 :         216 :         }
      63                 :             :     }
      64                 :             :     return true;
      65                 :             : }
      66                 :             : 
      67                 :             : template <typename Key, typename Value>
      68                 :         140 : Value Find(const std::map<Key, Value>& map, const Key& key)
      69                 :             : {
      70                 :         140 :     auto it = map.find(key);
      71   [ +  -  +  - ]:         280 :     BOOST_CHECK_MESSAGE(it != map.end(), strprintf("Cannot find %s", key.ToString()));
      72                 :         140 :     return it->second;
      73                 :             : }
      74                 :             : 
      75   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(miniminer_negative, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
      76                 :             : {
      77                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
      78         [ +  - ]:           1 :     LOCK2(::cs_main, pool.cs);
      79                 :           1 :     TestMemPoolEntryHelper entry;
      80                 :             : 
      81                 :             :     // Create a transaction that will be prioritised to have a negative modified fee.
      82                 :           1 :     const CAmount positive_base_fee{1000};
      83                 :           1 :     const CAmount negative_fee_delta{-50000};
      84                 :           1 :     const CAmount negative_modified_fees{positive_base_fee + negative_fee_delta};
      85   [ +  -  +  -  :           2 :     BOOST_CHECK(negative_modified_fees < 0);
                   +  - ]
      86   [ +  -  +  -  :           2 :     const auto tx_mod_negative = make_tx({COutPoint{m_coinbase_txns[4]->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
      87   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(positive_base_fee).FromTx(tx_mod_negative));
      88         [ +  - ]:           1 :     pool.PrioritiseTransaction(tx_mod_negative->GetHash(), negative_fee_delta);
      89         [ +  - ]:           1 :     const COutPoint only_outpoint{tx_mod_negative->GetHash(), 0};
      90                 :             : 
      91                 :             :     // When target feerate is 0, transactions with negative fees are not selected.
      92   [ +  -  +  -  :           2 :     node::MiniMiner mini_miner_target0(pool, {only_outpoint});
                   +  - ]
      93   [ +  -  +  -  :           2 :     BOOST_CHECK(mini_miner_target0.IsReadyToCalculate());
                   +  - ]
      94         [ +  - ]:           1 :     const CFeeRate feerate_zero(0);
      95         [ +  - ]:           1 :     mini_miner_target0.BuildMockTemplate(feerate_zero);
      96                 :             :     // Check the quit condition:
      97   [ +  -  +  -  :           2 :     BOOST_CHECK(negative_modified_fees < feerate_zero.GetFee(Assert(pool.GetEntry(tx_mod_negative->GetHash()))->GetTxSize()));
          +  -  +  -  +  
             -  +  -  +  
                      - ]
      98   [ +  -  +  -  :           3 :     BOOST_CHECK(mini_miner_target0.GetMockTemplateTxids().empty());
             +  -  +  - ]
      99                 :             : 
     100                 :             :     // With no target feerate, the template includes all transactions, even negative feerate ones.
     101   [ +  -  +  -  :           2 :     node::MiniMiner mini_miner_no_target(pool, {only_outpoint});
                   +  - ]
     102   [ +  -  +  -  :           2 :     BOOST_CHECK(mini_miner_no_target.IsReadyToCalculate());
                   +  - ]
     103         [ +  - ]:           1 :     mini_miner_no_target.BuildMockTemplate(std::nullopt);
     104         [ +  - ]:           1 :     const auto template_txids{mini_miner_no_target.GetMockTemplateTxids()};
     105   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(template_txids.size(), 1);
     106   [ +  -  +  - ]:           2 :     BOOST_CHECK(template_txids.count(tx_mod_negative->GetHash().ToUint256()) > 0);
     107   [ +  -  +  -  :           4 : }
                   +  - ]
     108                 :             : 
     109   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(miniminer_1p1c, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     110                 :             : {
     111                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     112         [ +  - ]:           1 :     LOCK2(::cs_main, pool.cs);
     113                 :           1 :     TestMemPoolEntryHelper entry;
     114                 :             : 
     115                 :             :     // Create a parent tx0 and child tx1 with normal fees:
     116   [ +  -  +  -  :           2 :     const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     117   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(med_fee).FromTx(tx0));
     118   [ +  -  +  -  :           2 :     const auto tx1 = make_tx({COutPoint{tx0->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
     119   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
     120                 :             : 
     121                 :             :     // Create a low-feerate parent tx2 and high-feerate child tx3 (cpfp)
     122   [ +  -  +  -  :           2 :     const auto tx2 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     123   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx2));
     124   [ +  -  +  -  :           2 :     const auto tx3 = make_tx({COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
     125   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
     126                 :             : 
     127                 :             :     // Create a parent tx4 and child tx5 where both have low fees
     128   [ +  -  +  -  :           2 :     const auto tx4 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     129   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx4));
     130   [ +  -  +  -  :           2 :     const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
     131   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
     132                 :           1 :     const CAmount tx5_delta{CENT/100};
     133                 :             :     // Make tx5's modified fee much higher than its base fee. This should cause it to pass
     134                 :             :     // the fee-related checks despite being low-feerate.
     135         [ +  - ]:           1 :     pool.PrioritiseTransaction(tx5->GetHash(), tx5_delta);
     136                 :           1 :     const CAmount tx5_mod_fee{low_fee + tx5_delta};
     137                 :             : 
     138                 :             :     // Create a high-feerate parent tx6, low-feerate child tx7
     139   [ +  -  +  -  :           2 :     const auto tx6 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     140   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx6));
     141   [ +  -  +  -  :           2 :     const auto tx7 = make_tx({COutPoint{tx6->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
     142   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx7));
     143                 :             : 
     144         [ +  - ]:           1 :     std::vector<COutPoint> all_unspent_outpoints({
     145                 :             :         COutPoint{tx0->GetHash(), 1},
     146                 :             :         COutPoint{tx1->GetHash(), 0},
     147                 :             :         COutPoint{tx2->GetHash(), 1},
     148                 :             :         COutPoint{tx3->GetHash(), 0},
     149                 :             :         COutPoint{tx4->GetHash(), 1},
     150                 :             :         COutPoint{tx5->GetHash(), 0},
     151                 :             :         COutPoint{tx6->GetHash(), 1},
     152                 :             :         COutPoint{tx7->GetHash(), 0}
     153         [ +  - ]:           1 :     });
     154   [ +  -  +  -  :           9 :     for (const auto& outpoint : all_unspent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
             +  -  +  + ]
     155                 :             : 
     156         [ +  - ]:           1 :     std::vector<COutPoint> all_spent_outpoints({
     157                 :             :         COutPoint{tx0->GetHash(), 0},
     158                 :             :         COutPoint{tx2->GetHash(), 0},
     159                 :             :         COutPoint{tx4->GetHash(), 0},
     160                 :             :         COutPoint{tx6->GetHash(), 0}
     161         [ +  - ]:           1 :     });
     162   [ +  -  +  -  :           5 :     for (const auto& outpoint : all_spent_outpoints) BOOST_CHECK(pool.GetConflictTx(outpoint) != nullptr);
             +  -  +  + ]
     163                 :             : 
     164         [ +  - ]:           1 :     std::vector<COutPoint> all_parent_outputs({
     165                 :             :         COutPoint{tx0->GetHash(), 0},
     166                 :             :         COutPoint{tx0->GetHash(), 1},
     167                 :             :         COutPoint{tx2->GetHash(), 0},
     168                 :             :         COutPoint{tx2->GetHash(), 1},
     169                 :             :         COutPoint{tx4->GetHash(), 0},
     170                 :             :         COutPoint{tx4->GetHash(), 1},
     171                 :             :         COutPoint{tx6->GetHash(), 0},
     172                 :             :         COutPoint{tx6->GetHash(), 1}
     173         [ +  - ]:           1 :     });
     174                 :             : 
     175                 :             : 
     176   [ +  +  +  -  :           9 :     std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
             -  -  -  - ]
     177                 :           1 :     struct TxDimensions {
     178                 :             :         int32_t vsize; CAmount mod_fee; CFeeRate feerate;
     179                 :             :     };
     180                 :           1 :     std::map<uint256, TxDimensions> tx_dims;
     181         [ +  + ]:           9 :     for (const auto& tx : all_transactions) {
     182   [ +  -  +  - ]:           8 :         const auto& entry{*Assert(pool.GetEntry(tx->GetHash()))};
     183   [ +  -  +  -  :           8 :         tx_dims.emplace(tx->GetHash(), TxDimensions{entry.GetTxSize(), entry.GetModifiedFee(),
             +  -  +  - ]
     184   [ +  -  +  - ]:           8 :                                               CFeeRate(entry.GetModifiedFee(), entry.GetTxSize())});
     185                 :             :     }
     186                 :             : 
     187         [ +  - ]:           1 :     const std::vector<CFeeRate> various_normal_feerates({CFeeRate(0), CFeeRate(500), CFeeRate(999),
     188                 :             :                                                          CFeeRate(1000), CFeeRate(2000), CFeeRate(2500),
     189                 :             :                                                          CFeeRate(3333), CFeeRate(7800), CFeeRate(11199),
     190         [ +  - ]:           1 :                                                          CFeeRate(23330), CFeeRate(50000), CFeeRate(5*CENT)});
     191                 :             : 
     192                 :             :     // All nonexistent entries have a bumpfee of zero, regardless of feerate
     193         [ +  - ]:           1 :     std::vector<COutPoint> nonexistent_outpoints({ COutPoint{Txid::FromUint256(GetRandHash()), 0}, COutPoint{Txid::FromUint256(GetRandHash()), 3} });
     194   [ +  -  +  -  :           3 :     for (const auto& outpoint : nonexistent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
             +  -  +  + ]
     195         [ +  + ]:          13 :     for (const auto& feerate : various_normal_feerates) {
     196         [ +  - ]:          12 :         node::MiniMiner mini_miner(pool, nonexistent_outpoints);
     197   [ +  -  +  -  :          24 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
                   +  - ]
     198         [ +  - ]:          12 :         auto bump_fees = mini_miner.CalculateBumpFees(feerate);
     199   [ +  -  +  -  :          24 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
                   +  - ]
     200   [ +  -  +  -  :          24 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
             +  -  +  - ]
     201   [ +  -  +  - ]:          24 :         BOOST_CHECK(bump_fees.size() == nonexistent_outpoints.size());
     202         [ +  + ]:          36 :         for (const auto& outpoint: nonexistent_outpoints) {
     203                 :          24 :             auto it = bump_fees.find(outpoint);
     204   [ +  -  +  -  :          48 :             BOOST_CHECK(it != bump_fees.end());
                   +  - ]
     205   [ +  -  +  - ]:          24 :             BOOST_CHECK_EQUAL(it->second, 0);
     206                 :             :         }
     207                 :          12 :     }
     208                 :             : 
     209                 :             :     // Gather bump fees for all available UTXOs.
     210         [ +  + ]:          13 :     for (const auto& target_feerate : various_normal_feerates) {
     211         [ +  - ]:          12 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     212   [ +  -  +  -  :          24 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
                   +  - ]
     213         [ +  - ]:          12 :         auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
     214   [ +  -  +  -  :          24 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
                   +  - ]
     215   [ +  -  +  -  :          24 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
             +  -  +  - ]
     216   [ +  -  +  - ]:          12 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     217                 :             : 
     218                 :             :         // Check tx0 bumpfee: no other bumper.
     219         [ +  - ]:          12 :         const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
     220         [ +  - ]:          12 :         CAmount bumpfee0 = Find(bump_fees, COutPoint{tx0->GetHash(), 1});
     221         [ +  + ]:          12 :         if (target_feerate <= tx0_dimensions.feerate) {
     222   [ +  -  +  - ]:          11 :             BOOST_CHECK_EQUAL(bumpfee0, 0);
     223                 :             :         } else {
     224                 :             :             // Difference is fee to bump tx0 from current to target feerate.
     225   [ +  -  +  -  :           1 :             BOOST_CHECK_EQUAL(bumpfee0, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
                   +  - ]
     226                 :             :         }
     227                 :             : 
     228                 :             :         // Check tx2 bumpfee: assisted by tx3.
     229                 :          12 :         const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
     230         [ +  - ]:          12 :         const TxDimensions& tx3_dimensions = tx_dims.find(tx3->GetHash())->second;
     231         [ +  - ]:          12 :         const CFeeRate tx2_feerate = CFeeRate(tx2_dimensions.mod_fee + tx3_dimensions.mod_fee, tx2_dimensions.vsize + tx3_dimensions.vsize);
     232         [ +  - ]:          12 :         CAmount bumpfee2 = Find(bump_fees, COutPoint{tx2->GetHash(), 1});
     233         [ +  + ]:          12 :         if (target_feerate <= tx2_feerate) {
     234                 :             :             // As long as target feerate is below tx3's ancestor feerate, there is no bump fee.
     235   [ +  -  +  - ]:          11 :             BOOST_CHECK_EQUAL(bumpfee2, 0);
     236                 :             :         } else {
     237                 :             :             // Difference is fee to bump tx2 from current to target feerate, without tx3.
     238   [ +  -  +  -  :           1 :             BOOST_CHECK_EQUAL(bumpfee2, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
                   +  - ]
     239                 :             :         }
     240                 :             : 
     241                 :             :         // If tx5’s modified fees are sufficient for tx4 and tx5 to be picked
     242                 :             :         // into the block, our prospective new transaction would not need to
     243                 :             :         // bump tx4 when using tx4’s second output. If however even tx5’s
     244                 :             :         // modified fee (which essentially indicates "effective feerate") is
     245                 :             :         // not sufficient to bump tx4, using the second output of tx4 would
     246                 :             :         // require our transaction to bump tx4 from scratch since we evaluate
     247                 :             :         // transaction packages per ancestor sets and do not consider multiple
     248                 :             :         // children’s fees.
     249                 :          12 :         const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
     250         [ +  - ]:          12 :         const TxDimensions& tx5_dimensions = tx_dims.find(tx5->GetHash())->second;
     251         [ +  - ]:          12 :         const CFeeRate tx4_feerate = CFeeRate(tx4_dimensions.mod_fee + tx5_dimensions.mod_fee, tx4_dimensions.vsize + tx5_dimensions.vsize);
     252         [ +  - ]:          12 :         CAmount bumpfee4 = Find(bump_fees, COutPoint{tx4->GetHash(), 1});
     253         [ +  + ]:          12 :         if (target_feerate <= tx4_feerate) {
     254                 :             :             // As long as target feerate is below tx5's ancestor feerate, there is no bump fee.
     255   [ +  -  +  - ]:          11 :             BOOST_CHECK_EQUAL(bumpfee4, 0);
     256                 :             :         } else {
     257                 :             :             // Difference is fee to bump tx4 from current to target feerate, without tx5.
     258   [ +  -  +  -  :           1 :             BOOST_CHECK_EQUAL(bumpfee4, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
                   +  - ]
     259                 :             :         }
     260                 :          12 :     }
     261                 :             :     // Spent outpoints should usually not be requested as they would not be
     262                 :             :     // considered available. However, when they are explicitly requested, we
     263                 :             :     // can calculate their bumpfee to facilitate RBF-replacements
     264         [ +  + ]:          13 :     for (const auto& target_feerate : various_normal_feerates) {
     265         [ +  - ]:          12 :         node::MiniMiner mini_miner_all_spent(pool, all_spent_outpoints);
     266   [ +  -  +  -  :          24 :         BOOST_CHECK(mini_miner_all_spent.IsReadyToCalculate());
                   +  - ]
     267         [ +  - ]:          12 :         auto bump_fees_all_spent = mini_miner_all_spent.CalculateBumpFees(target_feerate);
     268   [ +  -  +  -  :          24 :         BOOST_CHECK(!mini_miner_all_spent.IsReadyToCalculate());
                   +  - ]
     269   [ +  -  +  - ]:          12 :         BOOST_CHECK_EQUAL(bump_fees_all_spent.size(), all_spent_outpoints.size());
     270         [ +  - ]:          12 :         node::MiniMiner mini_miner_all_parents(pool, all_parent_outputs);
     271   [ +  -  +  -  :          24 :         BOOST_CHECK(mini_miner_all_parents.IsReadyToCalculate());
                   +  - ]
     272         [ +  - ]:          12 :         auto bump_fees_all_parents = mini_miner_all_parents.CalculateBumpFees(target_feerate);
     273   [ +  -  +  -  :          24 :         BOOST_CHECK(!mini_miner_all_parents.IsReadyToCalculate());
                   +  - ]
     274   [ +  -  +  - ]:          12 :         BOOST_CHECK_EQUAL(bump_fees_all_parents.size(), all_parent_outputs.size());
     275   [ +  -  +  -  :          60 :         for (auto& bump_fees : {bump_fees_all_parents, bump_fees_all_spent}) {
             +  +  -  - ]
     276                 :             :             // For all_parents case, both outputs from the parent should have the same bump fee,
     277                 :             :             // even though only one of them is in a to-be-replaced transaction.
     278   [ +  -  +  -  :          48 :             BOOST_CHECK(sanity_check(all_transactions, bump_fees));
                   +  - ]
     279                 :             : 
     280                 :             :             // Check tx0 bumpfee: no other bumper.
     281         [ +  - ]:          24 :             const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
     282         [ +  - ]:          24 :             CAmount it0_spent = Find(bump_fees, COutPoint{tx0->GetHash(), 0});
     283         [ +  + ]:          24 :             if (target_feerate <= tx0_dimensions.feerate) {
     284   [ +  -  +  - ]:          22 :                 BOOST_CHECK_EQUAL(it0_spent, 0);
     285                 :             :             } else {
     286                 :             :                 // Difference is fee to bump tx0 from current to target feerate.
     287   [ +  -  +  -  :           2 :                 BOOST_CHECK_EQUAL(it0_spent, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
                   +  - ]
     288                 :             :             }
     289                 :             : 
     290                 :             :             // Check tx2 bumpfee: no other bumper, because tx3 is to-be-replaced.
     291         [ +  - ]:          24 :             const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
     292                 :          24 :             const CFeeRate tx2_feerate_unbumped = tx2_dimensions.feerate;
     293         [ +  - ]:          24 :             auto it2_spent = Find(bump_fees, COutPoint{tx2->GetHash(), 0});
     294         [ +  + ]:          24 :             if (target_feerate <= tx2_feerate_unbumped) {
     295   [ +  -  +  - ]:          14 :                 BOOST_CHECK_EQUAL(it2_spent, 0);
     296                 :             :             } else {
     297                 :             :                 // Difference is fee to bump tx2 from current to target feerate, without tx3.
     298   [ +  -  +  -  :          10 :                 BOOST_CHECK_EQUAL(it2_spent, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
                   +  - ]
     299                 :             :             }
     300                 :             : 
     301                 :             :             // Check tx4 bumpfee: no other bumper, because tx5 is to-be-replaced.
     302         [ +  - ]:          24 :             const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
     303                 :          24 :             const CFeeRate tx4_feerate_unbumped = tx4_dimensions.feerate;
     304         [ +  - ]:          24 :             auto it4_spent = Find(bump_fees, COutPoint{tx4->GetHash(), 0});
     305         [ +  + ]:          24 :             if (target_feerate <= tx4_feerate_unbumped) {
     306   [ +  -  +  - ]:          14 :                 BOOST_CHECK_EQUAL(it4_spent, 0);
     307                 :             :             } else {
     308                 :             :                 // Difference is fee to bump tx4 from current to target feerate, without tx5.
     309   [ +  -  +  -  :          10 :                 BOOST_CHECK_EQUAL(it4_spent, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
                   +  - ]
     310                 :             :             }
     311   [ +  +  -  - ]:          36 :         }
     312                 :          12 :     }
     313                 :             : 
     314                 :             :     // Check m_inclusion_order for equivalent mempool- and manually-constructed MiniMiners.
     315                 :             :     // (We cannot check bump fees in manually-constructed MiniMiners because it doesn't know what
     316                 :             :     // outpoints are requested).
     317                 :           1 :     std::vector<node::MiniMinerMempoolEntry> miniminer_info;
     318                 :           1 :     {
     319         [ +  - ]:           1 :         const int32_t tx0_vsize{tx_dims.at(tx0->GetHash()).vsize};
     320         [ +  - ]:           1 :         const int32_t tx1_vsize{tx_dims.at(tx1->GetHash()).vsize};
     321         [ +  - ]:           1 :         const int32_t tx2_vsize{tx_dims.at(tx2->GetHash()).vsize};
     322         [ +  - ]:           1 :         const int32_t tx3_vsize{tx_dims.at(tx3->GetHash()).vsize};
     323         [ +  - ]:           1 :         const int32_t tx4_vsize{tx_dims.at(tx4->GetHash()).vsize};
     324         [ +  - ]:           1 :         const int32_t tx5_vsize{tx_dims.at(tx5->GetHash()).vsize};
     325         [ +  - ]:           1 :         const int32_t tx6_vsize{tx_dims.at(tx6->GetHash()).vsize};
     326         [ +  - ]:           1 :         const int32_t tx7_vsize{tx_dims.at(tx7->GetHash()).vsize};
     327                 :             : 
     328         [ +  - ]:           1 :         miniminer_info.emplace_back(tx0,/*vsize_self=*/tx0_vsize,/*vsize_ancestor=*/tx0_vsize,/*fee_self=*/med_fee,/*fee_ancestor=*/med_fee);
     329         [ +  - ]:           1 :         miniminer_info.emplace_back(tx1,               tx1_vsize,       tx0_vsize + tx1_vsize,             med_fee,               2*med_fee);
     330         [ +  - ]:           1 :         miniminer_info.emplace_back(tx2,               tx2_vsize,                   tx2_vsize,             low_fee,                 low_fee);
     331         [ +  - ]:           1 :         miniminer_info.emplace_back(tx3,               tx3_vsize,       tx2_vsize + tx3_vsize,            high_fee,      low_fee + high_fee);
     332         [ +  - ]:           1 :         miniminer_info.emplace_back(tx4,               tx4_vsize,                   tx4_vsize,             low_fee,                 low_fee);
     333         [ +  - ]:           1 :         miniminer_info.emplace_back(tx5,               tx5_vsize,       tx4_vsize + tx5_vsize,         tx5_mod_fee,   low_fee + tx5_mod_fee);
     334         [ +  - ]:           1 :         miniminer_info.emplace_back(tx6,               tx6_vsize,                   tx6_vsize,            high_fee,                high_fee);
     335         [ +  - ]:           1 :         miniminer_info.emplace_back(tx7,               tx7_vsize,       tx6_vsize + tx7_vsize,             low_fee,      high_fee + low_fee);
     336                 :             :     }
     337         [ +  - ]:           1 :     std::map<Txid, std::set<Txid>> descendant_caches;
     338   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx0->GetHash(), std::set<Txid>{tx0->GetHash(), tx1->GetHash()});
     339   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx1->GetHash(), std::set<Txid>{tx1->GetHash()});
     340   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx2->GetHash(), std::set<Txid>{tx2->GetHash(), tx3->GetHash()});
     341   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx3->GetHash(), std::set<Txid>{tx3->GetHash()});
     342   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx4->GetHash(), std::set<Txid>{tx4->GetHash(), tx5->GetHash()});
     343   [ +  -  +  -  :           1 :     descendant_caches.emplace(tx5->GetHash(), std::set<Txid>{tx5->GetHash()});
                   +  - ]
     344   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx6->GetHash(), std::set<Txid>{tx6->GetHash(), tx7->GetHash()});
     345   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx7->GetHash(), std::set<Txid>{tx7->GetHash()});
     346                 :             : 
     347         [ +  - ]:           1 :     node::MiniMiner miniminer_manual(miniminer_info, descendant_caches);
     348                 :             :     // Use unspent outpoints to avoid entries being omitted.
     349         [ +  - ]:           1 :     node::MiniMiner miniminer_pool(pool, all_unspent_outpoints);
     350   [ +  -  +  -  :           2 :     BOOST_CHECK(miniminer_manual.IsReadyToCalculate());
                   +  - ]
     351   [ +  -  +  -  :           2 :     BOOST_CHECK(miniminer_pool.IsReadyToCalculate());
                   +  - ]
     352   [ +  -  +  -  :           5 :     for (const auto& sequences : {miniminer_manual.Linearize(), miniminer_pool.Linearize()}) {
             +  +  -  - ]
     353                 :             :         // tx6 is selected first: high feerate with no parents to bump
     354   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx6->GetHash()), 0);
                   +  - ]
     355                 :             : 
     356                 :             :         // tx2 + tx3 CPFP are selected next
     357   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx2->GetHash()), 1);
                   +  - ]
     358   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx3->GetHash()), 1);
                   +  - ]
     359                 :             : 
     360                 :             :         // tx4 + prioritised tx5 CPFP
     361   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx4->GetHash()), 2);
                   +  - ]
     362   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx5->GetHash()), 2);
                   +  - ]
     363                 :             : 
     364   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx0->GetHash()), 3);
                   +  - ]
     365   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx1->GetHash()), 3);
                   +  - ]
     366                 :             : 
     367                 :             : 
     368                 :             :         // tx7 is selected last: low feerate with no children
     369   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx7->GetHash()), 4);
                   +  - ]
     370   [ +  +  -  - ]:           3 :     }
     371   [ +  -  +  -  :          20 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
             +  -  +  - ]
     372                 :             : 
     373   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(miniminer_overlap, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     374                 :             : {
     375                 :             : /*      Tx graph for `miniminer_overlap` unit test:
     376                 :             :  *
     377                 :             :  *     coinbase_tx [mined]        ... block-chain
     378                 :             :  *  -------------------------------------------------
     379                 :             :  *      /   |   \          \      ... mempool
     380                 :             :  *     /    |    \         |
     381                 :             :  *   tx0   tx1   tx2      tx4
     382                 :             :  *  [low] [med] [high]   [high]
     383                 :             :  *     \    |    /         |
     384                 :             :  *      \   |   /         tx5
     385                 :             :  *       \  |  /         [low]
     386                 :             :  *         tx3          /     \
     387                 :             :  *        [high]       tx6    tx7
     388                 :             :  *                    [med]  [high]
     389                 :             :  *
     390                 :             :  *  NOTE:
     391                 :             :  *  -> "low"/"med"/"high" denote the _absolute_ fee of each tx
     392                 :             :  *  -> tx3 has 3 inputs and 3 outputs, all other txs have 1 input and 2 outputs
     393                 :             :  *  -> tx3's feerate is lower than tx2's, as tx3 has more weight (due to having more inputs and outputs)
     394                 :             :  *
     395                 :             :  *  -> tx2_FR = high / tx2_vsize
     396                 :             :  *  -> tx3_FR = high / tx3_vsize
     397                 :             :  *  -> tx3_ASFR = (low+med+high+high) / (tx0_vsize + tx1_vsize + tx2_vsize + tx3_vsize)
     398                 :             :  *  -> tx4_FR = high / tx4_vsize
     399                 :             :  *  -> tx6_ASFR = (high+low+med) / (tx4_vsize + tx5_vsize + tx6_vsize)
     400                 :             :  *  -> tx7_ASFR = (high+low+high) / (tx4_vsize + tx5_vsize + tx7_vsize) */
     401                 :             : 
     402                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     403         [ +  - ]:           1 :     LOCK2(::cs_main, pool.cs);
     404                 :           1 :     TestMemPoolEntryHelper entry;
     405                 :             : 
     406                 :             :     // Create 3 parents of different feerates, and 1 child spending outputs from all 3 parents.
     407   [ +  -  +  -  :           2 :     const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     408   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx0));
     409   [ +  -  +  -  :           2 :     const auto tx1 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     410   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
     411   [ +  -  +  -  :           2 :     const auto tx2 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     412   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx2));
     413   [ +  -  +  -  :           2 :     const auto tx3 = make_tx({COutPoint{tx0->GetHash(), 0}, COutPoint{tx1->GetHash(), 0}, COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/3);
                   +  - ]
     414   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
     415                 :             : 
     416                 :             :     // Create 1 grandparent and 1 parent, then 2 children.
     417   [ +  -  +  -  :           2 :     const auto tx4 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     418   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
     419   [ +  -  +  -  :           2 :     const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/3);
                   +  - ]
     420   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
     421   [ +  -  +  -  :           2 :     const auto tx6 = make_tx({COutPoint{tx5->GetHash(), 0}}, /*num_outputs=*/2);
                   +  - ]
     422   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(med_fee).FromTx(tx6));
     423   [ +  -  +  -  :           2 :     const auto tx7 = make_tx({COutPoint{tx5->GetHash(), 1}}, /*num_outputs=*/2);
                   +  - ]
     424   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
     425                 :             : 
     426   [ +  +  +  -  :           9 :     std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
             -  -  -  - ]
     427                 :           1 :     std::vector<int64_t> tx_vsizes;
     428         [ +  - ]:           1 :     tx_vsizes.reserve(all_transactions.size());
     429   [ +  -  +  -  :           9 :     for (const auto& tx : all_transactions) tx_vsizes.push_back(GetVirtualTransactionSize(*tx));
                   +  + ]
     430                 :             : 
     431         [ +  - ]:           1 :     std::vector<COutPoint> all_unspent_outpoints({
     432                 :             :         COutPoint{tx0->GetHash(), 1},
     433                 :             :         COutPoint{tx1->GetHash(), 1},
     434                 :             :         COutPoint{tx2->GetHash(), 1},
     435                 :             :         COutPoint{tx3->GetHash(), 0},
     436                 :             :         COutPoint{tx3->GetHash(), 1},
     437                 :             :         COutPoint{tx3->GetHash(), 2},
     438                 :             :         COutPoint{tx4->GetHash(), 1},
     439                 :             :         COutPoint{tx5->GetHash(), 2},
     440                 :             :         COutPoint{tx6->GetHash(), 0},
     441                 :             :         COutPoint{tx7->GetHash(), 0}
     442         [ +  - ]:           1 :     });
     443   [ +  -  +  -  :          11 :     for (const auto& outpoint : all_unspent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
             +  -  +  + ]
     444                 :             : 
     445         [ +  - ]:           1 :     const auto tx2_feerate = CFeeRate(high_fee, tx_vsizes[2]);
     446         [ +  - ]:           1 :     const auto tx3_feerate = CFeeRate(high_fee, tx_vsizes[3]);
     447                 :             :     // tx3's feerate is lower than tx2's. same fee, different weight.
     448   [ +  -  +  -  :           2 :     BOOST_CHECK(tx2_feerate > tx3_feerate);
                   +  - ]
     449         [ +  - ]:           1 :     const auto tx3_anc_feerate = CFeeRate(low_fee + med_fee + high_fee + high_fee, tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]);
     450   [ +  -  +  - ]:           1 :     const auto& tx3_entry{*Assert(pool.GetEntry(tx3->GetHash()))};
     451   [ +  -  +  -  :           2 :     BOOST_CHECK(tx3_anc_feerate == CFeeRate(tx3_entry.GetModFeesWithAncestors(), tx3_entry.GetSizeWithAncestors()));
             +  -  +  - ]
     452         [ +  - ]:           1 :     const auto tx4_feerate = CFeeRate(high_fee, tx_vsizes[4]);
     453         [ +  - ]:           1 :     const auto tx6_anc_feerate = CFeeRate(high_fee + low_fee + med_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]);
     454   [ +  -  +  - ]:           1 :     const auto& tx6_entry{*Assert(pool.GetEntry(tx6->GetHash()))};
     455   [ +  -  +  -  :           2 :     BOOST_CHECK(tx6_anc_feerate == CFeeRate(tx6_entry.GetModFeesWithAncestors(), tx6_entry.GetSizeWithAncestors()));
             +  -  +  - ]
     456         [ +  - ]:           1 :     const auto tx7_anc_feerate = CFeeRate(high_fee + low_fee + high_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[7]);
     457   [ +  -  +  - ]:           1 :     const auto& tx7_entry{*Assert(pool.GetEntry(tx7->GetHash()))};
     458   [ +  -  +  -  :           2 :     BOOST_CHECK(tx7_anc_feerate == CFeeRate(tx7_entry.GetModFeesWithAncestors(), tx7_entry.GetSizeWithAncestors()));
             +  -  +  - ]
     459   [ +  -  +  -  :           2 :     BOOST_CHECK(tx4_feerate > tx6_anc_feerate);
                   +  - ]
     460   [ +  -  +  -  :           2 :     BOOST_CHECK(tx4_feerate > tx7_anc_feerate);
                   +  - ]
     461                 :             : 
     462                 :             :     // Extremely high feerate: everybody's bumpfee is from their full ancestor set.
     463                 :           1 :     {
     464         [ +  - ]:           1 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     465         [ +  - ]:           1 :         const CFeeRate very_high_feerate(COIN);
     466   [ +  -  +  -  :           2 :         BOOST_CHECK(tx3_anc_feerate < very_high_feerate);
                   +  - ]
     467   [ +  -  +  -  :           2 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
                   +  - ]
     468         [ +  - ]:           1 :         auto bump_fees = mini_miner.CalculateBumpFees(very_high_feerate);
     469   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     470   [ +  -  +  -  :           2 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
                   +  - ]
     471   [ +  -  +  -  :           2 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
                   +  - ]
     472                 :           1 :         const auto tx0_bumpfee = bump_fees.find(COutPoint{tx0->GetHash(), 1});
     473   [ +  -  +  -  :           2 :         BOOST_CHECK(tx0_bumpfee != bump_fees.end());
                   +  - ]
     474   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx0_bumpfee->second, very_high_feerate.GetFee(tx_vsizes[0]) - low_fee);
                   +  - ]
     475                 :           1 :         const auto tx3_bumpfee = bump_fees.find(COutPoint{tx3->GetHash(), 0});
     476   [ +  -  +  -  :           2 :         BOOST_CHECK(tx3_bumpfee != bump_fees.end());
                   +  - ]
     477   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx3_bumpfee->second,
                   +  - ]
     478                 :             :             very_high_feerate.GetFee(tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]) - (low_fee + med_fee + high_fee + high_fee));
     479                 :           1 :         const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
     480   [ +  -  +  -  :           2 :         BOOST_CHECK(tx6_bumpfee != bump_fees.end());
                   +  - ]
     481   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx6_bumpfee->second,
                   +  - ]
     482                 :             :             very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]) - (high_fee + low_fee + med_fee));
     483                 :           1 :         const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
     484   [ +  -  +  -  :           2 :         BOOST_CHECK(tx7_bumpfee != bump_fees.end());
                   +  - ]
     485   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx7_bumpfee->second,
                   +  - ]
     486                 :             :             very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[7]) - (high_fee + low_fee + high_fee));
     487                 :             :         // Total fees: if spending multiple outputs from tx3 don't double-count fees.
     488   [ +  -  +  -  :           2 :         node::MiniMiner mini_miner_total_tx3(pool, {COutPoint{tx3->GetHash(), 0}, COutPoint{tx3->GetHash(), 1}});
                   +  - ]
     489   [ +  -  +  -  :           2 :         BOOST_CHECK(mini_miner_total_tx3.IsReadyToCalculate());
                   +  - ]
     490         [ +  - ]:           1 :         const auto tx3_bump_fee = mini_miner_total_tx3.CalculateTotalBumpFees(very_high_feerate);
     491   [ +  -  +  -  :           2 :         BOOST_CHECK(!mini_miner_total_tx3.IsReadyToCalculate());
                   +  - ]
     492   [ +  -  +  -  :           2 :         BOOST_CHECK(tx3_bump_fee.has_value());
                   +  - ]
     493   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx3_bump_fee.value(),
             +  -  +  - ]
     494                 :             :             very_high_feerate.GetFee(tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]) - (low_fee + med_fee + high_fee + high_fee));
     495                 :             :         // Total fees: if spending both tx6 and tx7, don't double-count fees.
     496   [ +  -  +  -  :           2 :         node::MiniMiner mini_miner_tx6_tx7(pool, {COutPoint{tx6->GetHash(), 0}, COutPoint{tx7->GetHash(), 0}});
                   +  - ]
     497   [ +  -  +  -  :           2 :         BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
                   +  - ]
     498         [ +  - ]:           1 :         const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(very_high_feerate);
     499   [ +  -  +  -  :           2 :         BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
                   +  - ]
     500   [ +  -  +  -  :           2 :         BOOST_CHECK(tx6_tx7_bumpfee.has_value());
                   +  - ]
     501   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx6_tx7_bumpfee.value(),
             +  -  +  - ]
     502                 :             :             very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6] + tx_vsizes[7]) - (high_fee + low_fee + med_fee + high_fee));
     503                 :           1 :     }
     504                 :             :     // Feerate just below tx4: tx6 and tx7 have different bump fees.
     505                 :           1 :     {
     506         [ +  - ]:           1 :         const auto just_below_tx4 = CFeeRate(tx4_feerate.GetFeePerK() - 5);
     507         [ +  - ]:           1 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     508   [ +  -  +  -  :           2 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
                   +  - ]
     509         [ +  - ]:           1 :         auto bump_fees = mini_miner.CalculateBumpFees(just_below_tx4);
     510   [ +  -  +  -  :           2 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
                   +  - ]
     511   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     512   [ +  -  +  -  :           2 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
                   +  - ]
     513                 :           1 :         const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
     514   [ +  -  +  -  :           2 :         BOOST_CHECK(tx6_bumpfee != bump_fees.end());
                   +  - ]
     515   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx6_bumpfee->second, just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[6]) - (low_fee + med_fee));
                   +  - ]
     516                 :           1 :         const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
     517   [ +  -  +  -  :           2 :         BOOST_CHECK(tx7_bumpfee != bump_fees.end());
                   +  - ]
     518   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx7_bumpfee->second, just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[7]) - (low_fee + high_fee));
                   +  - ]
     519                 :             :         // Total fees: if spending both tx6 and tx7, don't double-count fees.
     520   [ +  -  +  -  :           2 :         node::MiniMiner mini_miner_tx6_tx7(pool, {COutPoint{tx6->GetHash(), 0}, COutPoint{tx7->GetHash(), 0}});
                   +  - ]
     521   [ +  -  +  -  :           2 :         BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
                   +  - ]
     522         [ +  - ]:           1 :         const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(just_below_tx4);
     523   [ +  -  +  -  :           2 :         BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
                   +  - ]
     524   [ +  -  +  -  :           2 :         BOOST_CHECK(tx6_tx7_bumpfee.has_value());
                   +  - ]
     525   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx6_tx7_bumpfee.value(), just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[6]) - (low_fee + med_fee));
             +  -  +  - ]
     526                 :           1 :     }
     527                 :             :     // Feerate between tx6 and tx7's ancestor feerates: don't need to bump tx5 because tx7 already does.
     528                 :           1 :     {
     529         [ +  - ]:           1 :         const auto just_above_tx6 = CFeeRate(med_fee + 10, tx_vsizes[6]);
     530   [ +  -  +  -  :           2 :         BOOST_CHECK(just_above_tx6 <= CFeeRate(low_fee + high_fee, tx_vsizes[5] + tx_vsizes[7]));
             +  -  +  - ]
     531         [ +  - ]:           1 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     532   [ +  -  +  -  :           2 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
                   +  - ]
     533         [ +  - ]:           1 :         auto bump_fees = mini_miner.CalculateBumpFees(just_above_tx6);
     534   [ +  -  +  -  :           2 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
                   +  - ]
     535   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     536   [ +  -  +  -  :           2 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
                   +  - ]
     537                 :           1 :         const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
     538   [ +  -  +  -  :           2 :         BOOST_CHECK(tx6_bumpfee != bump_fees.end());
                   +  - ]
     539   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(tx6_bumpfee->second, just_above_tx6.GetFee(tx_vsizes[6]) - (med_fee));
                   +  - ]
     540                 :           1 :         const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
     541   [ +  -  +  -  :           2 :         BOOST_CHECK(tx7_bumpfee != bump_fees.end());
                   +  - ]
     542   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(tx7_bumpfee->second, 0);
     543                 :           1 :     }
     544                 :             :     // Check linearization order
     545                 :           1 :     std::vector<node::MiniMinerMempoolEntry> miniminer_info;
     546         [ +  - ]:           1 :     miniminer_info.emplace_back(tx0,/*vsize_self=*/tx_vsizes[0],                     /*vsize_ancestor=*/tx_vsizes[0], /*fee_self=*/low_fee,   /*fee_ancestor=*/low_fee);
     547         [ +  - ]:           1 :     miniminer_info.emplace_back(tx1,               tx_vsizes[1],                                        tx_vsizes[1],              med_fee,                    med_fee);
     548         [ +  - ]:           1 :     miniminer_info.emplace_back(tx2,               tx_vsizes[2],                                        tx_vsizes[2],             high_fee,                   high_fee);
     549         [ +  - ]:           1 :     miniminer_info.emplace_back(tx3,               tx_vsizes[3], tx_vsizes[0]+tx_vsizes[1]+tx_vsizes[2]+tx_vsizes[3],             high_fee, low_fee+med_fee+2*high_fee);
     550         [ +  - ]:           1 :     miniminer_info.emplace_back(tx4,               tx_vsizes[4],                                        tx_vsizes[4],             high_fee,                   high_fee);
     551         [ +  - ]:           1 :     miniminer_info.emplace_back(tx5,               tx_vsizes[5],                           tx_vsizes[4]+tx_vsizes[5],              low_fee,         low_fee + high_fee);
     552         [ +  - ]:           1 :     miniminer_info.emplace_back(tx6,               tx_vsizes[6],              tx_vsizes[4]+tx_vsizes[5]+tx_vsizes[6],              med_fee,   high_fee+low_fee+med_fee);
     553         [ +  - ]:           1 :     miniminer_info.emplace_back(tx7,               tx_vsizes[7],              tx_vsizes[4]+tx_vsizes[5]+tx_vsizes[7],             high_fee,  high_fee+low_fee+high_fee);
     554                 :             : 
     555         [ +  - ]:           1 :     std::map<Txid, std::set<Txid>> descendant_caches;
     556   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx0->GetHash(), std::set<Txid>{tx0->GetHash(), tx3->GetHash()});
     557   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx1->GetHash(), std::set<Txid>{tx1->GetHash(), tx3->GetHash()});
     558   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx2->GetHash(), std::set<Txid>{tx2->GetHash(), tx3->GetHash()});
     559   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx3->GetHash(), std::set<Txid>{tx3->GetHash()});
     560   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx4->GetHash(), std::set<Txid>{tx4->GetHash(), tx5->GetHash(), tx6->GetHash(), tx7->GetHash()});
     561   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx5->GetHash(), std::set<Txid>{tx5->GetHash(), tx6->GetHash(), tx7->GetHash()});
     562   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx6->GetHash(), std::set<Txid>{tx6->GetHash()});
     563   [ +  -  +  - ]:           1 :     descendant_caches.emplace(tx7->GetHash(), std::set<Txid>{tx7->GetHash()});
     564                 :             : 
     565         [ +  - ]:           1 :     node::MiniMiner miniminer_manual(miniminer_info, descendant_caches);
     566                 :             :     // Use unspent outpoints to avoid entries being omitted.
     567         [ +  - ]:           1 :     node::MiniMiner miniminer_pool(pool, all_unspent_outpoints);
     568   [ +  -  +  -  :           2 :     BOOST_CHECK(miniminer_manual.IsReadyToCalculate());
                   +  - ]
     569   [ +  -  +  -  :           2 :     BOOST_CHECK(miniminer_pool.IsReadyToCalculate());
                   +  - ]
     570   [ +  -  +  -  :           5 :     for (const auto& sequences : {miniminer_manual.Linearize(), miniminer_pool.Linearize()}) {
             +  +  -  - ]
     571                 :             :         // tx2 and tx4 selected first: high feerate with nothing to bump
     572   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx4->GetHash()), 0);
                   +  - ]
     573   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx2->GetHash()), 1);
                   +  - ]
     574                 :             : 
     575                 :             :         // tx5 + tx7 CPFP
     576   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx5->GetHash()), 2);
                   +  - ]
     577   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx7->GetHash()), 2);
                   +  - ]
     578                 :             : 
     579                 :             :         // tx0 and tx1 CPFP'd by tx3
     580   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx0->GetHash()), 3);
                   +  - ]
     581   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx1->GetHash()), 3);
                   +  - ]
     582   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx3->GetHash()), 3);
                   +  - ]
     583                 :             : 
     584                 :             :         // tx6 at medium feerate
     585   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(Find(sequences, tx6->GetHash()), 4);
                   +  - ]
     586   [ +  +  -  - ]:           3 :     }
     587   [ +  -  +  -  :          20 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
             +  -  +  - ]
     588   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(calculate_cluster, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     589                 :             : {
     590                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     591         [ +  - ]:           1 :     LOCK2(cs_main, pool.cs);
     592                 :             : 
     593                 :             :     // TODO this can be removed once the mempool interface uses Txid, Wtxid
     594                 :           3 :     auto convert_to_uint256_vec = [](const std::vector<Txid>& vec) -> std::vector<uint256> {
     595                 :           2 :         std::vector<uint256> out;
     596         [ +  - ]:           2 :         std::transform(vec.begin(), vec.end(), std::back_inserter(out),
     597                 :         599 :                        [](const Txid& txid) { return txid.ToUint256(); });
     598                 :           2 :         return out;
     599                 :           0 :     };
     600                 :             : 
     601                 :             :     // Add chain of size 500
     602                 :           1 :     TestMemPoolEntryHelper entry;
     603                 :           1 :     std::vector<Txid> chain_txids;
     604                 :           1 :     auto& lasttx = m_coinbase_txns[0];
     605         [ +  + ]:         501 :     for (auto i{0}; i < 500; ++i) {
     606   [ +  -  +  -  :        1000 :         const auto tx = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
     607   [ +  -  +  - ]:         500 :         pool.addUnchecked(entry.Fee(CENT).FromTx(tx));
     608         [ +  - ]:         500 :         chain_txids.push_back(tx->GetHash());
     609         [ +  - ]:         500 :         lasttx = tx;
     610                 :         500 :     }
     611   [ +  -  +  -  :           2 :     const auto cluster_500tx = pool.GatherClusters({lasttx->GetHash()});
                   +  - ]
     612         [ +  - ]:           1 :     CTxMemPool::setEntries cluster_500tx_set{cluster_500tx.begin(), cluster_500tx.end()};
     613   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(cluster_500tx.size(), cluster_500tx_set.size());
     614   [ +  -  +  - ]:           1 :     const auto vec_iters_500 = pool.GetIterVec(convert_to_uint256_vec(chain_txids));
     615   [ +  -  +  -  :         501 :     for (const auto& iter : vec_iters_500) BOOST_CHECK(cluster_500tx_set.count(iter));
                   +  + ]
     616                 :             : 
     617                 :             :     // GatherClusters stops at 500 transactions.
     618   [ +  -  +  -  :           2 :     const auto tx_501 = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
                   +  - ]
     619   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(CENT).FromTx(tx_501));
     620   [ +  -  +  -  :           2 :     const auto cluster_501 = pool.GatherClusters({tx_501->GetHash()});
                   +  - ]
     621   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(cluster_501.size(), 0);
     622                 :             : 
     623                 :             :     /* Zig Zag cluster:
     624                 :             :      * txp0     txp1     txp2    ...  txp48  txp49
     625                 :             :      *    \    /    \   /   \            \   /
     626                 :             :      *     txc0     txc1    txc2  ...    txc48
     627                 :             :      * Note that each transaction's ancestor size is 1 or 3, and each descendant size is 1, 2 or 3.
     628                 :             :      * However, all of these transactions are in the same cluster. */
     629                 :           1 :     std::vector<Txid> zigzag_txids;
     630         [ +  + ]:          51 :     for (auto p{0}; p < 50; ++p) {
     631   [ +  -  +  -  :         100 :         const auto txp = make_tx({COutPoint{Txid::FromUint256(GetRandHash()), 0}}, /*num_outputs=*/2);
                   +  - ]
     632   [ +  -  +  - ]:          50 :         pool.addUnchecked(entry.Fee(CENT).FromTx(txp));
     633         [ +  - ]:          50 :         zigzag_txids.push_back(txp->GetHash());
     634                 :          50 :     }
     635         [ +  + ]:          50 :     for (auto c{0}; c < 49; ++c) {
     636   [ +  -  +  -  :          98 :         const auto txc = make_tx({COutPoint{zigzag_txids[c], 1}, COutPoint{zigzag_txids[c+1], 0}}, /*num_outputs=*/1);
                   +  - ]
     637   [ +  -  +  - ]:          49 :         pool.addUnchecked(entry.Fee(CENT).FromTx(txc));
     638         [ +  - ]:          49 :         zigzag_txids.push_back(txc->GetHash());
     639                 :          49 :     }
     640   [ +  -  +  - ]:           1 :     const auto vec_iters_zigzag = pool.GetIterVec(convert_to_uint256_vec(zigzag_txids));
     641                 :             :     // It doesn't matter which tx we calculate cluster for, everybody is in it.
     642         [ +  - ]:           1 :     const std::vector<size_t> indices{0, 22, 72, zigzag_txids.size() - 1};
     643         [ +  + ]:           5 :     for (const auto index : indices) {
     644   [ +  -  +  -  :           8 :         const auto cluster = pool.GatherClusters({zigzag_txids[index]});
                   +  - ]
     645   [ +  -  +  - ]:           4 :         BOOST_CHECK_EQUAL(cluster.size(), zigzag_txids.size());
     646         [ +  - ]:           4 :         CTxMemPool::setEntries clusterset{cluster.begin(), cluster.end()};
     647   [ +  -  +  - ]:           4 :         BOOST_CHECK_EQUAL(cluster.size(), clusterset.size());
     648   [ +  -  +  -  :         400 :         for (const auto& iter : vec_iters_zigzag) BOOST_CHECK(clusterset.count(iter));
                   +  + ]
     649                 :           4 :     }
     650   [ +  -  +  -  :           4 : }
                   +  - ]
     651                 :             : 
     652   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(manual_ctor, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     653                 :             : {
     654                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     655         [ +  - ]:           1 :     LOCK2(cs_main, pool.cs);
     656                 :           1 :     {
     657                 :             :         // 3 pairs of grandparent + fee-bumping parent, plus 1 low-feerate child.
     658                 :             :         // 0 fee + high fee
     659   [ +  -  +  -  :           2 :         auto grandparent_zero_fee = make_tx({{m_coinbase_txns.at(0)->GetHash(), 0}}, 1);
             +  -  +  - ]
     660   [ +  -  +  -  :           2 :         auto parent_high_feerate = make_tx({{grandparent_zero_fee->GetHash(), 0}}, 1);
                   +  - ]
     661                 :             :         // double low fee + med fee
     662   [ +  -  +  -  :           2 :         auto grandparent_double_low_feerate = make_tx({{m_coinbase_txns.at(2)->GetHash(), 0}}, 1);
             +  -  +  - ]
     663   [ +  -  +  -  :           2 :         auto parent_med_feerate = make_tx({{grandparent_double_low_feerate->GetHash(), 0}}, 1);
                   +  - ]
     664                 :             :         // low fee + double low fee
     665   [ +  -  +  -  :           2 :         auto grandparent_low_feerate = make_tx({{m_coinbase_txns.at(1)->GetHash(), 0}}, 1);
             +  -  +  - ]
     666   [ +  -  +  -  :           2 :         auto parent_double_low_feerate = make_tx({{grandparent_low_feerate->GetHash(), 0}}, 1);
                   +  - ]
     667                 :             :         // child is below the cpfp package feerates because it is larger than everything else
     668   [ +  -  +  -  :           2 :         auto child = make_tx({{parent_high_feerate->GetHash(), 0}, {parent_double_low_feerate->GetHash(), 0}, {parent_med_feerate->GetHash(), 0}}, 1);
                   +  - ]
     669                 :             : 
     670                 :             :         // We artificially record each transaction (except the child) with a uniform vsize of 100vB.
     671                 :           1 :         const int64_t tx_vsize{100};
     672                 :           1 :         const int64_t child_vsize{1000};
     673                 :             : 
     674                 :           1 :         std::vector<node::MiniMinerMempoolEntry> miniminer_info;
     675         [ +  - ]:           1 :         miniminer_info.emplace_back(grandparent_zero_fee,          /*vsize_self=*/tx_vsize,/*vsize_ancestor=*/tx_vsize, /*fee_self=*/0,/*fee_ancestor=*/0);
     676         [ +  - ]:           1 :         miniminer_info.emplace_back(parent_high_feerate,                          tx_vsize,                 2*tx_vsize, high_fee,      high_fee);
     677         [ +  - ]:           1 :         miniminer_info.emplace_back(grandparent_double_low_feerate,               tx_vsize,                   tx_vsize, 2*low_fee,     2*low_fee);
     678         [ +  - ]:           1 :         miniminer_info.emplace_back(parent_med_feerate,                           tx_vsize,                 2*tx_vsize, med_fee,       2*low_fee+med_fee);
     679         [ +  - ]:           1 :         miniminer_info.emplace_back(grandparent_low_feerate,                      tx_vsize,                   tx_vsize, low_fee,       low_fee);
     680         [ +  - ]:           1 :         miniminer_info.emplace_back(parent_double_low_feerate,                    tx_vsize,                 2*tx_vsize, 2*low_fee,     3*low_fee);
     681         [ +  - ]:           1 :         miniminer_info.emplace_back(child,                                     child_vsize,     6*tx_vsize+child_vsize, low_fee,       high_fee+med_fee+6*low_fee);
     682         [ +  - ]:           1 :         std::map<Txid, std::set<Txid>> descendant_caches;
     683   [ +  -  +  - ]:           1 :         descendant_caches.emplace(grandparent_zero_fee->GetHash(), std::set<Txid>{grandparent_zero_fee->GetHash(), parent_high_feerate->GetHash(), child->GetHash()});
     684   [ +  -  +  - ]:           1 :         descendant_caches.emplace(grandparent_low_feerate->GetHash(), std::set<Txid>{grandparent_low_feerate->GetHash(), parent_double_low_feerate->GetHash(), child->GetHash()});
     685   [ +  -  +  - ]:           1 :         descendant_caches.emplace(grandparent_double_low_feerate->GetHash(), std::set<Txid>{grandparent_double_low_feerate->GetHash(), parent_med_feerate->GetHash(), child->GetHash()});
     686   [ +  -  +  - ]:           1 :         descendant_caches.emplace(parent_high_feerate->GetHash(), std::set<Txid>{parent_high_feerate->GetHash(), child->GetHash()});
     687   [ +  -  +  - ]:           1 :         descendant_caches.emplace(parent_med_feerate->GetHash(), std::set<Txid>{parent_med_feerate->GetHash(), child->GetHash()});
     688   [ +  -  +  - ]:           1 :         descendant_caches.emplace(parent_double_low_feerate->GetHash(), std::set<Txid>{parent_double_low_feerate->GetHash(), child->GetHash()});
     689   [ +  -  +  - ]:           1 :         descendant_caches.emplace(child->GetHash(), std::set<Txid>{child->GetHash()});
     690                 :             : 
     691         [ +  - ]:           1 :         node::MiniMiner miniminer_manual(miniminer_info, descendant_caches);
     692   [ +  -  +  -  :           2 :         BOOST_CHECK(miniminer_manual.IsReadyToCalculate());
                   +  - ]
     693         [ +  - ]:           1 :         const auto sequences{miniminer_manual.Linearize()};
     694                 :             : 
     695                 :             :         // CPFP zero + high
     696   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(grandparent_zero_fee->GetHash()), 0);
                   +  - ]
     697   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(parent_high_feerate->GetHash()), 0);
                   +  - ]
     698                 :             : 
     699                 :             :         // CPFP double low + med
     700   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(grandparent_double_low_feerate->GetHash()), 1);
                   +  - ]
     701   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(parent_med_feerate->GetHash()), 1);
                   +  - ]
     702                 :             : 
     703                 :             :         // CPFP low + double low
     704   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(grandparent_low_feerate->GetHash()), 2);
                   +  - ]
     705   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(parent_double_low_feerate->GetHash()), 2);
                   +  - ]
     706                 :             : 
     707                 :             :         // Child at the end
     708   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(sequences.at(child->GetHash()), 3);
                   +  - ]
     709   [ +  -  +  -  :           8 :     }
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     710         [ +  - ]:           2 : }
     711                 :             : 
     712                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1