LCOV - code coverage report
Current view: top level - src/test - rbf_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 98.7 % 398 393
Test Date: 2024-11-04 04:45:35 Functions: 100.0 % 13 13
Branches: 48.8 % 2336 1141

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2021-2022 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 <common/system.h>
       5                 :             : #include <policy/rbf.h>
       6                 :             : #include <random.h>
       7                 :             : #include <test/util/txmempool.h>
       8                 :             : #include <txmempool.h>
       9                 :             : #include <util/time.h>
      10                 :             : 
      11                 :             : #include <test/util/setup_common.h>
      12                 :             : 
      13                 :             : #include <boost/test/unit_test.hpp>
      14                 :             : #include <optional>
      15                 :             : #include <vector>
      16                 :             : 
      17                 :             : BOOST_FIXTURE_TEST_SUITE(rbf_tests, TestingSetup)
      18                 :             : 
      19                 :         127 : static inline CTransactionRef make_tx(const std::vector<CTransactionRef>& inputs,
      20                 :             :                                       const std::vector<CAmount>& output_values)
      21                 :             : {
      22                 :         127 :     CMutableTransaction tx = CMutableTransaction();
      23         [ +  - ]:         127 :     tx.vin.resize(inputs.size());
      24         [ +  - ]:         127 :     tx.vout.resize(output_values.size());
      25         [ +  + ]:         261 :     for (size_t i = 0; i < inputs.size(); ++i) {
      26         [ +  - ]:         134 :         tx.vin[i].prevout.hash = inputs[i]->GetHash();
      27                 :         134 :         tx.vin[i].prevout.n = 0;
      28                 :             :         // Add a witness so wtxid != txid
      29                 :         134 :         CScriptWitness witness;
      30         [ +  - ]:         134 :         witness.stack.emplace_back(i + 10);
      31         [ +  - ]:         134 :         tx.vin[i].scriptWitness = witness;
      32                 :         134 :     }
      33         [ +  + ]:         255 :     for (size_t i = 0; i < output_values.size(); ++i) {
      34   [ +  -  +  - ]:         128 :         tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
      35                 :         128 :         tx.vout[i].nValue = output_values[i];
      36                 :             :     }
      37         [ +  - ]:         254 :     return MakeTransactionRef(tx);
      38                 :         127 : }
      39                 :             : 
      40                 :             : // Make two child transactions from parent (which must have at least 2 outputs).
      41                 :             : // Each tx will have the same outputs, using the amounts specified in output_values.
      42                 :           1 : static inline std::pair<CTransactionRef, CTransactionRef> make_two_siblings(const CTransactionRef parent,
      43                 :             :                                       const std::vector<CAmount>& output_values)
      44                 :             : {
      45         [ -  + ]:           1 :     assert(parent->vout.size() >= 2);
      46                 :             : 
      47                 :             :     // First tx takes first parent output
      48                 :           1 :     CMutableTransaction tx1 = CMutableTransaction();
      49         [ +  - ]:           1 :     tx1.vin.resize(1);
      50         [ +  - ]:           1 :     tx1.vout.resize(output_values.size());
      51                 :             : 
      52         [ +  - ]:           1 :     tx1.vin[0].prevout.hash = parent->GetHash();
      53                 :           1 :     tx1.vin[0].prevout.n = 0;
      54                 :             :     // Add a witness so wtxid != txid
      55                 :           1 :     CScriptWitness witness;
      56         [ +  - ]:           1 :     witness.stack.emplace_back(10);
      57         [ +  - ]:           1 :     tx1.vin[0].scriptWitness = witness;
      58                 :             : 
      59         [ +  + ]:           2 :     for (size_t i = 0; i < output_values.size(); ++i) {
      60   [ +  -  +  - ]:           1 :         tx1.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
      61                 :           1 :         tx1.vout[i].nValue = output_values[i];
      62                 :             :     }
      63                 :             : 
      64                 :             :     // Second tx takes second parent output
      65         [ +  - ]:           1 :     CMutableTransaction tx2 = tx1;
      66         [ +  - ]:           1 :     tx2.vin[0].prevout.n = 1;
      67                 :             : 
      68   [ +  -  +  -  :           3 :     return std::make_pair(MakeTransactionRef(tx1), MakeTransactionRef(tx2));
             -  +  -  + ]
      69                 :           2 : }
      70                 :             : 
      71                 :           8 : static CTransactionRef add_descendants(const CTransactionRef& tx, int32_t num_descendants, CTxMemPool& pool)
      72                 :             :     EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
      73                 :             : {
      74                 :           8 :     AssertLockHeld(::cs_main);
      75                 :           8 :     AssertLockHeld(pool.cs);
      76                 :           8 :     TestMemPoolEntryHelper entry;
      77                 :             :     // Assumes this isn't already spent in mempool
      78         [ +  - ]:           8 :     auto tx_to_spend = tx;
      79         [ +  + ]:         104 :     for (int32_t i{0}; i < num_descendants; ++i) {
      80   [ +  -  +  -  :         384 :         auto next_tx = make_tx(/*inputs=*/{tx_to_spend}, /*output_values=*/{(50 - i) * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
      81   [ +  -  +  - ]:          96 :         pool.addUnchecked(entry.FromTx(next_tx));
      82         [ +  - ]:          96 :         tx_to_spend = next_tx;
      83                 :          96 :     }
      84                 :             :     // Return last created tx
      85                 :           8 :     return tx_to_spend;
      86   [ +  -  +  - ]:         192 : }
      87                 :             : 
      88                 :           1 : static CTransactionRef add_descendant_to_parents(const std::vector<CTransactionRef>& parents, CTxMemPool& pool)
      89                 :             :     EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
      90                 :             : {
      91                 :           1 :     AssertLockHeld(::cs_main);
      92                 :           1 :     AssertLockHeld(pool.cs);
      93                 :           1 :     TestMemPoolEntryHelper entry;
      94                 :             :     // Assumes this isn't already spent in mempool
      95   [ +  -  +  - ]:           2 :     auto child_tx = make_tx(/*inputs=*/parents, /*output_values=*/{50 * CENT});
      96   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.FromTx(child_tx));
      97                 :             :     // Return last created tx
      98                 :           1 :     return child_tx;
      99                 :           0 : }
     100                 :             : 
     101                 :             : // Makes two children for a single parent
     102                 :           1 : static std::pair<CTransactionRef, CTransactionRef> add_children_to_parent(const CTransactionRef parent, CTxMemPool& pool)
     103                 :             :     EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs)
     104                 :             : {
     105                 :           1 :     AssertLockHeld(::cs_main);
     106                 :           1 :     AssertLockHeld(pool.cs);
     107                 :           1 :     TestMemPoolEntryHelper entry;
     108                 :             :     // Assumes this isn't already spent in mempool
     109   [ +  -  +  - ]:           3 :     auto children_tx = make_two_siblings(/*parent=*/parent, /*output_values=*/{50 * CENT});
     110   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.FromTx(children_tx.first));
     111   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.FromTx(children_tx.second));
     112                 :           1 :     return children_tx;
     113                 :           0 : }
     114                 :             : 
     115   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     116                 :             : {
     117                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     118         [ +  - ]:           1 :     LOCK2(::cs_main, pool.cs);
     119                 :           1 :     TestMemPoolEntryHelper entry;
     120                 :             : 
     121                 :           1 :     const CAmount low_fee{CENT/100};
     122                 :           1 :     const CAmount normal_fee{CENT/10};
     123                 :           1 :     const CAmount high_fee{CENT};
     124                 :             : 
     125                 :             :     // Create a parent tx1 and child tx2 with normal fees:
     126   [ +  -  +  -  :           4 :     const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     127   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx1));
     128   [ +  -  +  -  :           4 :     const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     129   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
     130                 :             : 
     131                 :             :     // Create a low-feerate parent tx3 and high-feerate child tx4 (cpfp)
     132   [ +  -  +  -  :           4 :     const auto tx3 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {1099 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     133   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx3));
     134   [ +  -  +  -  :           4 :     const auto tx4 = make_tx(/*inputs=*/ {tx3}, /*output_values=*/ {999 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     135   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
     136                 :             : 
     137                 :             :     // Create a parent tx5 and child tx6 where both have very low fees
     138   [ +  -  +  -  :           4 :     const auto tx5 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {1099 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     139   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
     140   [ +  -  +  -  :           4 :     const auto tx6 = make_tx(/*inputs=*/ {tx5}, /*output_values=*/ {1098 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     141   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx6));
     142                 :             :     // Make tx6's modified fee much higher than its base fee. This should cause it to pass
     143                 :             :     // the fee-related checks despite being low-feerate.
     144         [ +  - ]:           1 :     pool.PrioritiseTransaction(tx6->GetHash(), 1 * COIN);
     145                 :             : 
     146                 :             :     // Two independent high-feerate transactions, tx7 and tx8
     147   [ +  -  +  -  :           4 :     const auto tx7 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {999 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     148   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
     149   [ +  -  +  -  :           4 :     const auto tx8 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {999 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     150   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(tx8));
     151                 :             : 
     152                 :             :     // Normal txs, will chain txns right before CheckConflictTopology test
     153   [ +  -  +  -  :           4 :     const auto tx9 = make_tx(/*inputs=*/ {m_coinbase_txns[5]}, /*output_values=*/ {995 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     154   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx9));
     155   [ +  -  +  -  :           4 :     const auto tx10 = make_tx(/*inputs=*/ {m_coinbase_txns[6]}, /*output_values=*/ {995 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     156   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx10));
     157                 :             : 
     158                 :             :     // Will make these two parents of single child
     159   [ +  -  +  -  :           4 :     const auto tx11 = make_tx(/*inputs=*/ {m_coinbase_txns[7]}, /*output_values=*/ {995 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     160   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx11));
     161   [ +  -  +  -  :           5 :     const auto tx12 = make_tx(/*inputs=*/ {m_coinbase_txns[8]}, /*output_values=*/ {995 * CENT});
          +  -  +  -  +  
          +  +  -  +  -  
             -  -  -  - ]
     162   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx12));
     163                 :             : 
     164                 :             :     // Will make two children of this single parent
     165   [ +  -  +  -  :           4 :     const auto tx13 = make_tx(/*inputs=*/ {m_coinbase_txns[9]}, /*output_values=*/ {995 * CENT, 995 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     166   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx13));
     167                 :             : 
     168         [ +  - ]:           1 :     const auto entry1_normal = pool.GetIter(tx1->GetHash()).value();
     169         [ +  - ]:           1 :     const auto entry2_normal = pool.GetIter(tx2->GetHash()).value();
     170         [ +  - ]:           1 :     const auto entry3_low = pool.GetIter(tx3->GetHash()).value();
     171         [ +  - ]:           1 :     const auto entry4_high = pool.GetIter(tx4->GetHash()).value();
     172         [ +  - ]:           1 :     const auto entry5_low = pool.GetIter(tx5->GetHash()).value();
     173         [ +  - ]:           1 :     const auto entry6_low_prioritised = pool.GetIter(tx6->GetHash()).value();
     174         [ +  - ]:           1 :     const auto entry7_high = pool.GetIter(tx7->GetHash()).value();
     175         [ +  - ]:           1 :     const auto entry8_high = pool.GetIter(tx8->GetHash()).value();
     176         [ +  - ]:           1 :     const auto entry9_unchained = pool.GetIter(tx9->GetHash()).value();
     177         [ +  - ]:           1 :     const auto entry10_unchained = pool.GetIter(tx10->GetHash()).value();
     178         [ +  - ]:           1 :     const auto entry11_unchained = pool.GetIter(tx11->GetHash()).value();
     179         [ +  - ]:           1 :     const auto entry12_unchained = pool.GetIter(tx12->GetHash()).value();
     180         [ +  - ]:           1 :     const auto entry13_unchained = pool.GetIter(tx13->GetHash()).value();
     181                 :             : 
     182   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry1_normal->GetFee(), normal_fee);
     183   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry2_normal->GetFee(), normal_fee);
     184   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry3_low->GetFee(), low_fee);
     185   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry4_high->GetFee(), high_fee);
     186   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry5_low->GetFee(), low_fee);
     187   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry6_low_prioritised->GetFee(), low_fee);
     188   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry7_high->GetFee(), high_fee);
     189   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(entry8_high->GetFee(), high_fee);
     190                 :             : 
     191         [ +  - ]:           1 :     CTxMemPool::setEntries set_12_normal{entry1_normal, entry2_normal};
     192         [ +  - ]:           1 :     CTxMemPool::setEntries set_34_cpfp{entry3_low, entry4_high};
     193         [ +  - ]:           1 :     CTxMemPool::setEntries set_56_low{entry5_low, entry6_low_prioritised};
     194         [ +  - ]:           1 :     CTxMemPool::setEntries set_78_high{entry7_high, entry8_high};
     195                 :           1 :     CTxMemPool::setEntries all_entries{entry1_normal, entry2_normal, entry3_low, entry4_high,
     196         [ +  - ]:           1 :                                        entry5_low, entry6_low_prioritised, entry7_high, entry8_high};
     197                 :           1 :     CTxMemPool::setEntries empty_set;
     198                 :             : 
     199                 :           1 :     const auto unused_txid{GetRandHash()};
     200                 :             : 
     201                 :             :     // Tests for PaysMoreThanConflicts
     202                 :             :     // These tests use feerate, not absolute fee.
     203   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts(/*iters_conflicting=*/set_12_normal,
          +  -  +  -  +  
                -  +  - ]
     204                 :             :                                       /*replacement_feerate=*/CFeeRate(entry1_normal->GetModifiedFee() + 1, entry1_normal->GetTxSize() + 2),
     205                 :             :                                       /*txid=*/unused_txid).has_value());
     206                 :             :     // Replacement must be strictly greater than the originals.
     207   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1_normal->GetModifiedFee(), entry1_normal->GetTxSize()), unused_txid).has_value());
          +  -  +  -  +  
                -  +  - ]
     208   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1_normal->GetModifiedFee() + 1, entry1_normal->GetTxSize()), unused_txid) == std::nullopt);
          +  -  +  -  +  
                -  +  - ]
     209                 :             :     // These tests use modified fees (including prioritisation), not base fees.
     210   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts({entry5_low}, CFeeRate(entry5_low->GetModifiedFee() + 1, entry5_low->GetTxSize()), unused_txid) == std::nullopt);
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     211   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts({entry6_low_prioritised}, CFeeRate(entry6_low_prioritised->GetFee() + 1, entry6_low_prioritised->GetTxSize()), unused_txid).has_value());
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     212   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts({entry6_low_prioritised}, CFeeRate(entry6_low_prioritised->GetModifiedFee() + 1, entry6_low_prioritised->GetTxSize()), unused_txid) == std::nullopt);
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     213                 :             :     // PaysMoreThanConflicts checks individual feerate, not ancestor feerate. This test compares
     214                 :             :     // replacement_feerate and entry4_high's feerate, which are the same. The replacement_feerate is
     215                 :             :     // considered too low even though entry4_high has a low ancestor feerate.
     216   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysMoreThanConflicts(set_34_cpfp, CFeeRate(entry4_high->GetModifiedFee(), entry4_high->GetTxSize()), unused_txid).has_value());
          +  -  +  -  +  
                -  +  - ]
     217                 :             : 
     218                 :             :     // Tests for EntriesAndTxidsDisjoint
     219   [ +  -  +  -  :           2 :     BOOST_CHECK(EntriesAndTxidsDisjoint(empty_set, {tx1->GetHash()}, unused_txid) == std::nullopt);
          +  -  +  -  +  
                      - ]
     220   [ +  -  +  -  :           2 :     BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx3->GetHash()}, unused_txid) == std::nullopt);
          +  -  +  -  +  
                      - ]
     221   [ +  -  +  -  :           2 :     BOOST_CHECK(EntriesAndTxidsDisjoint({entry2_normal}, {tx2->GetHash()}, unused_txid).has_value());
          +  -  +  -  +  
                -  +  - ]
     222   [ +  -  +  -  :           2 :     BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx1->GetHash()}, unused_txid).has_value());
          +  -  +  -  +  
                      - ]
     223   [ +  -  +  -  :           2 :     BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx2->GetHash()}, unused_txid).has_value());
          +  -  +  -  +  
                      - ]
     224                 :             :     // EntriesAndTxidsDisjoint does not calculate descendants of iters_conflicting; it uses whatever
     225                 :             :     // the caller passed in. As such, no error is returned even though entry2_normal is a descendant of tx1.
     226   [ +  -  +  -  :           2 :     BOOST_CHECK(EntriesAndTxidsDisjoint({entry2_normal}, {tx1->GetHash()}, unused_txid) == std::nullopt);
          +  -  +  -  +  
                -  +  - ]
     227                 :             : 
     228                 :             :     // Tests for PaysForRBF
     229         [ +  - ]:           1 :     const CFeeRate incremental_relay_feerate{DEFAULT_INCREMENTAL_RELAY_FEE};
     230                 :           1 :     const CFeeRate higher_relay_feerate{2 * DEFAULT_INCREMENTAL_RELAY_FEE};
     231                 :             :     // Must pay at least as much as the original.
     232   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(/*original_fees=*/high_fee,
             +  -  +  - ]
     233                 :             :                            /*replacement_fees=*/high_fee,
     234                 :             :                            /*replacement_vsize=*/1,
     235                 :             :                            /*relay_fee=*/CFeeRate(0),
     236                 :             :                            /*txid=*/unused_txid)
     237                 :             :                            == std::nullopt);
     238   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(high_fee, high_fee - 1, 1, CFeeRate(0), unused_txid).has_value());
             +  -  +  - ]
     239   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(high_fee + 1, high_fee, 1, CFeeRate(0), unused_txid).has_value());
             +  -  +  - ]
     240                 :             :     // Additional fees must cover the replacement's vsize at incremental relay fee
     241   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 2, incremental_relay_feerate, unused_txid).has_value());
             +  -  +  - ]
     242   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, incremental_relay_feerate, unused_txid) == std::nullopt);
             +  -  +  - ]
     243   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, higher_relay_feerate, unused_txid).has_value());
             +  -  +  - ]
     244   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(high_fee, high_fee + 4, 2, higher_relay_feerate, unused_txid) == std::nullopt);
             +  -  +  - ]
     245   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(low_fee, high_fee, 99999999, incremental_relay_feerate, unused_txid).has_value());
             +  -  +  - ]
     246   [ +  -  +  -  :           2 :     BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt);
             +  -  +  - ]
     247                 :             : 
     248                 :             :     // Tests for GetEntriesForConflicts
     249         [ +  - ]:           1 :     CTxMemPool::setEntries all_parents{entry1_normal, entry3_low, entry5_low, entry7_high, entry8_high};
     250         [ +  - ]:           1 :     CTxMemPool::setEntries all_children{entry2_normal, entry4_high, entry6_low_prioritised};
     251         [ +  - ]:           1 :     const std::vector<CTransactionRef> parent_inputs({m_coinbase_txns[0], m_coinbase_txns[1], m_coinbase_txns[2],
     252   [ +  +  +  -  :           6 :                                                 m_coinbase_txns[3], m_coinbase_txns[4]});
             -  -  -  - ]
     253   [ +  -  +  -  :           2 :     const auto conflicts_with_parents = make_tx(parent_inputs, {50 * CENT});
                   +  - ]
     254         [ +  - ]:           1 :     CTxMemPool::setEntries all_conflicts;
     255   [ +  -  +  -  :           2 :     BOOST_CHECK(GetEntriesForConflicts(/*tx=*/ *conflicts_with_parents.get(),
             +  -  +  - ]
     256                 :             :                                        /*pool=*/ pool,
     257                 :             :                                        /*iters_conflicting=*/ all_parents,
     258                 :             :                                        /*all_conflicts=*/ all_conflicts) == std::nullopt);
     259   [ +  -  +  - ]:           2 :     BOOST_CHECK(all_conflicts == all_entries);
     260                 :           1 :     auto conflicts_size = all_conflicts.size();
     261                 :           1 :     all_conflicts.clear();
     262                 :             : 
     263         [ +  - ]:           1 :     add_descendants(tx2, 23, pool);
     264   [ +  -  +  -  :           2 :     BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
             +  -  +  - ]
     265                 :           1 :     conflicts_size += 23;
     266   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
     267                 :           1 :     all_conflicts.clear();
     268                 :             : 
     269         [ +  - ]:           1 :     add_descendants(tx4, 23, pool);
     270   [ +  -  +  -  :           2 :     BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
             +  -  +  - ]
     271                 :           1 :     conflicts_size += 23;
     272   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
     273                 :           1 :     all_conflicts.clear();
     274                 :             : 
     275         [ +  - ]:           1 :     add_descendants(tx6, 23, pool);
     276   [ +  -  +  -  :           2 :     BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
             +  -  +  - ]
     277                 :           1 :     conflicts_size += 23;
     278   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
     279                 :           1 :     all_conflicts.clear();
     280                 :             : 
     281         [ +  - ]:           1 :     add_descendants(tx7, 23, pool);
     282   [ +  -  +  -  :           2 :     BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt);
             +  -  +  - ]
     283                 :           1 :     conflicts_size += 23;
     284   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size);
     285   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(all_conflicts.size(), 100);
     286                 :           1 :     all_conflicts.clear();
     287                 :             : 
     288                 :             :     // Exceeds maximum number of conflicts.
     289         [ +  - ]:           1 :     add_descendants(tx8, 1, pool);
     290   [ +  -  +  -  :           2 :     BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts).has_value());
             +  -  +  - ]
     291                 :             : 
     292                 :             :     // Tests for HasNoNewUnconfirmed
     293   [ +  -  +  -  :           4 :     const auto spends_unconfirmed = make_tx({tx1}, {36 * CENT});
          +  +  +  -  -  
                -  -  - ]
     294         [ +  + ]:           2 :     for (const auto& input : spends_unconfirmed->vin) {
     295                 :             :         // Spends unconfirmed inputs.
     296   [ +  -  +  -  :           2 :         BOOST_CHECK(pool.exists(GenTxid::Txid(input.prevout.hash)));
                   +  - ]
     297                 :             :     }
     298   [ +  -  +  -  :           2 :     BOOST_CHECK(HasNoNewUnconfirmed(/*tx=*/ *spends_unconfirmed.get(),
             +  -  +  - ]
     299                 :             :                                     /*pool=*/ pool,
     300                 :             :                                     /*iters_conflicting=*/ all_entries) == std::nullopt);
     301   [ +  -  +  -  :           2 :     BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, {entry2_normal}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     302   [ +  -  +  -  :           2 :     BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, empty_set).has_value());
             +  -  +  - ]
     303                 :             : 
     304   [ +  -  +  -  :           5 :     const auto spends_new_unconfirmed = make_tx({tx1, tx8}, {36 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     305   [ +  -  +  -  :           2 :     BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, {entry2_normal}).has_value());
          +  -  +  -  +  
                      - ]
     306   [ +  -  +  -  :           2 :     BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, all_entries).has_value());
             +  -  +  - ]
     307                 :             : 
     308   [ +  -  +  -  :           5 :     const auto spends_conflicting_confirmed = make_tx({m_coinbase_txns[0], m_coinbase_txns[1]}, {45 * CENT});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     309   [ +  -  +  -  :           2 :     BOOST_CHECK(HasNoNewUnconfirmed(*spends_conflicting_confirmed.get(), pool, {entry1_normal, entry3_low}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     310                 :             : 
     311                 :             :     // Tests for CheckConflictTopology
     312                 :             : 
     313                 :             :     // Tx4 has 23 descendants
     314   [ +  -  +  -  :           3 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology(set_34_cpfp).value(), strprintf("%s has 23 descendants, max 1 allowed", entry4_high->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     315                 :             : 
     316                 :             :     // No descendants yet
     317   [ +  -  +  -  :           2 :     BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     318                 :             : 
     319                 :             :     // Add 1 descendant, still ok
     320         [ +  - ]:           1 :     add_descendants(tx9, 1, pool);
     321   [ +  -  +  -  :           2 :     BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     322                 :             : 
     323                 :             :     // N direct conflicts; ok
     324   [ +  -  +  -  :           2 :     BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     325                 :             : 
     326                 :             :     // Add 1 descendant, still ok, even if it's considered a direct conflict as well
     327         [ +  - ]:           1 :     const auto child_tx = add_descendants(tx10, 1, pool);
     328         [ +  - ]:           1 :     const auto entry10_child = pool.GetIter(child_tx->GetHash()).value();
     329   [ +  -  +  -  :           2 :     BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     330   [ +  -  +  -  :           2 :     BOOST_CHECK(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained, entry10_child}) == std::nullopt);
          +  -  +  -  +  
                      - ]
     331                 :             : 
     332                 :             :     // One more, size 3 cluster too much
     333         [ +  - ]:           1 :     const auto grand_child_tx = add_descendants(child_tx, 1, pool);
     334         [ +  - ]:           1 :     const auto entry10_grand_child = pool.GetIter(grand_child_tx->GetHash()).value();
     335   [ +  -  +  -  :           3 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry9_unchained, entry10_unchained, entry11_unchained}).value(), strprintf("%s has 2 descendants, max 1 allowed", entry10_unchained->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     336                 :             :     // even if direct conflict is descendent itself
     337   [ +  -  +  -  :           3 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry9_unchained, entry10_grand_child, entry11_unchained}).value(), strprintf("%s has 2 ancestors, max 1 allowed", entry10_grand_child->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     338                 :             : 
     339                 :             :     // Make a single child from two singleton parents
     340   [ +  -  +  +  :           3 :     const auto two_parent_child_tx = add_descendant_to_parents({tx11, tx12}, pool);
          +  -  -  -  -  
                      - ]
     341         [ +  - ]:           1 :     const auto entry_two_parent_child = pool.GetIter(two_parent_child_tx->GetHash()).value();
     342   [ +  -  +  -  :           5 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry11_unchained}).value(), strprintf("%s is not the only parent of child %s", entry11_unchained->GetSharedTx()->GetHash().ToString(), entry_two_parent_child->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     343   [ +  -  +  -  :           5 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry12_unchained}).value(), strprintf("%s is not the only parent of child %s", entry12_unchained->GetSharedTx()->GetHash().ToString(), entry_two_parent_child->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     344   [ +  -  +  -  :           3 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry_two_parent_child}).value(), strprintf("%s has 2 ancestors, max 1 allowed", entry_two_parent_child->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     345                 :             : 
     346                 :             :     // Single parent with two children, we will conflict with the siblings directly only
     347   [ +  -  +  - ]:           2 :     const auto two_siblings = add_children_to_parent(tx13, pool);
     348         [ +  - ]:           1 :     const auto entry_sibling_1 = pool.GetIter(two_siblings.first->GetHash()).value();
     349         [ +  - ]:           1 :     const auto entry_sibling_2 = pool.GetIter(two_siblings.second->GetHash()).value();
     350   [ +  -  +  -  :           5 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry_sibling_1}).value(), strprintf("%s is not the only child of parent %s", entry_sibling_1->GetSharedTx()->GetHash().ToString(), entry13_unchained->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     351   [ +  -  +  -  :           5 :     BOOST_CHECK_EQUAL(pool.CheckConflictTopology({entry_sibling_2}).value(), strprintf("%s is not the only child of parent %s", entry_sibling_2->GetSharedTx()->GetHash().ToString(), entry13_unchained->GetSharedTx()->GetHash().ToString()));
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     352                 :             : 
     353   [ +  -  +  -  :          55 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
             +  -  +  - ]
     354                 :             : 
     355   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(improves_feerate, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     356                 :             : {
     357                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     358         [ +  - ]:           1 :     LOCK2(::cs_main, pool.cs);
     359                 :           1 :     TestMemPoolEntryHelper entry;
     360                 :             : 
     361                 :           1 :     const CAmount low_fee{CENT/100};
     362                 :           1 :     const CAmount normal_fee{CENT/10};
     363                 :             : 
     364                 :             :     // low feerate parent with normal feerate child
     365   [ +  -  +  -  :           4 :     const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     366   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(tx1));
     367   [ +  -  +  -  :           4 :     const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     368   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
     369                 :             : 
     370         [ +  - ]:           1 :     const auto entry1 = pool.GetIter(tx1->GetHash()).value();
     371         [ +  - ]:           1 :     const auto tx1_fee = entry1->GetModifiedFee();
     372         [ +  - ]:           1 :     const auto tx1_size = entry1->GetTxSize();
     373         [ +  - ]:           1 :     const auto entry2 = pool.GetIter(tx2->GetHash()).value();
     374         [ +  - ]:           1 :     const auto tx2_fee = entry2->GetModifiedFee();
     375         [ +  - ]:           1 :     const auto tx2_size = entry2->GetTxSize();
     376                 :             : 
     377                 :             :     // Now test ImprovesFeerateDiagram with various levels of "package rbf" feerates
     378                 :             : 
     379                 :             :     // It doesn't improve itself
     380   [ +  -  +  -  :           2 :     const auto res1 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee, tx1_size + tx2_size);
                   +  - ]
     381   [ +  -  +  -  :           2 :     BOOST_CHECK(res1.has_value());
                   +  - ]
     382   [ +  -  +  -  :           2 :     BOOST_CHECK(res1.value().first == DiagramCheckError::FAILURE);
             +  -  +  - ]
     383   [ +  -  +  -  :           2 :     BOOST_CHECK(res1.value().second == "insufficient feerate: does not improve feerate diagram");
             +  -  +  - ]
     384                 :             : 
     385                 :             :     // With one more satoshi it does
     386   [ +  -  +  -  :           2 :     BOOST_CHECK(ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size) == std::nullopt);
          +  -  +  -  +  
                -  +  - ]
     387                 :             : 
     388                 :             :     // With prioritisation of in-mempool conflicts, it affects the results of the comparison using the same args as just above
     389   [ +  -  +  - ]:           2 :     pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/1);
     390   [ +  -  +  -  :           2 :     const auto res2 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size);
                   +  - ]
     391   [ +  -  +  -  :           2 :     BOOST_CHECK(res2.has_value());
                   +  - ]
     392   [ +  -  +  -  :           2 :     BOOST_CHECK(res2.value().first == DiagramCheckError::FAILURE);
             +  -  +  - ]
     393   [ +  -  +  -  :           2 :     BOOST_CHECK(res2.value().second == "insufficient feerate: does not improve feerate diagram");
             +  -  +  - ]
     394   [ +  -  +  - ]:           2 :     pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/-1);
     395                 :             : 
     396                 :             :     // With one less vB it does
     397   [ +  -  +  -  :           2 :     BOOST_CHECK(ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee, tx1_size + tx2_size - 1) == std::nullopt);
          +  -  +  -  +  
                -  +  - ]
     398                 :             : 
     399                 :             :     // Adding a grandchild makes the cluster size 3, which is uncalculable
     400   [ +  -  +  -  :           4 :     const auto tx3 = make_tx(/*inputs=*/ {tx2}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     401   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx3));
     402   [ +  -  +  -  :           2 :     const auto res3 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size);
                   +  - ]
     403   [ +  -  +  -  :           2 :     BOOST_CHECK(res3.has_value());
                   +  - ]
     404   [ +  -  +  -  :           2 :     BOOST_CHECK(res3.value().first == DiagramCheckError::UNCALCULABLE);
             +  -  +  - ]
     405   [ +  -  +  -  :           3 :     BOOST_CHECK(res3.value().second == strprintf("%s has 2 descendants, max 1 allowed", tx1->GetHash().GetHex()));
             +  -  +  - ]
     406                 :             : 
     407   [ +  -  +  -  :          11 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     408                 :             : 
     409   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     410                 :             : {
     411                 :           1 :     CTxMemPool& pool = *Assert(m_node.mempool);
     412         [ +  - ]:           1 :     LOCK2(::cs_main, pool.cs);
     413                 :           1 :     TestMemPoolEntryHelper entry;
     414                 :             : 
     415                 :           1 :     const CAmount low_fee{CENT/100};
     416                 :           1 :     const CAmount normal_fee{CENT/10};
     417                 :           1 :     const CAmount high_fee{CENT};
     418                 :             : 
     419                 :             :     // low -> high -> medium fee transactions that would result in two chunks together since they
     420                 :             :     // are all same size
     421   [ +  -  +  -  :           4 :     const auto low_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     422   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx));
     423                 :             : 
     424         [ +  - ]:           1 :     const auto entry_low = pool.GetIter(low_tx->GetHash()).value();
     425         [ +  - ]:           1 :     const auto low_size = entry_low->GetTxSize();
     426                 :             : 
     427                 :             :     // Replacement of size 1
     428                 :           1 :     {
     429   [ +  -  +  -  :           2 :         const auto replace_one{pool.CalculateChunksForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low})};
                   +  - ]
     430   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_one.has_value());
                   +  - ]
     431         [ +  - ]:           1 :         std::vector<FeeFrac> expected_old_chunks{{low_fee, low_size}};
     432   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_one->first == expected_old_chunks);
                   +  - ]
     433         [ +  - ]:           1 :         std::vector<FeeFrac> expected_new_chunks{{0, 1}};
     434   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_one->second == expected_new_chunks);
     435                 :           1 :     }
     436                 :             : 
     437                 :             :     // Non-zero replacement fee/size
     438                 :           1 :     {
     439   [ +  -  +  -  :           2 :         const auto replace_one_fee{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low})};
                   +  - ]
     440   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_one_fee.has_value());
                   +  - ]
     441         [ +  - ]:           1 :         std::vector<FeeFrac> expected_old_diagram{{low_fee, low_size}};
     442   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_one_fee->first == expected_old_diagram);
                   +  - ]
     443         [ +  - ]:           1 :         std::vector<FeeFrac> expected_new_diagram{{high_fee, low_size}};
     444   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_one_fee->second == expected_new_diagram);
     445                 :           1 :     }
     446                 :             : 
     447                 :             :     // Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF
     448   [ +  -  +  -  :           4 :     const auto high_tx = make_tx(/*inputs=*/ {low_tx}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     449   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx));
     450         [ +  - ]:           1 :     const auto entry_high = pool.GetIter(high_tx->GetHash()).value();
     451         [ +  - ]:           1 :     const auto high_size = entry_high->GetTxSize();
     452                 :             : 
     453                 :           1 :     {
     454   [ +  -  +  -  :           2 :         const auto replace_single_chunk{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high})};
                   +  - ]
     455   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_single_chunk.has_value());
                   +  - ]
     456         [ +  - ]:           1 :         std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
     457   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_single_chunk->first == expected_old_chunks);
                   +  - ]
     458         [ +  - ]:           1 :         std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}};
     459   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_single_chunk->second == expected_new_chunks);
     460                 :           1 :     }
     461                 :             : 
     462                 :             :     // Conflict with the 2nd tx, resulting in new diagram with three entries
     463                 :           1 :     {
     464   [ +  -  +  -  :           2 :         const auto replace_cpfp_child{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high})};
                   +  - ]
     465   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_cpfp_child.has_value());
                   +  - ]
     466         [ +  - ]:           1 :         std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
     467   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_cpfp_child->first == expected_old_chunks);
                   +  - ]
     468         [ +  - ]:           1 :         std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size}, {low_fee, low_size}};
     469   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_cpfp_child->second == expected_new_chunks);
     470                 :           1 :     }
     471                 :             : 
     472                 :             :     // third transaction causes the topology check to fail
     473   [ +  -  +  -  :           4 :     const auto normal_tx = make_tx(/*inputs=*/ {high_tx}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     474   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(normal_fee).FromTx(normal_tx));
     475         [ +  - ]:           1 :     const auto entry_normal = pool.GetIter(normal_tx->GetHash()).value();
     476         [ +  - ]:           1 :     const auto normal_size = entry_normal->GetTxSize();
     477                 :             : 
     478                 :           1 :     {
     479   [ +  -  +  -  :           2 :         const auto replace_too_large{pool.CalculateChunksForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
                   +  - ]
     480   [ +  -  +  -  :           2 :         BOOST_CHECK(!replace_too_large.has_value());
                   +  - ]
     481   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(util::ErrorString(replace_too_large).original, strprintf("%s has 2 descendants, max 1 allowed", low_tx->GetHash().GetHex()));
          +  -  +  -  +  
                      - ]
     482                 :           0 :     }
     483                 :             : 
     484                 :             :     // Make a size 2 cluster that is itself two chunks; evict both txns
     485   [ +  -  +  -  :           4 :     const auto high_tx_2 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     486   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx_2));
     487         [ +  - ]:           1 :     const auto entry_high_2 = pool.GetIter(high_tx_2->GetHash()).value();
     488         [ +  - ]:           1 :     const auto high_size_2 = entry_high_2->GetTxSize();
     489                 :             : 
     490   [ +  -  +  -  :           4 :     const auto low_tx_2 = make_tx(/*inputs=*/ {high_tx_2}, /*output_values=*/ {9 * COIN});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     491   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx_2));
     492         [ +  - ]:           1 :     const auto entry_low_2 = pool.GetIter(low_tx_2->GetHash()).value();
     493         [ +  - ]:           1 :     const auto low_size_2 = entry_low_2->GetTxSize();
     494                 :             : 
     495                 :           1 :     {
     496   [ +  -  +  -  :           2 :         const auto replace_two_chunks_single_cluster{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high_2}, {entry_high_2, entry_low_2})};
                   +  - ]
     497   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_two_chunks_single_cluster.has_value());
                   +  - ]
     498         [ +  - ]:           1 :         std::vector<FeeFrac> expected_old_chunks{{high_fee, high_size_2}, {low_fee, low_size_2}};
     499   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_chunks);
                   +  - ]
     500         [ +  - ]:           1 :         std::vector<FeeFrac> expected_new_chunks{{high_fee, low_size_2}};
     501   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_two_chunks_single_cluster->second == expected_new_chunks);
     502                 :           1 :     }
     503                 :             : 
     504                 :             :     // You can have more than two direct conflicts if the there are multiple affected clusters, all of size 2 or less
     505   [ +  -  +  -  :           4 :     const auto conflict_1 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     506   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1));
     507         [ +  - ]:           1 :     const auto conflict_1_entry = pool.GetIter(conflict_1->GetHash()).value();
     508                 :             : 
     509   [ +  -  +  -  :           4 :     const auto conflict_2 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     510   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_2));
     511         [ +  - ]:           1 :     const auto conflict_2_entry = pool.GetIter(conflict_2->GetHash()).value();
     512                 :             : 
     513   [ +  -  +  -  :           4 :     const auto conflict_3 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {10 * COIN});
          +  -  +  +  +  
          -  +  -  -  -  
                   -  - ]
     514   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_3));
     515         [ +  - ]:           1 :     const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();
     516                 :             : 
     517                 :           1 :     {
     518   [ +  -  +  -  :           2 :         const auto replace_multiple_clusters{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry})};
                   +  - ]
     519   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_multiple_clusters.has_value());
                   +  - ]
     520   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_multiple_clusters->first.size() == 3);
                   +  - ]
     521   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_multiple_clusters->second.size() == 1);
     522                 :           0 :     }
     523                 :             : 
     524                 :             :     // Add a child transaction to conflict_1 and make it cluster size 2, two chunks due to same feerate
     525   [ +  -  +  -  :           4 :     const auto conflict_1_child = make_tx(/*inputs=*/{conflict_1}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     526   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1_child));
     527         [ +  - ]:           1 :     const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
     528                 :             : 
     529                 :           1 :     {
     530   [ +  -  +  -  :           2 :         const auto replace_multiple_clusters_2{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry})};
                   +  - ]
     531                 :             : 
     532   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_multiple_clusters_2.has_value());
                   +  - ]
     533   [ +  -  +  -  :           2 :         BOOST_CHECK(replace_multiple_clusters_2->first.size() == 4);
                   +  - ]
     534   [ +  -  +  - ]:           2 :         BOOST_CHECK(replace_multiple_clusters_2->second.size() == 1);
     535                 :           0 :     }
     536                 :             : 
     537                 :             :     // Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
     538   [ +  -  +  -  :           4 :     const auto conflict_1_grand_child = make_tx(/*inputs=*/{conflict_1_child}, /*output_values=*/ {995 * CENT});
          +  +  +  -  +  
             -  -  -  -  
                      - ]
     539   [ +  -  +  - ]:           1 :     pool.addUnchecked(entry.Fee(high_fee).FromTx(conflict_1_grand_child));
     540         [ +  - ]:           1 :     const auto conflict_1_grand_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
     541                 :             : 
     542                 :           1 :     {
     543   [ +  -  +  -  :           2 :         const auto replace_cluster_size_3{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry, conflict_1_grand_child_entry})};
                   +  - ]
     544                 :             : 
     545   [ +  -  +  -  :           2 :         BOOST_CHECK(!replace_cluster_size_3.has_value());
                   +  - ]
     546   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(util::ErrorString(replace_cluster_size_3).original, strprintf("%s has 2 descendants, max 1 allowed", conflict_1->GetHash().GetHex()));
          +  -  +  -  +  
                      - ]
     547         [ +  - ]:           1 :     }
     548   [ +  -  +  -  :          27 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                -  +  - ]
     549                 :             : 
     550   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(feerate_chunks_utilities)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     551                 :             : {
     552                 :             :     // Sanity check the correctness of the feerate chunks comparison.
     553                 :             : 
     554                 :             :     // A strictly better case.
     555                 :           1 :     std::vector<FeeFrac> old_chunks{{{950, 300}, {100, 100}}};
     556         [ +  - ]:           1 :     std::vector<FeeFrac> new_chunks{{{1000, 300}, {50, 100}}};
     557                 :             : 
     558   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     559   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     560                 :             : 
     561                 :             :     // Incomparable diagrams
     562         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 100}};
     563         [ +  - ]:           1 :     new_chunks = {{1000, 300}, {0, 100}};
     564                 :             : 
     565   [ +  -  +  -  :           3 :     BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
             +  -  +  - ]
     566   [ +  -  +  -  :           3 :     BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
             +  -  +  - ]
     567                 :             : 
     568                 :             :     // Strictly better but smaller size.
     569         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 100}};
     570         [ +  - ]:           1 :     new_chunks = {{1100, 300}};
     571                 :             : 
     572   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     573   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     574                 :             : 
     575                 :             :     // New diagram is strictly better due to the first chunk, even though
     576                 :             :     // second chunk contributes no fees
     577         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 100}};
     578         [ +  - ]:           1 :     new_chunks = {{1100, 100}, {0, 100}};
     579                 :             : 
     580   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     581   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     582                 :             : 
     583                 :             :     // Feerate of first new chunk is better with, but second chunk is worse
     584         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 100}};
     585         [ +  - ]:           1 :     new_chunks = {{750, 100}, {249, 250}, {151, 650}};
     586                 :             : 
     587   [ +  -  +  -  :           3 :     BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
             +  -  +  - ]
     588   [ +  -  +  -  :           3 :     BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
             +  -  +  - ]
     589                 :             : 
     590                 :             :     // If we make the second chunk slightly better, the new diagram now wins.
     591         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 100}};
     592         [ +  - ]:           1 :     new_chunks = {{750, 100}, {250, 250}, {150, 150}};
     593                 :             : 
     594   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_lt(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     595   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_gt(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     596                 :             : 
     597                 :             :     // Identical diagrams, cannot be strictly better
     598         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 100}};
     599         [ +  - ]:           1 :     new_chunks = {{950, 300}, {100, 100}};
     600                 :             : 
     601   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_eq(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     602   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_eq(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     603                 :             : 
     604                 :             :     // Same aggregate fee, but different total size (trigger single tail fee check step)
     605         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 99}};
     606         [ +  - ]:           1 :     new_chunks = {{950, 300}, {100, 100}};
     607                 :             : 
     608                 :             :     // No change in evaluation when tail check needed.
     609   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     610   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     611                 :             : 
     612                 :             :     // Trigger multiple tail fee check steps
     613         [ +  - ]:           1 :     old_chunks = {{950, 300}, {100, 99}};
     614         [ +  - ]:           1 :     new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}};
     615                 :             : 
     616   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_gt(CompareChunks(old_chunks, new_chunks)));
             +  -  +  - ]
     617   [ +  -  +  -  :           2 :     BOOST_CHECK(std::is_lt(CompareChunks(new_chunks, old_chunks)));
             +  -  +  - ]
     618                 :             : 
     619                 :             :     // Multiple tail fee check steps, unordered result
     620         [ +  - ]:           1 :     new_chunks = {{950, 300}, {100, 100}, {0, 1}, {0, 1}, {1, 1}};
     621   [ +  -  +  -  :           3 :     BOOST_CHECK(CompareChunks(old_chunks, new_chunks) == std::partial_ordering::unordered);
             +  -  +  - ]
     622   [ +  -  +  -  :           3 :     BOOST_CHECK(CompareChunks(new_chunks, old_chunks) == std::partial_ordering::unordered);
                   +  - ]
     623                 :           1 : }
     624                 :             : 
     625                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1