LCOV - code coverage report
Current view: top level - src/test - miniminer_tests.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 0 343 0.0 %
Date: 2023-10-05 12:38:51 Functions: 0 25 0.0 %
Branches: 0 0 -

           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                 :          0 : BOOST_FIXTURE_TEST_SUITE(miniminer_tests, TestingSetup)
      17                 :          0 : 
      18                 :          0 : static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, size_t num_outputs)
      19                 :            : {
      20                 :          0 :     CMutableTransaction tx = CMutableTransaction();
      21                 :          0 :     tx.vin.resize(inputs.size());
      22                 :          0 :     tx.vout.resize(num_outputs);
      23                 :          0 :     for (size_t i = 0; i < inputs.size(); ++i) {
      24                 :          0 :         tx.vin[i].prevout = inputs[i];
      25                 :          0 :     }
      26                 :          0 :     for (size_t i = 0; i < num_outputs; ++i) {
      27                 :          0 :         tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
      28                 :            :         // The actual input and output values of these transactions don't really
      29                 :            :         // matter, since all accounting will use the entries' cached fees.
      30                 :          0 :         tx.vout[i].nValue = COIN;
      31                 :          0 :     }
      32                 :          0 :     return MakeTransactionRef(tx);
      33                 :          0 : }
      34                 :            : 
      35                 :          0 : static inline bool sanity_check(const std::vector<CTransactionRef>& transactions,
      36                 :            :                                 const std::map<COutPoint, CAmount>& bumpfees)
      37                 :            : {
      38                 :            :     // No negative bumpfees.
      39                 :          0 :     for (const auto& [outpoint, fee] : bumpfees) {
      40                 :          0 :         if (fee < 0) return false;
      41                 :          0 :         if (fee == 0) continue;
      42                 :          0 :         auto outpoint_ = outpoint; // structured bindings can't be captured in C++17, so we need to use a variable
      43                 :          0 :         const bool found = std::any_of(transactions.cbegin(), transactions.cend(), [&](const auto& tx) {
      44                 :          0 :             return outpoint_.hash == tx->GetHash() && outpoint_.n < tx->vout.size();
      45                 :            :         });
      46                 :          0 :         if (!found) return false;
      47                 :            :     }
      48                 :          0 :     for (const auto& tx : transactions) {
      49                 :            :         // If tx has multiple outputs, they must all have the same bumpfee (if they exist).
      50                 :          0 :         if (tx->vout.size() > 1) {
      51                 :          0 :             std::set<CAmount> distinct_bumpfees;
      52                 :          0 :             for (size_t i{0}; i < tx->vout.size(); ++i) {
      53                 :          0 :                 const auto bumpfee = bumpfees.find(COutPoint{tx->GetHash(), static_cast<uint32_t>(i)});
      54                 :          0 :                 if (bumpfee != bumpfees.end()) distinct_bumpfees.insert(bumpfee->second);
      55                 :          0 :             }
      56                 :          0 :             if (distinct_bumpfees.size() > 1) return false;
      57                 :          0 :         }
      58                 :            :     }
      59                 :          0 :     return true;
      60                 :          0 : }
      61                 :            : 
      62                 :            : template <typename Key, typename Value>
      63                 :          0 : Value Find(const std::map<Key, Value>& map, const Key& key)
      64                 :            : {
      65                 :          0 :     auto it = map.find(key);
      66                 :          0 :     BOOST_CHECK_MESSAGE(it != map.end(), strprintf("Cannot find %s", key.ToString()));
      67                 :          0 :     return it->second;
      68                 :          0 : }
      69                 :            : 
      70                 :          0 : BOOST_FIXTURE_TEST_CASE(miniminer_1p1c, TestChain100Setup)
      71                 :            : {
      72                 :          0 :     CTxMemPool& pool = *Assert(m_node.mempool);
      73                 :          0 :     LOCK2(::cs_main, pool.cs);
      74                 :          0 :     TestMemPoolEntryHelper entry;
      75                 :            : 
      76                 :          0 :     const CAmount low_fee{CENT/2000};
      77                 :          0 :     const CAmount normal_fee{CENT/200};
      78                 :          0 :     const CAmount high_fee{CENT/10};
      79                 :            : 
      80                 :            :     // Create a parent tx0 and child tx1 with normal fees:
      81                 :          0 :     const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
      82                 :          0 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx0));
      83                 :          0 :     const auto tx1 = make_tx({COutPoint{tx0->GetHash(), 0}}, /*num_outputs=*/1);
      84                 :          0 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx1));
      85                 :            : 
      86                 :            :     // Create a low-feerate parent tx2 and high-feerate child tx3 (cpfp)
      87                 :          0 :     const auto tx2 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
      88                 :          0 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx2));
      89                 :          0 :     const auto tx3 = make_tx({COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/1);
      90                 :          0 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
      91                 :            : 
      92                 :            :     // Create a parent tx4 and child tx5 where both have low fees
      93                 :          0 :     const auto tx4 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
      94                 :          0 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx4));
      95                 :          0 :     const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/1);
      96                 :          0 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
      97                 :            :     // Make tx5's modified fee much higher than its base fee. This should cause it to pass
      98                 :            :     // the fee-related checks despite being low-feerate.
      99                 :          0 :     pool.PrioritiseTransaction(tx5->GetHash(), CENT/100);
     100                 :            : 
     101                 :            :     // Create a high-feerate parent tx6, low-feerate child tx7
     102                 :          0 :     const auto tx6 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
     103                 :          0 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx6));
     104                 :          0 :     const auto tx7 = make_tx({COutPoint{tx6->GetHash(), 0}}, /*num_outputs=*/1);
     105                 :          0 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx7));
     106                 :            : 
     107                 :          0 :     std::vector<COutPoint> all_unspent_outpoints({
     108                 :          0 :         COutPoint{tx0->GetHash(), 1},
     109                 :          0 :         COutPoint{tx1->GetHash(), 0},
     110                 :          0 :         COutPoint{tx2->GetHash(), 1},
     111                 :          0 :         COutPoint{tx3->GetHash(), 0},
     112                 :          0 :         COutPoint{tx4->GetHash(), 1},
     113                 :          0 :         COutPoint{tx5->GetHash(), 0},
     114                 :          0 :         COutPoint{tx6->GetHash(), 1},
     115                 :          0 :         COutPoint{tx7->GetHash(), 0}
     116                 :            :     });
     117                 :          0 :     for (const auto& outpoint : all_unspent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
     118                 :            : 
     119                 :          0 :     std::vector<COutPoint> all_spent_outpoints({
     120                 :          0 :         COutPoint{tx0->GetHash(), 0},
     121                 :          0 :         COutPoint{tx2->GetHash(), 0},
     122                 :          0 :         COutPoint{tx4->GetHash(), 0},
     123                 :          0 :         COutPoint{tx6->GetHash(), 0}
     124                 :            :     });
     125                 :          0 :     for (const auto& outpoint : all_spent_outpoints) BOOST_CHECK(pool.GetConflictTx(outpoint) != nullptr);
     126                 :            : 
     127                 :          0 :     std::vector<COutPoint> all_parent_outputs({
     128                 :          0 :         COutPoint{tx0->GetHash(), 0},
     129                 :          0 :         COutPoint{tx0->GetHash(), 1},
     130                 :          0 :         COutPoint{tx2->GetHash(), 0},
     131                 :          0 :         COutPoint{tx2->GetHash(), 1},
     132                 :          0 :         COutPoint{tx4->GetHash(), 0},
     133                 :          0 :         COutPoint{tx4->GetHash(), 1},
     134                 :          0 :         COutPoint{tx6->GetHash(), 0},
     135                 :          0 :         COutPoint{tx6->GetHash(), 1}
     136                 :            :     });
     137                 :            : 
     138                 :            : 
     139                 :          0 :     std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
     140                 :            :     struct TxDimensions {
     141                 :            :         int32_t vsize; CAmount mod_fee; CFeeRate feerate;
     142                 :            :     };
     143                 :          0 :     std::map<uint256, TxDimensions> tx_dims;
     144                 :          0 :     for (const auto& tx : all_transactions) {
     145                 :          0 :         const auto it = pool.GetIter(tx->GetHash()).value();
     146                 :          0 :         tx_dims.emplace(tx->GetHash(), TxDimensions{it->GetTxSize(), it->GetModifiedFee(),
     147                 :          0 :                                               CFeeRate(it->GetModifiedFee(), it->GetTxSize())});
     148                 :            :     }
     149                 :            : 
     150                 :          0 :     const std::vector<CFeeRate> various_normal_feerates({CFeeRate(0), CFeeRate(500), CFeeRate(999),
     151                 :          0 :                                                          CFeeRate(1000), CFeeRate(2000), CFeeRate(2500),
     152                 :          0 :                                                          CFeeRate(3333), CFeeRate(7800), CFeeRate(11199),
     153                 :          0 :                                                          CFeeRate(23330), CFeeRate(50000), CFeeRate(5*CENT)});
     154                 :            : 
     155                 :            :     // All nonexistent entries have a bumpfee of zero, regardless of feerate
     156                 :          0 :     std::vector<COutPoint> nonexistent_outpoints({ COutPoint{GetRandHash(), 0}, COutPoint{GetRandHash(), 3} });
     157                 :          0 :     for (const auto& outpoint : nonexistent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
     158                 :          0 :     for (const auto& feerate : various_normal_feerates) {
     159                 :          0 :         node::MiniMiner mini_miner(pool, nonexistent_outpoints);
     160                 :          0 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
     161                 :          0 :         auto bump_fees = mini_miner.CalculateBumpFees(feerate);
     162                 :          0 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
     163                 :          0 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
     164                 :          0 :         BOOST_CHECK(bump_fees.size() == nonexistent_outpoints.size());
     165                 :          0 :         for (const auto& outpoint: nonexistent_outpoints) {
     166                 :          0 :             auto it = bump_fees.find(outpoint);
     167                 :          0 :             BOOST_CHECK(it != bump_fees.end());
     168                 :          0 :             BOOST_CHECK_EQUAL(it->second, 0);
     169                 :            :         }
     170                 :          0 :     }
     171                 :            : 
     172                 :            :     // Gather bump fees for all available UTXOs.
     173                 :          0 :     for (const auto& target_feerate : various_normal_feerates) {
     174                 :          0 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     175                 :          0 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
     176                 :          0 :         auto bump_fees = mini_miner.CalculateBumpFees(target_feerate);
     177                 :          0 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
     178                 :          0 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
     179                 :          0 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     180                 :            : 
     181                 :            :         // Check tx0 bumpfee: no other bumper.
     182                 :          0 :         const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
     183                 :          0 :         CAmount bumpfee0 = Find(bump_fees, COutPoint{tx0->GetHash(), 1});
     184                 :          0 :         if (target_feerate <= tx0_dimensions.feerate) {
     185                 :          0 :             BOOST_CHECK_EQUAL(bumpfee0, 0);
     186                 :          0 :         } else {
     187                 :            :             // Difference is fee to bump tx0 from current to target feerate.
     188                 :          0 :             BOOST_CHECK_EQUAL(bumpfee0, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
     189                 :            :         }
     190                 :            : 
     191                 :            :         // Check tx2 bumpfee: assisted by tx3.
     192                 :          0 :         const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
     193                 :          0 :         const TxDimensions& tx3_dimensions = tx_dims.find(tx3->GetHash())->second;
     194                 :          0 :         const CFeeRate tx2_feerate = CFeeRate(tx2_dimensions.mod_fee + tx3_dimensions.mod_fee, tx2_dimensions.vsize + tx3_dimensions.vsize);
     195                 :          0 :         CAmount bumpfee2 = Find(bump_fees, COutPoint{tx2->GetHash(), 1});
     196                 :          0 :         if (target_feerate <= tx2_feerate) {
     197                 :            :             // As long as target feerate is below tx3's ancestor feerate, there is no bump fee.
     198                 :          0 :             BOOST_CHECK_EQUAL(bumpfee2, 0);
     199                 :          0 :         } else {
     200                 :            :             // Difference is fee to bump tx2 from current to target feerate, without tx3.
     201                 :          0 :             BOOST_CHECK_EQUAL(bumpfee2, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
     202                 :            :         }
     203                 :            : 
     204                 :            :         // If tx5’s modified fees are sufficient for tx4 and tx5 to be picked
     205                 :            :         // into the block, our prospective new transaction would not need to
     206                 :            :         // bump tx4 when using tx4’s second output. If however even tx5’s
     207                 :            :         // modified fee (which essentially indicates "effective feerate") is
     208                 :            :         // not sufficient to bump tx4, using the second output of tx4 would
     209                 :            :         // require our transaction to bump tx4 from scratch since we evaluate
     210                 :            :         // transaction packages per ancestor sets and do not consider multiple
     211                 :            :         // children’s fees.
     212                 :          0 :         const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
     213                 :          0 :         const TxDimensions& tx5_dimensions = tx_dims.find(tx5->GetHash())->second;
     214                 :          0 :         const CFeeRate tx4_feerate = CFeeRate(tx4_dimensions.mod_fee + tx5_dimensions.mod_fee, tx4_dimensions.vsize + tx5_dimensions.vsize);
     215                 :          0 :         CAmount bumpfee4 = Find(bump_fees, COutPoint{tx4->GetHash(), 1});
     216                 :          0 :         if (target_feerate <= tx4_feerate) {
     217                 :            :             // As long as target feerate is below tx5's ancestor feerate, there is no bump fee.
     218                 :          0 :             BOOST_CHECK_EQUAL(bumpfee4, 0);
     219                 :          0 :         } else {
     220                 :            :             // Difference is fee to bump tx4 from current to target feerate, without tx5.
     221                 :          0 :             BOOST_CHECK_EQUAL(bumpfee4, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
     222                 :            :         }
     223                 :          0 :     }
     224                 :            :     // Spent outpoints should usually not be requested as they would not be
     225                 :            :     // considered available. However, when they are explicitly requested, we
     226                 :            :     // can calculate their bumpfee to facilitate RBF-replacements
     227                 :          0 :     for (const auto& target_feerate : various_normal_feerates) {
     228                 :          0 :         node::MiniMiner mini_miner_all_spent(pool, all_spent_outpoints);
     229                 :          0 :         BOOST_CHECK(mini_miner_all_spent.IsReadyToCalculate());
     230                 :          0 :         auto bump_fees_all_spent = mini_miner_all_spent.CalculateBumpFees(target_feerate);
     231                 :          0 :         BOOST_CHECK(!mini_miner_all_spent.IsReadyToCalculate());
     232                 :          0 :         BOOST_CHECK_EQUAL(bump_fees_all_spent.size(), all_spent_outpoints.size());
     233                 :          0 :         node::MiniMiner mini_miner_all_parents(pool, all_parent_outputs);
     234                 :          0 :         BOOST_CHECK(mini_miner_all_parents.IsReadyToCalculate());
     235                 :          0 :         auto bump_fees_all_parents = mini_miner_all_parents.CalculateBumpFees(target_feerate);
     236                 :          0 :         BOOST_CHECK(!mini_miner_all_parents.IsReadyToCalculate());
     237                 :          0 :         BOOST_CHECK_EQUAL(bump_fees_all_parents.size(), all_parent_outputs.size());
     238                 :          0 :         for (auto& bump_fees : {bump_fees_all_parents, bump_fees_all_spent}) {
     239                 :            :             // For all_parents case, both outputs from the parent should have the same bump fee,
     240                 :            :             // even though only one of them is in a to-be-replaced transaction.
     241                 :          0 :             BOOST_CHECK(sanity_check(all_transactions, bump_fees));
     242                 :            : 
     243                 :            :             // Check tx0 bumpfee: no other bumper.
     244                 :          0 :             const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
     245                 :          0 :             CAmount it0_spent = Find(bump_fees, COutPoint{tx0->GetHash(), 0});
     246                 :          0 :             if (target_feerate <= tx0_dimensions.feerate) {
     247                 :          0 :                 BOOST_CHECK_EQUAL(it0_spent, 0);
     248                 :          0 :             } else {
     249                 :            :                 // Difference is fee to bump tx0 from current to target feerate.
     250                 :          0 :                 BOOST_CHECK_EQUAL(it0_spent, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
     251                 :            :             }
     252                 :            : 
     253                 :            :             // Check tx2 bumpfee: no other bumper, because tx3 is to-be-replaced.
     254                 :          0 :             const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
     255                 :          0 :             const CFeeRate tx2_feerate_unbumped = tx2_dimensions.feerate;
     256                 :          0 :             auto it2_spent = Find(bump_fees, COutPoint{tx2->GetHash(), 0});
     257                 :          0 :             if (target_feerate <= tx2_feerate_unbumped) {
     258                 :          0 :                 BOOST_CHECK_EQUAL(it2_spent, 0);
     259                 :          0 :             } else {
     260                 :            :                 // Difference is fee to bump tx2 from current to target feerate, without tx3.
     261                 :          0 :                 BOOST_CHECK_EQUAL(it2_spent, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
     262                 :            :             }
     263                 :            : 
     264                 :            :             // Check tx4 bumpfee: no other bumper, because tx5 is to-be-replaced.
     265                 :          0 :             const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
     266                 :          0 :             const CFeeRate tx4_feerate_unbumped = tx4_dimensions.feerate;
     267                 :          0 :             auto it4_spent = Find(bump_fees, COutPoint{tx4->GetHash(), 0});
     268                 :          0 :             if (target_feerate <= tx4_feerate_unbumped) {
     269                 :          0 :                 BOOST_CHECK_EQUAL(it4_spent, 0);
     270                 :          0 :             } else {
     271                 :            :                 // Difference is fee to bump tx4 from current to target feerate, without tx5.
     272                 :          0 :                 BOOST_CHECK_EQUAL(it4_spent, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
     273                 :            :             }
     274                 :            :         }
     275                 :          0 :     }
     276                 :          0 : }
     277                 :            : 
     278                 :          0 : BOOST_FIXTURE_TEST_CASE(miniminer_overlap, TestChain100Setup)
     279                 :            : {
     280                 :            : /*      Tx graph for `miniminer_overlap` unit test:
     281                 :            :  *
     282                 :            :  *     coinbase_tx [mined]        ... block-chain
     283                 :            :  *  -------------------------------------------------
     284                 :            :  *      /   |   \          \      ... mempool
     285                 :            :  *     /    |    \         |
     286                 :            :  *   tx0   tx1   tx2      tx4
     287                 :            :  *  [low] [med] [high]   [high]
     288                 :            :  *     \    |    /         |
     289                 :            :  *      \   |   /         tx5
     290                 :            :  *       \  |  /         [low]
     291                 :            :  *         tx3          /     \
     292                 :            :  *        [high]       tx6    tx7
     293                 :            :  *                    [med]  [high]
     294                 :            :  *
     295                 :            :  *  NOTE:
     296                 :            :  *  -> "low"/"med"/"high" denote the _absolute_ fee of each tx
     297                 :            :  *  -> tx3 has 3 inputs and 3 outputs, all other txs have 1 input and 2 outputs
     298                 :            :  *  -> tx3's feerate is lower than tx2's, as tx3 has more weight (due to having more inputs and outputs)
     299                 :            :  *
     300                 :            :  *  -> tx2_FR = high / tx2_vsize
     301                 :            :  *  -> tx3_FR = high / tx3_vsize
     302                 :            :  *  -> tx3_ASFR = (low+med+high+high) / (tx0_vsize + tx1_vsize + tx2_vsize + tx3_vsize)
     303                 :            :  *  -> tx4_FR = high / tx4_vsize
     304                 :            :  *  -> tx6_ASFR = (high+low+med) / (tx4_vsize + tx5_vsize + tx6_vsize)
     305                 :            :  *  -> tx7_ASFR = (high+low+high) / (tx4_vsize + tx5_vsize + tx7_vsize) */
     306                 :            : 
     307                 :          0 :     CTxMemPool& pool = *Assert(m_node.mempool);
     308                 :          0 :     LOCK2(::cs_main, pool.cs);
     309                 :          0 :     TestMemPoolEntryHelper entry;
     310                 :            : 
     311                 :          0 :     const CAmount low_fee{CENT/2000}; // 500 ṩ
     312                 :          0 :     const CAmount med_fee{CENT/200}; // 5000 ṩ
     313                 :          0 :     const CAmount high_fee{CENT/10}; // 100_000 ṩ
     314                 :            : 
     315                 :            :     // Create 3 parents of different feerates, and 1 child spending outputs from all 3 parents.
     316                 :          0 :     const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
     317                 :          0 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx0));
     318                 :          0 :     const auto tx1 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
     319                 :          0 :     pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
     320                 :          0 :     const auto tx2 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
     321                 :          0 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx2));
     322                 :          0 :     const auto tx3 = make_tx({COutPoint{tx0->GetHash(), 0}, COutPoint{tx1->GetHash(), 0}, COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/3);
     323                 :          0 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
     324                 :            : 
     325                 :            :     // Create 1 grandparent and 1 parent, then 2 children.
     326                 :          0 :     const auto tx4 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
     327                 :          0 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
     328                 :          0 :     const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/3);
     329                 :          0 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
     330                 :          0 :     const auto tx6 = make_tx({COutPoint{tx5->GetHash(), 0}}, /*num_outputs=*/2);
     331                 :          0 :     pool.addUnchecked(entry.Fee(med_fee).FromTx(tx6));
     332                 :          0 :     const auto tx7 = make_tx({COutPoint{tx5->GetHash(), 1}}, /*num_outputs=*/2);
     333                 :          0 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
     334                 :            : 
     335                 :          0 :     std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
     336                 :          0 :     std::vector<int64_t> tx_vsizes;
     337                 :          0 :     tx_vsizes.reserve(all_transactions.size());
     338                 :          0 :     for (const auto& tx : all_transactions) tx_vsizes.push_back(GetVirtualTransactionSize(*tx));
     339                 :            : 
     340                 :          0 :     std::vector<COutPoint> all_unspent_outpoints({
     341                 :          0 :         COutPoint{tx0->GetHash(), 1},
     342                 :          0 :         COutPoint{tx1->GetHash(), 1},
     343                 :          0 :         COutPoint{tx2->GetHash(), 1},
     344                 :          0 :         COutPoint{tx3->GetHash(), 0},
     345                 :          0 :         COutPoint{tx3->GetHash(), 1},
     346                 :          0 :         COutPoint{tx3->GetHash(), 2},
     347                 :          0 :         COutPoint{tx4->GetHash(), 1},
     348                 :          0 :         COutPoint{tx5->GetHash(), 2},
     349                 :          0 :         COutPoint{tx6->GetHash(), 0},
     350                 :          0 :         COutPoint{tx7->GetHash(), 0}
     351                 :            :     });
     352                 :          0 :     for (const auto& outpoint : all_unspent_outpoints) BOOST_CHECK(!pool.isSpent(outpoint));
     353                 :            : 
     354                 :          0 :     const auto tx2_feerate = CFeeRate(high_fee, tx_vsizes[2]);
     355                 :          0 :     const auto tx3_feerate = CFeeRate(high_fee, tx_vsizes[3]);
     356                 :            :     // tx3's feerate is lower than tx2's. same fee, different weight.
     357                 :          0 :     BOOST_CHECK(tx2_feerate > tx3_feerate);
     358                 :          0 :     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]);
     359                 :          0 :     const auto tx3_iter = pool.GetIter(tx3->GetHash());
     360                 :          0 :     BOOST_CHECK(tx3_anc_feerate == CFeeRate(tx3_iter.value()->GetModFeesWithAncestors(), tx3_iter.value()->GetSizeWithAncestors()));
     361                 :          0 :     const auto tx4_feerate = CFeeRate(high_fee, tx_vsizes[4]);
     362                 :          0 :     const auto tx6_anc_feerate = CFeeRate(high_fee + low_fee + med_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]);
     363                 :          0 :     const auto tx6_iter = pool.GetIter(tx6->GetHash());
     364                 :          0 :     BOOST_CHECK(tx6_anc_feerate == CFeeRate(tx6_iter.value()->GetModFeesWithAncestors(), tx6_iter.value()->GetSizeWithAncestors()));
     365                 :          0 :     const auto tx7_anc_feerate = CFeeRate(high_fee + low_fee + high_fee, tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[7]);
     366                 :          0 :     const auto tx7_iter = pool.GetIter(tx7->GetHash());
     367                 :          0 :     BOOST_CHECK(tx7_anc_feerate == CFeeRate(tx7_iter.value()->GetModFeesWithAncestors(), tx7_iter.value()->GetSizeWithAncestors()));
     368                 :          0 :     BOOST_CHECK(tx4_feerate > tx6_anc_feerate);
     369                 :          0 :     BOOST_CHECK(tx4_feerate > tx7_anc_feerate);
     370                 :            : 
     371                 :            :     // Extremely high feerate: everybody's bumpfee is from their full ancestor set.
     372                 :            :     {
     373                 :          0 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     374                 :          0 :         const CFeeRate very_high_feerate(COIN);
     375                 :          0 :         BOOST_CHECK(tx3_anc_feerate < very_high_feerate);
     376                 :          0 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
     377                 :          0 :         auto bump_fees = mini_miner.CalculateBumpFees(very_high_feerate);
     378                 :          0 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     379                 :          0 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
     380                 :          0 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
     381                 :          0 :         const auto tx0_bumpfee = bump_fees.find(COutPoint{tx0->GetHash(), 1});
     382                 :          0 :         BOOST_CHECK(tx0_bumpfee != bump_fees.end());
     383                 :          0 :         BOOST_CHECK_EQUAL(tx0_bumpfee->second, very_high_feerate.GetFee(tx_vsizes[0]) - low_fee);
     384                 :          0 :         const auto tx3_bumpfee = bump_fees.find(COutPoint{tx3->GetHash(), 0});
     385                 :          0 :         BOOST_CHECK(tx3_bumpfee != bump_fees.end());
     386                 :          0 :         BOOST_CHECK_EQUAL(tx3_bumpfee->second,
     387                 :            :             very_high_feerate.GetFee(tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]) - (low_fee + med_fee + high_fee + high_fee));
     388                 :          0 :         const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
     389                 :          0 :         BOOST_CHECK(tx6_bumpfee != bump_fees.end());
     390                 :          0 :         BOOST_CHECK_EQUAL(tx6_bumpfee->second,
     391                 :            :             very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6]) - (high_fee + low_fee + med_fee));
     392                 :          0 :         const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
     393                 :          0 :         BOOST_CHECK(tx7_bumpfee != bump_fees.end());
     394                 :          0 :         BOOST_CHECK_EQUAL(tx7_bumpfee->second,
     395                 :            :             very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[7]) - (high_fee + low_fee + high_fee));
     396                 :            :         // Total fees: if spending multiple outputs from tx3 don't double-count fees.
     397                 :          0 :         node::MiniMiner mini_miner_total_tx3(pool, {COutPoint{tx3->GetHash(), 0}, COutPoint{tx3->GetHash(), 1}});
     398                 :          0 :         BOOST_CHECK(mini_miner_total_tx3.IsReadyToCalculate());
     399                 :          0 :         const auto tx3_bump_fee = mini_miner_total_tx3.CalculateTotalBumpFees(very_high_feerate);
     400                 :          0 :         BOOST_CHECK(!mini_miner_total_tx3.IsReadyToCalculate());
     401                 :          0 :         BOOST_CHECK(tx3_bump_fee.has_value());
     402                 :          0 :         BOOST_CHECK_EQUAL(tx3_bump_fee.value(),
     403                 :            :             very_high_feerate.GetFee(tx_vsizes[0] + tx_vsizes[1] + tx_vsizes[2] + tx_vsizes[3]) - (low_fee + med_fee + high_fee + high_fee));
     404                 :            :         // Total fees: if spending both tx6 and tx7, don't double-count fees.
     405                 :          0 :         node::MiniMiner mini_miner_tx6_tx7(pool, {COutPoint{tx6->GetHash(), 0}, COutPoint{tx7->GetHash(), 0}});
     406                 :          0 :         BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
     407                 :          0 :         const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(very_high_feerate);
     408                 :          0 :         BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
     409                 :          0 :         BOOST_CHECK(tx6_tx7_bumpfee.has_value());
     410                 :          0 :         BOOST_CHECK_EQUAL(tx6_tx7_bumpfee.value(),
     411                 :            :             very_high_feerate.GetFee(tx_vsizes[4] + tx_vsizes[5] + tx_vsizes[6] + tx_vsizes[7]) - (high_fee + low_fee + med_fee + high_fee));
     412                 :          0 :     }
     413                 :            :     // Feerate just below tx4: tx6 and tx7 have different bump fees.
     414                 :            :     {
     415                 :          0 :         const auto just_below_tx4 = CFeeRate(tx4_feerate.GetFeePerK() - 5);
     416                 :          0 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     417                 :          0 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
     418                 :          0 :         auto bump_fees = mini_miner.CalculateBumpFees(just_below_tx4);
     419                 :          0 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
     420                 :          0 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     421                 :          0 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
     422                 :          0 :         const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
     423                 :          0 :         BOOST_CHECK(tx6_bumpfee != bump_fees.end());
     424                 :          0 :         BOOST_CHECK_EQUAL(tx6_bumpfee->second, just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[6]) - (low_fee + med_fee));
     425                 :          0 :         const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
     426                 :          0 :         BOOST_CHECK(tx7_bumpfee != bump_fees.end());
     427                 :          0 :         BOOST_CHECK_EQUAL(tx7_bumpfee->second, just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[7]) - (low_fee + high_fee));
     428                 :            :         // Total fees: if spending both tx6 and tx7, don't double-count fees.
     429                 :          0 :         node::MiniMiner mini_miner_tx6_tx7(pool, {COutPoint{tx6->GetHash(), 0}, COutPoint{tx7->GetHash(), 0}});
     430                 :          0 :         BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
     431                 :          0 :         const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(just_below_tx4);
     432                 :          0 :         BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
     433                 :          0 :         BOOST_CHECK(tx6_tx7_bumpfee.has_value());
     434                 :          0 :         BOOST_CHECK_EQUAL(tx6_tx7_bumpfee.value(), just_below_tx4.GetFee(tx_vsizes[5] + tx_vsizes[6]) - (low_fee + med_fee));
     435                 :          0 :     }
     436                 :            :     // Feerate between tx6 and tx7's ancestor feerates: don't need to bump tx5 because tx7 already does.
     437                 :            :     {
     438                 :          0 :         const auto just_above_tx6 = CFeeRate(med_fee + 10, tx_vsizes[6]);
     439                 :          0 :         BOOST_CHECK(just_above_tx6 <= CFeeRate(low_fee + high_fee, tx_vsizes[5] + tx_vsizes[7]));
     440                 :          0 :         node::MiniMiner mini_miner(pool, all_unspent_outpoints);
     441                 :          0 :         BOOST_CHECK(mini_miner.IsReadyToCalculate());
     442                 :          0 :         auto bump_fees = mini_miner.CalculateBumpFees(just_above_tx6);
     443                 :          0 :         BOOST_CHECK(!mini_miner.IsReadyToCalculate());
     444                 :          0 :         BOOST_CHECK_EQUAL(bump_fees.size(), all_unspent_outpoints.size());
     445                 :          0 :         BOOST_CHECK(sanity_check(all_transactions, bump_fees));
     446                 :          0 :         const auto tx6_bumpfee = bump_fees.find(COutPoint{tx6->GetHash(), 0});
     447                 :          0 :         BOOST_CHECK(tx6_bumpfee != bump_fees.end());
     448                 :          0 :         BOOST_CHECK_EQUAL(tx6_bumpfee->second, just_above_tx6.GetFee(tx_vsizes[6]) - (med_fee));
     449                 :          0 :         const auto tx7_bumpfee = bump_fees.find(COutPoint{tx7->GetHash(), 0});
     450                 :          0 :         BOOST_CHECK(tx7_bumpfee != bump_fees.end());
     451                 :          0 :         BOOST_CHECK_EQUAL(tx7_bumpfee->second, 0);
     452                 :          0 :     }
     453                 :          0 : }
     454                 :          0 : BOOST_FIXTURE_TEST_CASE(calculate_cluster, TestChain100Setup)
     455                 :            : {
     456                 :          0 :     CTxMemPool& pool = *Assert(m_node.mempool);
     457                 :          0 :     LOCK2(cs_main, pool.cs);
     458                 :            : 
     459                 :            :     // Add chain of size 500
     460                 :          0 :     TestMemPoolEntryHelper entry;
     461                 :          0 :     std::vector<uint256> chain_txids;
     462                 :          0 :     auto& lasttx = m_coinbase_txns[0];
     463                 :          0 :     for (auto i{0}; i < 500; ++i) {
     464                 :          0 :         const auto tx = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
     465                 :          0 :         pool.addUnchecked(entry.Fee(CENT).FromTx(tx));
     466                 :          0 :         chain_txids.push_back(tx->GetHash());
     467                 :          0 :         lasttx = tx;
     468                 :          0 :     }
     469                 :          0 :     const auto cluster_500tx = pool.GatherClusters({lasttx->GetHash()});
     470                 :          0 :     CTxMemPool::setEntries cluster_500tx_set{cluster_500tx.begin(), cluster_500tx.end()};
     471                 :          0 :     BOOST_CHECK_EQUAL(cluster_500tx.size(), cluster_500tx_set.size());
     472                 :          0 :     const auto vec_iters_500 = pool.GetIterVec(chain_txids);
     473                 :          0 :     for (const auto& iter : vec_iters_500) BOOST_CHECK(cluster_500tx_set.count(iter));
     474                 :            : 
     475                 :            :     // GatherClusters stops at 500 transactions.
     476                 :          0 :     const auto tx_501 = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
     477                 :          0 :     pool.addUnchecked(entry.Fee(CENT).FromTx(tx_501));
     478                 :          0 :     const auto cluster_501 = pool.GatherClusters({tx_501->GetHash()});
     479                 :          0 :     BOOST_CHECK_EQUAL(cluster_501.size(), 0);
     480                 :            : 
     481                 :            :     /* Zig Zag cluster:
     482                 :            :      * txp0     txp1     txp2    ...  txp48  txp49
     483                 :            :      *    \    /    \   /   \            \   /
     484                 :            :      *     txc0     txc1    txc2  ...    txc48
     485                 :            :      * Note that each transaction's ancestor size is 1 or 3, and each descendant size is 1, 2 or 3.
     486                 :            :      * However, all of these transactions are in the same cluster. */
     487                 :          0 :     std::vector<uint256> zigzag_txids;
     488                 :          0 :     for (auto p{0}; p < 50; ++p) {
     489                 :          0 :         const auto txp = make_tx({COutPoint{GetRandHash(), 0}}, /*num_outputs=*/2);
     490                 :          0 :         pool.addUnchecked(entry.Fee(CENT).FromTx(txp));
     491                 :          0 :         zigzag_txids.push_back(txp->GetHash());
     492                 :          0 :     }
     493                 :          0 :     for (auto c{0}; c < 49; ++c) {
     494                 :          0 :         const auto txc = make_tx({COutPoint{zigzag_txids[c], 1}, COutPoint{zigzag_txids[c+1], 0}}, /*num_outputs=*/1);
     495                 :          0 :         pool.addUnchecked(entry.Fee(CENT).FromTx(txc));
     496                 :          0 :         zigzag_txids.push_back(txc->GetHash());
     497                 :          0 :     }
     498                 :          0 :     const auto vec_iters_zigzag = pool.GetIterVec(zigzag_txids);
     499                 :            :     // It doesn't matter which tx we calculate cluster for, everybody is in it.
     500                 :          0 :     const std::vector<size_t> indices{0, 22, 72, zigzag_txids.size() - 1};
     501                 :          0 :     for (const auto index : indices) {
     502                 :          0 :         const auto cluster = pool.GatherClusters({zigzag_txids[index]});
     503                 :          0 :         BOOST_CHECK_EQUAL(cluster.size(), zigzag_txids.size());
     504                 :          0 :         CTxMemPool::setEntries clusterset{cluster.begin(), cluster.end()};
     505                 :          0 :         BOOST_CHECK_EQUAL(cluster.size(), clusterset.size());
     506                 :          0 :         for (const auto& iter : vec_iters_zigzag) BOOST_CHECK(clusterset.count(iter));
     507                 :          0 :     }
     508                 :          0 : }
     509                 :            : 
     510                 :          0 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14