LCOV - code coverage report
Current view: top level - src/test - mempool_fee_estimator_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 99.1 % 231 229
Test Date: 2026-08-25 06:16:57 Functions: 100.0 % 8 8
Branches: 52.2 % 876 457

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license. See the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <kernel/mempool_entry.h>
       6                 :             : #include <policy/fees/estimator_args.h>
       7                 :             : #include <policy/fees/mempool_estimator.h>
       8                 :             : #include <policy/policy.h>
       9                 :             : #include <primitives/block.h>
      10                 :             : #include <random.h>
      11                 :             : #include <test/util/setup_common.h>
      12                 :             : #include <test/util/txmempool.h>
      13                 :             : #include <txmempool.h>
      14                 :             : #include <uint256.h>
      15                 :             : #include <util/feefrac.h>
      16                 :             : #include <util/fees.h>
      17                 :             : #include <util/time.h>
      18                 :             : #include <validation.h>
      19                 :             : 
      20                 :             : #include <boost/test/unit_test.hpp>
      21                 :             : 
      22                 :             : #include <string>
      23                 :             : 
      24                 :             : BOOST_FIXTURE_TEST_SUITE(mempool_fee_estimator_tests, TestingSetup)
      25                 :             : 
      26                 :      186335 : static inline CTransactionRef MakeRandomTx()
      27                 :             : {
      28                 :      186335 :     auto rng = FastRandomContext();
      29         [ +  - ]:      186335 :     auto tx = CMutableTransaction();
      30         [ +  - ]:      186335 :     tx.vin.resize(1);
      31         [ +  - ]:      186335 :     tx.vout.resize(1);
      32         [ +  - ]:      186335 :     tx.vin[0].prevout.hash = Txid::FromUint256(rng.rand256());
      33                 :      186335 :     tx.vin[0].prevout.n = 0;
      34         [ +  - ]:      186335 :     tx.vin[0].scriptSig << OP_TRUE;
      35         [ +  - ]:      186335 :     tx.vout[0].scriptPubKey = CScript() << OP_TRUE;
      36         [ +  - ]:      186335 :     tx.vout[0].nValue = COIN;
      37         [ +  - ]:      372670 :     return MakeTransactionRef(tx);
      38                 :      186335 : }
      39                 :             : 
      40                 :          28 : void AddRemovedBlock(MemPoolFeeRateEstimator& fee_est,
      41                 :             :                      int32_t removed_txs_weight,
      42                 :             :                      int32_t block_txs_weight,
      43                 :             :                      unsigned int& height)
      44                 :             : {
      45                 :          28 :     auto block = std::make_shared<CBlock>();
      46                 :          28 :     std::vector<RemovedMempoolTransactionInfo> removed_txs;
      47                 :          28 :     TestMemPoolEntryHelper entry;
      48         [ -  + ]:          28 :     Assert(block_txs_weight >= removed_txs_weight);
      49   [ +  -  +  - ]:          28 :     block->vtx.emplace_back(MakeRandomTx()); // Add a coinbase tx
      50         [ +  + ]:      169376 :     while (block_txs_weight > 0) {
      51         [ +  - ]:      169370 :         auto tx = MakeRandomTx();
      52                 :      169370 :         auto tx_weight = GetTransactionWeight(*tx);
      53         [ +  + ]:      169370 :         if (block_txs_weight - tx_weight < 0) break;
      54         [ +  - ]:      169348 :         block->vtx.emplace_back(tx);
      55                 :      169348 :         block_txs_weight -= tx_weight;
      56         [ +  + ]:      169348 :         if (removed_txs_weight - tx_weight >= 0) {
      57   [ +  -  +  - ]:      157250 :             removed_txs.emplace_back(entry.FromTx(tx));
      58                 :      157250 :             removed_txs_weight -= tx_weight;
      59                 :             :         }
      60                 :      169370 :     }
      61   [ +  -  +  - ]:          56 :     fee_est.MempoolTxsRemovedForBlock(block, removed_txs, height);
      62                 :          28 :     height += 1;
      63         [ +  - ]:          56 : }
      64                 :             : 
      65   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(calculate_max_weight_percentiles)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      66                 :             : {
      67                 :             :     // With no chunks neither percentile can be populated.
      68                 :           1 :     const auto empty = MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles({});
      69         [ +  - ]:           2 :     BOOST_CHECK(empty.p50.IsEmpty());
      70   [ +  -  +  - ]:           2 :     BOOST_CHECK(empty.p75.IsEmpty());
      71                 :           1 :     const int32_t chunk_size{10};
      72                 :           1 :     const int32_t individual_tx_vsize = static_cast<int32_t>(DEFAULT_BLOCK_MAX_WEIGHT / WITNESS_SCALE_FACTOR) / chunk_size;
      73                 :           1 :     const FeePerVSize super_high_fee_rate{500 * individual_tx_vsize, individual_tx_vsize};
      74                 :           1 :     const FeePerVSize high_fee_rate{100 * individual_tx_vsize, individual_tx_vsize};
      75                 :           1 :     const FeePerVSize medium_fee_rate{50 * individual_tx_vsize, individual_tx_vsize};
      76                 :           1 :     const FeePerVSize low_fee_rate{10 * individual_tx_vsize, individual_tx_vsize};
      77                 :           1 :     std::vector<FeePerVSize> chunk_feerates;
      78         [ +  - ]:           1 :     chunk_feerates.reserve(chunk_size);
      79         [ +  + ]:          11 :     for (int i = 0; i < chunk_size; ++i) {
      80         [ +  + ]:          10 :         if (i < 3) {
      81         [ +  - ]:           3 :             chunk_feerates.emplace_back(super_high_fee_rate);
      82         [ +  + ]:           7 :         } else if (i < 5) {
      83         [ +  - ]:           2 :             chunk_feerates.emplace_back(high_fee_rate);
      84         [ +  + ]:           5 :         } else if (i < 8) {
      85         [ +  - ]:           3 :             chunk_feerates.emplace_back(medium_fee_rate);
      86                 :             :             // Once 50% coverage is reached but 75% is not, only the p50 (conservative)
      87                 :             :             // percentile is populated; p75 (economical) is left empty for the caller to floor.
      88         [ +  + ]:           3 :             if (i < 7) {
      89   [ -  +  +  - ]:           2 :                 const auto partial = MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles(chunk_feerates);
      90   [ +  -  +  - ]:           2 :                 BOOST_CHECK_EQUAL(partial.p50.fee, high_fee_rate.fee);
      91   [ +  -  +  - ]:           2 :                 BOOST_CHECK_EQUAL(partial.p50.size, high_fee_rate.size);
      92   [ +  -  +  - ]:           4 :                 BOOST_CHECK(partial.p75.IsEmpty());
      93                 :             :             }
      94                 :             :         } else {
      95         [ +  - ]:           2 :             chunk_feerates.emplace_back(low_fee_rate);
      96                 :             :         }
      97                 :             :     }
      98   [ -  +  +  - ]:           1 :     const auto percentiles = MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles(chunk_feerates);
      99   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(percentiles.p50.fee, high_fee_rate.fee);
     100   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(percentiles.p50.size, high_fee_rate.size);
     101   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(percentiles.p75.fee, medium_fee_rate.fee);
     102   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(percentiles.p75.size, medium_fee_rate.size);
     103   [ +  -  +  - ]:           2 :     BOOST_CHECK(ByRatio{percentiles.p50} > ByRatio{percentiles.p75});
     104                 :           1 : }
     105                 :             : 
     106   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(mempool_fee_rate_estimator_cache)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     107                 :             : {
     108                 :           1 :     FakeNodeClock clock{};
     109                 :           1 :     MemPoolFeeRateEstimatorCache cache;
     110                 :           1 :     const uint256 tip_hash{uint256::ONE};
     111                 :           1 :     const uint256 next_tip_hash{uint256{2}};
     112                 :           1 :     const FeePerVSize conservative{2, 1};
     113                 :           1 :     const FeePerVSize economical{1, 1};
     114                 :             : 
     115   [ +  -  +  -  :           2 :     BOOST_CHECK(cache.IsStale());
             +  -  +  - ]
     116   [ +  -  +  -  :           2 :     BOOST_CHECK(!cache.GetCachedEstimate(tip_hash));
             +  -  +  - ]
     117                 :             : 
     118         [ +  - ]:           1 :     cache.Update(conservative, economical, tip_hash);
     119   [ +  -  +  -  :           2 :     BOOST_CHECK(!cache.IsStale());
             +  -  +  - ]
     120         [ +  - ]:           1 :     const auto cached{cache.GetCachedEstimate(tip_hash)};
     121   [ +  -  +  -  :           2 :     BOOST_REQUIRE(cached);
                   +  - ]
     122   [ +  -  +  -  :           3 :     BOOST_CHECK(cached->m_conservative == conservative);
             +  -  +  - ]
     123   [ +  -  +  -  :           3 :     BOOST_CHECK(cached->m_economical == economical);
             +  -  +  - ]
     124   [ +  -  +  -  :           2 :     BOOST_CHECK(!cache.GetCachedEstimate(next_tip_hash));
             +  -  +  - ]
     125                 :             : 
     126         [ +  - ]:           1 :     clock += CACHE_LIFE + std::chrono::seconds{1};
     127   [ +  -  +  -  :           2 :     BOOST_CHECK(cache.IsStale());
             +  -  +  - ]
     128   [ +  -  +  -  :           2 :     BOOST_CHECK(!cache.GetCachedEstimate(tip_hash));
                   +  - ]
     129                 :           1 : }
     130                 :             : 
     131   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(MempoolFeeRateEstimator)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     132                 :             : {
     133         [ +  - ]:           1 :     auto mempool_estimator = MemPoolFeeRateEstimator(MempoolPolicyEstimatorPath(*m_node.args), *m_node.mempool, *m_node.chainman);
     134   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(mempool_estimator.MaximumTarget(), MEMPOOL_FEE_ESTIMATOR_MAX_TARGET);
     135                 :             :     // Before the mempool has finished loading, no estimate is available.
     136                 :           1 :     {
     137                 :           1 :         const std::string unloaded_err = strprintf("%s: Mempool not loaded yet, no fee rate estimate available",
     138   [ +  -  +  - ]:           1 :                                                    FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY));
     139         [ +  - ]:           1 :         const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     140   [ +  -  +  -  :           2 :         BOOST_CHECK(!result);
                   +  - ]
     141   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.error().reason, unloaded_err);
     142                 :           1 :     }
     143         [ +  - ]:           1 :     m_node.mempool->SetLoadTried(true);
     144                 :             : 
     145   [ +  -  +  -  :           2 :     BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     146   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.GetMempoolHealth() == MemPoolFeeRateEstimator::MempoolHealth::INSUFFICIENT_DATA);
             +  -  +  - ]
     147                 :           1 :     {
     148         [ +  - ]:           1 :         const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     149                 :           1 :         const std::string insufficient_err{strprintf("%s: Not enough recent block data for fee rate estimation",
     150   [ +  -  +  - ]:           1 :                                                      FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY))};
     151   [ +  -  +  -  :           2 :         BOOST_CHECK(!result);
                   +  - ]
     152   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.error().reason, insufficient_err);
     153                 :           1 :     }
     154                 :           1 :     {
     155                 :           1 :         MemPoolFeeRateEstimator custom_mempool_estimator{
     156   [ +  -  +  - ]:           1 :             MempoolPolicyEstimatorPath(*m_node.args), *m_node.mempool, *m_node.chainman};
     157                 :           1 :         unsigned int custom_height{100};
     158         [ +  + ]:           6 :         for (size_t block_count{1}; block_count < MEMPOOL_HEALTH_WINDOW_BLOCKS; ++block_count) {
     159         [ +  - ]:           5 :             AddRemovedBlock(custom_mempool_estimator,
     160                 :             :                             /*removed_txs_weight=*/0,
     161                 :             :                             /*block_txs_weight=*/0,
     162                 :             :                             custom_height);
     163   [ +  -  +  -  :          10 :             BOOST_CHECK(!custom_mempool_estimator.IsMempoolHealthy());
                   +  - ]
     164                 :             :         }
     165                 :           1 :         {
     166                 :           1 :             const int64_t low_activity_weight{1000};
     167         [ +  - ]:           1 :             AddRemovedBlock(custom_mempool_estimator, low_activity_weight / 2, low_activity_weight, custom_height);
     168                 :             :         }
     169                 :             :         // Below one block worth of total activity across the full window, even
     170                 :             :         // poor coverage in the only non-empty block is too noisy to reject the
     171                 :             :         // mempool as unhealthy.
     172   [ +  -  +  -  :           2 :         BOOST_CHECK(custom_mempool_estimator.IsMempoolHealthy());
                   +  - ]
     173                 :           1 :     }
     174                 :           1 :     size_t block_count = 1;
     175                 :           1 :     const int64_t weight{DEFAULT_BLOCK_MAX_WEIGHT / 2};
     176                 :           1 :     unsigned int height = 100;
     177                 :             :     // Equal weight
     178         [ +  + ]:           7 :     while (block_count <= MEMPOOL_HEALTH_WINDOW_BLOCKS) {
     179         [ +  - ]:           6 :         AddRemovedBlock(mempool_estimator, weight, weight, height);
     180         [ +  + ]:           6 :         if (block_count < MEMPOOL_HEALTH_WINDOW_BLOCKS) {
     181   [ +  -  +  -  :          10 :             BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
                   +  - ]
     182                 :             :         }
     183                 :           6 :         block_count += 1;
     184                 :             :     }
     185                 :             :     // Total txs weight ~11999k WU (~3.0 blocks), removed txs ~11999k WU (~3.0 blocks); coverage = 100%.
     186   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     187                 :             :     // Adding a single underrepresented block will not make the mempool unhealthy
     188                 :             :     // while the window coverage remains above the threshold.
     189         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight / 2, weight, height);
     190                 :             :     // Total txs weight ~11999k WU (~3.0 blocks), removed txs ~10999k WU (~2.75 blocks); coverage = ~92%.
     191   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     192                 :             :     // Empty block
     193                 :             :     // Total txs weight ~9999k WU (~2.5 blocks), removed txs ~8999k WU (~2.25 blocks); coverage = 90%.
     194         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, 0, 0, height);
     195   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     196                 :             :     // Total txs weight ~9999k WU (~2.5 blocks), removed txs ~7999k WU (~2.0 blocks); coverage = 80%.
     197         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight / 2, weight, height);
     198   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     199                 :             :     // Total txs weight ~9999k WU (~2.5 blocks), removed txs ~7000k WU (~1.75 blocks); coverage = 70%.
     200         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight / 2, weight, height);
     201   [ +  -  +  -  :           2 :     BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
                   +  - ]
     202                 :           1 :     block_count = 1;
     203         [ +  + ]:           4 :     while (block_count <= 3) {
     204         [ +  - ]:           3 :         AddRemovedBlock(mempool_estimator, weight, weight, height);
     205         [ +  + ]:           3 :         if (block_count < 3) {
     206   [ +  -  +  -  :           4 :             BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
                   +  - ]
     207                 :             :         }
     208                 :           3 :         block_count += 1;
     209                 :             :     }
     210                 :             :     // Total txs weight ~9999k WU (~2.5 blocks), removed txs ~7999k WU (~2.0 blocks); coverage = 80%.
     211   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     212                 :             : 
     213                 :             :     // Reorg out and replace the last block. Replacing the tip block should keep a full
     214                 :             :     // healthy window when the replacement block has good mempool representation.
     215                 :           1 :     height -= 1;
     216         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight, weight, height);
     217   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     218                 :             : 
     219                 :             :     // Reorg out the last two blocks. The estimator should discard the stale suffix,
     220                 :             :     // become temporarily unhealthy due to having fewer than MEMPOOL_HEALTH_WINDOW_BLOCKS stats,
     221                 :             :     // then recover after the replacement chain catches up.
     222                 :           1 :     height -= 2;
     223         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight, weight, height);
     224   [ +  -  +  -  :           2 :     BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     225         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight, weight, height);
     226   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     227                 :             : 
     228                 :             :     // A forward height gap (e.g. stale persisted stats after an unclean shutdown
     229                 :             :     // while the chain advanced) resets the tracked window entirely; the estimator
     230                 :             :     // stays unhealthy until a full window of contiguous blocks is seen again.
     231                 :           1 :     height += 3;
     232         [ +  - ]:           1 :     AddRemovedBlock(mempool_estimator, weight, weight, height);
     233   [ +  -  +  -  :           2 :     BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
                   +  - ]
     234         [ +  + ]:           6 :     for (size_t i = 1; i < MEMPOOL_HEALTH_WINDOW_BLOCKS; ++i) {
     235         [ +  - ]:           5 :         AddRemovedBlock(mempool_estimator, weight, weight, height);
     236         [ +  + ]:           5 :         if (i < MEMPOOL_HEALTH_WINDOW_BLOCKS - 1) {
     237   [ +  -  +  -  :           8 :             BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());
                   +  - ]
     238                 :             :         }
     239                 :             :     }
     240   [ +  -  +  -  :           2 :     BOOST_CHECK(mempool_estimator.IsMempoolHealthy());
             +  -  +  - ]
     241                 :           1 :     {
     242         [ +  - ]:           1 :         LOCK(m_node.mempool->cs);
     243   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(m_node.mempool->GetTotalTxSize(), 0);
                   +  - ]
     244                 :           0 :     }
     245                 :             :     // With an empty mempool there is nothing to build a feerate estimate from, so both
     246                 :             :     // estimates fall back to the floor fee rate: the higher of the minimum relay fee rate
     247                 :             :     // and the current mempool minimum fee rate.
     248   [ +  -  +  - ]:           1 :     const FeePerVSize floor{std::max(m_node.mempool->m_opts.min_relay_feerate, m_node.mempool->GetMinFee()).GetFeePerVSize()};
     249                 :           1 :     {
     250         [ +  - ]:           1 :         const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     251   [ +  -  +  -  :           2 :         BOOST_REQUIRE(result.has_value());
                   +  - ]
     252   [ +  -  +  -  :           3 :         BOOST_CHECK(result->feerate == floor);
             +  -  +  - ]
     253   [ +  -  +  -  :           2 :         BOOST_CHECK(result->feerate_estimator == FeeRateEstimatorType::MEMPOOL_POLICY);
                   +  - ]
     254   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result->returned_target, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET);
     255                 :             : 
     256                 :             :         // The floor estimate is cached like any other; a second call returns the same value.
     257         [ +  - ]:           1 :         const auto cached_result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     258   [ +  -  +  -  :           2 :         BOOST_REQUIRE(cached_result.has_value());
                   +  - ]
     259   [ +  -  +  -  :           3 :         BOOST_CHECK(cached_result->feerate == floor);
                   +  - ]
     260                 :           1 :     }
     261                 :           1 :     TestMemPoolEntryHelper entry;
     262   [ +  -  +  -  :           1 :     const auto tx_vsize = entry.FromTx(MakeRandomTx()).GetTxSize();
             +  -  +  - ]
     263                 :           1 :     const CAmount low_fee{CENT / 3000};
     264                 :           1 :     const CAmount med_fee{CENT / 100};
     265                 :           1 :     const CAmount high_fee{CENT / 10};
     266                 :           1 :     const CAmount very_high_fee{CENT};
     267                 :             :     // A mempool that cannot fill 50% of a block leaves both percentiles empty,
     268                 :             :     // so both estimate still fall back to the floor.
     269                 :           1 :     {
     270                 :             :         // Add high_fee transactions until mempool weight exceeds 25% of DEFAULT_BLOCK_MAX_WEIGHT.
     271                 :           1 :         {
     272   [ +  -  +  - ]:           1 :             LOCK2(cs_main, m_node.mempool->cs);
     273         [ +  + ]:        4034 :             while ((m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <= (DEFAULT_BLOCK_MAX_WEIGHT * 25 / 100)) {
     274   [ +  -  +  -  :        8066 :                 TryAddToMempool(*m_node.mempool, entry.Fee(high_fee).FromTx(MakeRandomTx()));
             +  -  +  - ]
     275                 :             :             }
     276         [ +  - ]:           1 :         }
     277                 :             :         // Expire the cached floor estimate so the denser mempool is observed.
     278         [ +  - ]:           1 :         SetMockTime(GetTime<std::chrono::seconds>() + CACHE_LIFE + std::chrono::seconds{1});
     279         [ +  - ]:           1 :         const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     280   [ +  -  +  -  :           2 :         BOOST_REQUIRE(result.has_value());
                   +  - ]
     281   [ +  -  +  -  :           3 :         BOOST_CHECK(result->feerate == floor);
                   +  - ]
     282                 :           0 :     }
     283                 :             :     // A mempool that fills 50% of a block but not 75% has a conservative (p50)
     284                 :             :     // estimate, while the economical (p75) estimate falls back to the floor.
     285                 :           1 :     {
     286                 :             :         // Add med_fee transactions until mempool weight exceeds 50% of DEFAULT_BLOCK_MAX_WEIGHT.
     287                 :           1 :         {
     288   [ +  -  +  - ]:           1 :             LOCK2(cs_main, m_node.mempool->cs);
     289         [ +  + ]:        4033 :             while ((m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <= (DEFAULT_BLOCK_MAX_WEIGHT * 50 / 100)) {
     290   [ +  -  +  -  :        8064 :                 TryAddToMempool(*m_node.mempool, entry.Fee(med_fee).FromTx(MakeRandomTx()));
             +  -  +  - ]
     291                 :             :             }
     292         [ +  - ]:           1 :         }
     293         [ +  - ]:           1 :         SetMockTime(GetTime<std::chrono::seconds>() + CACHE_LIFE + std::chrono::seconds{1});
     294         [ +  - ]:           1 :         const auto conservative = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     295         [ +  - ]:           1 :         const auto economical = mempool_estimator.EstimateFeeRate(/*conservative=*/false);
     296   [ +  -  +  -  :           2 :         BOOST_REQUIRE(conservative.has_value());
                   +  - ]
     297   [ +  -  +  -  :           2 :         BOOST_REQUIRE(economical.has_value());
                   +  - ]
     298   [ +  -  +  -  :           3 :         BOOST_CHECK(conservative->feerate == FeeFrac(med_fee, tx_vsize));
             +  -  +  - ]
     299   [ +  -  +  -  :           3 :         BOOST_CHECK(economical->feerate == floor);
                   +  - ]
     300                 :           1 :     }
     301                 :             :     // Mempool transactions are enough to provide both feerate estimates.
     302                 :           1 :     {
     303                 :             :         // Add low_fee transactions until mempool transactions weight
     304                 :             :         // is enough to reach the 75% coverage requirement
     305                 :           1 :         {
     306   [ +  -  +  - ]:           1 :             LOCK2(cs_main, m_node.mempool->cs);
     307         [ +  + ]:        4033 :             while ((m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <= (DEFAULT_BLOCK_MAX_WEIGHT * 75 / 100)) {
     308   [ +  -  +  -  :        8064 :                 TryAddToMempool(*m_node.mempool, entry.Fee(low_fee).FromTx(MakeRandomTx()));
             +  -  +  - ]
     309                 :             :             }
     310         [ +  - ]:           1 :         }
     311                 :             :         // Expire the sparse-result cache before expecting the estimator to observe the denser mempool.
     312         [ +  - ]:           1 :         SetMockTime(GetTime<std::chrono::seconds>() + CACHE_LIFE + std::chrono::seconds{1});
     313         [ +  - ]:           1 :         const auto result_conservative = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
     314         [ +  - ]:           1 :         const auto result_economical = mempool_estimator.EstimateFeeRate(/*conservative=*/false);
     315   [ +  -  +  -  :           2 :         BOOST_CHECK(result_conservative.has_value());
                   +  - ]
     316   [ +  -  +  -  :           2 :         BOOST_CHECK(result_economical.has_value());
                   +  - ]
     317   [ +  -  +  -  :           3 :         BOOST_CHECK(result_economical->feerate == FeeFrac(low_fee, tx_vsize));
             +  -  +  - ]
     318   [ +  -  +  -  :           3 :         BOOST_CHECK(result_conservative->feerate == FeeFrac(med_fee, tx_vsize));
             +  -  +  - ]
     319   [ +  -  +  -  :           2 :         BOOST_CHECK(ByRatio{result_conservative->feerate} > ByRatio{result_economical->feerate});
                   +  - ]
     320   [ +  -  +  -  :           2 :         BOOST_CHECK(result_conservative->feerate_estimator == FeeRateEstimatorType::MEMPOOL_POLICY);
                   +  - ]
     321   [ +  -  +  -  :           2 :         BOOST_CHECK(result_economical->feerate_estimator == FeeRateEstimatorType::MEMPOOL_POLICY);
                   +  - ]
     322   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result_conservative->returned_target, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET);
     323   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result_economical->returned_target, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET);
     324                 :             : 
     325                 :             :         // Adding another 30% of very-high-fee transactions should change the
     326                 :             :         // estimates after recomputation, but not while the cached estimate is fresh.
     327                 :           1 :         {
     328   [ +  -  +  - ]:           1 :             LOCK2(cs_main, m_node.mempool->cs);
     329         [ +  + ]:        4840 :             while ((m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <=
     330                 :             :                    (DEFAULT_BLOCK_MAX_WEIGHT * 105 / 100)) {
     331   [ +  -  +  -  :        9678 :                 TryAddToMempool(*m_node.mempool, entry.Fee(very_high_fee).FromTx(MakeRandomTx()));
             +  -  +  - ]
     332                 :             :             }
     333         [ +  - ]:           1 :         }
     334   [ +  -  +  -  :           4 :         BOOST_CHECK(mempool_estimator.EstimateFeeRate(/*conservative=*/false).value().feerate == FeeFrac(low_fee, tx_vsize));
          +  -  +  -  +  
                      - ]
     335   [ +  -  +  -  :           4 :         BOOST_CHECK(mempool_estimator.EstimateFeeRate(/*conservative=*/true).value().feerate == FeeFrac(med_fee, tx_vsize));
             +  -  +  - ]
     336                 :             :         // Expire the cache by advancing mock time past CACHE_LIFE so the next call recomputes.
     337         [ +  - ]:           1 :         SetMockTime(GetTime<std::chrono::seconds>() + CACHE_LIFE + std::chrono::seconds{1});
     338   [ +  -  +  -  :           4 :         BOOST_CHECK(mempool_estimator.EstimateFeeRate(/*conservative=*/false).value().feerate == FeeFrac(med_fee, tx_vsize));
          +  -  +  -  +  
                      - ]
     339   [ +  -  +  -  :           4 :         BOOST_CHECK(mempool_estimator.EstimateFeeRate(/*conservative=*/true).value().feerate == FeeFrac(high_fee, tx_vsize));
             +  -  +  - ]
     340                 :           1 :     }
     341                 :           1 : }
     342                 :             : 
     343                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1