policyestimator_tests.cpp raw

   1  // Copyright (c) 2011-present 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 <policy/fees/block_policy_estimator.h>
   6  #include <policy/fees/block_policy_estimator_args.h>
   7  #include <policy/policy.h>
   8  #include <test/util/txmempool.h>
   9  #include <txmempool.h>
  10  #include <uint256.h>
  11  #include <util/time.h>
  12  #include <validationinterface.h>
  13  
  14  #include <test/util/setup_common.h>
  15  
  16  #include <boost/test/unit_test.hpp>
  17  
  18  BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, ChainTestingSetup)
  19  
  20  BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
  21  {
  22      CBlockPolicyEstimator feeEst{FeeestPath(*m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES};
  23      CTxMemPool& mpool = *Assert(m_node.mempool);
  24      m_node.validation_signals->RegisterValidationInterface(&feeEst);
  25      TestMemPoolEntryHelper entry;
  26      CAmount basefee(2000);
  27      CAmount deltaFee(100);
  28      std::vector<CAmount> feeV;
  29      feeV.reserve(10);
  30  
  31      // Populate vectors of increasing fees
  32      for (int j = 0; j < 10; j++) {
  33          feeV.push_back(basefee * (j+1));
  34      }
  35  
  36      // Store the hashes of transactions that have been
  37      // added to the mempool by their associate fee
  38      // txHashes[j] is populated with transactions either of
  39      // fee = basefee * (j+1)
  40      std::vector<Txid> txHashes[10];
  41  
  42      // Create a transaction template
  43      CScript garbage;
  44      for (unsigned int i = 0; i < 128; i++)
  45          garbage.push_back('X');
  46      CMutableTransaction tx;
  47      tx.vin.resize(1);
  48      tx.vin[0].scriptSig = garbage;
  49      tx.vout.resize(1);
  50      tx.vout[0].nValue=0LL;
  51      CFeeRate baseRate(basefee, GetVirtualTransactionSize(CTransaction(tx)));
  52  
  53      // Create a fake block
  54      std::vector<CTransactionRef> block;
  55      int blocknum = 0;
  56  
  57      // Loop through 200 blocks
  58      // At a decay .9952 and 4 fee transactions per block
  59      // This makes the tx count about 2.5 per bucket, well above the 0.1 threshold
  60      while (blocknum < 200) {
  61          for (int j = 0; j < 10; j++) { // For each fee
  62              for (int k = 0; k < 4; k++) { // add 4 fee txs
  63                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
  64                  {
  65                      LOCK2(cs_main, mpool.cs);
  66                      TryAddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
  67                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
  68                      // not TryAddToMempool, we cheat and create one manually here
  69                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
  70                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
  71                                                                                        feeV[j],
  72                                                                                        virtual_size,
  73                                                                                        entry.nHeight,
  74                                                                                        /*mempool_limit_bypassed=*/false,
  75                                                                                        /*submitted_in_package=*/false,
  76                                                                                        /*chainstate_is_current=*/true,
  77                                                                                        /*has_no_mempool_parents=*/true)};
  78                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
  79                  }
  80                  txHashes[j].push_back(tx.GetHash());
  81              }
  82          }
  83          //Create blocks where higher fee txs are included more often
  84          for (int h = 0; h <= blocknum%10; h++) {
  85              // 10/10 blocks add highest fee transactions
  86              // 9/10 blocks add 2nd highest and so on until ...
  87              // 1/10 blocks add lowest fee transactions
  88              while (txHashes[9-h].size()) {
  89                  CTransactionRef ptx = mpool.get(txHashes[9-h].back());
  90                  if (ptx)
  91                      block.push_back(ptx);
  92                  txHashes[9-h].pop_back();
  93              }
  94          }
  95  
  96          {
  97              LOCK(mpool.cs);
  98              mpool.removeForBlock(block, ++blocknum);
  99          }
 100  
 101          block.clear();
 102          // Check after just a few txs that combining buckets works as expected
 103          if (blocknum == 3) {
 104              // Wait for fee estimator to catch up
 105              m_node.validation_signals->SyncWithValidationInterfaceQueue();
 106              // At this point we should need to combine 3 buckets to get enough data points
 107              // So estimateFee(1) should fail and estimateFee(2) should return somewhere around
 108              // 9*baserate.  estimateFee(2) %'s are 100,100,90 = average 97%
 109              BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
 110              BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() < 9*baseRate.GetFeePerK() + deltaFee);
 111              BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() > 9*baseRate.GetFeePerK() - deltaFee);
 112          }
 113      }
 114  
 115      // Wait for fee estimator to catch up
 116      m_node.validation_signals->SyncWithValidationInterfaceQueue();
 117  
 118      std::vector<CAmount> origFeeEst;
 119      // Highest feerate is 10*baseRate and gets in all blocks,
 120      // second highest feerate is 9*baseRate and gets in 9/10 blocks = 90%,
 121      // third highest feerate is 8*base rate, and gets in 8/10 blocks = 80%,
 122      // so estimateFee(1) would return 10*baseRate but is hardcoded to return failure
 123      // Second highest feerate has 100% chance of being included by 2 blocks,
 124      // so estimateFee(2) should return 9*baseRate etc...
 125      for (int i = 1; i < 10;i++) {
 126          origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
 127          if (i > 2) { // Fee estimates should be monotonically decreasing
 128              BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]);
 129          }
 130          int mult = 11-i;
 131          if (i % 2 == 0) { //At scale 2, test logic is only correct for even targets
 132              BOOST_CHECK(origFeeEst[i-1] < mult*baseRate.GetFeePerK() + deltaFee);
 133              BOOST_CHECK(origFeeEst[i-1] > mult*baseRate.GetFeePerK() - deltaFee);
 134          }
 135      }
 136      // Fill out rest of the original estimates
 137      for (int i = 10; i <= 48; i++) {
 138          origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
 139      }
 140  
 141      // Mine 50 more blocks with no transactions happening, estimates shouldn't change
 142      // We haven't decayed the moving average enough so we still have enough data points in every bucket
 143      while (blocknum < 250) {
 144          LOCK(mpool.cs);
 145          mpool.removeForBlock(block, ++blocknum);
 146      }
 147  
 148      // Wait for fee estimator to catch up
 149      m_node.validation_signals->SyncWithValidationInterfaceQueue();
 150  
 151      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
 152      for (int i = 2; i < 10;i++) {
 153          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee);
 154          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
 155      }
 156  
 157  
 158      // Mine 15 more blocks with lots of transactions happening and not getting mined
 159      // Estimates should go up
 160      while (blocknum < 265) {
 161          for (int j = 0; j < 10; j++) { // For each fee multiple
 162              for (int k = 0; k < 4; k++) { // add 4 fee txs
 163                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
 164                  {
 165                      LOCK2(cs_main, mpool.cs);
 166                      TryAddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
 167                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
 168                      // not TryAddToMempool, we cheat and create one manually here
 169                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
 170                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
 171                                                                                        feeV[j],
 172                                                                                        virtual_size,
 173                                                                                        entry.nHeight,
 174                                                                                        /*mempool_limit_bypassed=*/false,
 175                                                                                        /*submitted_in_package=*/false,
 176                                                                                        /*chainstate_is_current=*/true,
 177                                                                                        /*has_no_mempool_parents=*/true)};
 178                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
 179                  }
 180                  txHashes[j].push_back(tx.GetHash());
 181              }
 182          }
 183          {
 184              LOCK(mpool.cs);
 185              mpool.removeForBlock(block, ++blocknum);
 186          }
 187      }
 188  
 189      // Wait for fee estimator to catch up
 190      m_node.validation_signals->SyncWithValidationInterfaceQueue();
 191  
 192      for (int i = 1; i < 10;i++) {
 193          BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
 194      }
 195  
 196      // Mine all those transactions
 197      // Estimates should still not be below original
 198      for (int j = 0; j < 10; j++) {
 199          while(txHashes[j].size()) {
 200              CTransactionRef ptx = mpool.get(txHashes[j].back());
 201              if (ptx)
 202                  block.push_back(ptx);
 203              txHashes[j].pop_back();
 204          }
 205      }
 206  
 207      {
 208          LOCK(mpool.cs);
 209          mpool.removeForBlock(block, 266);
 210      }
 211      block.clear();
 212  
 213      // Wait for fee estimator to catch up
 214      m_node.validation_signals->SyncWithValidationInterfaceQueue();
 215  
 216      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
 217      for (int i = 2; i < 10;i++) {
 218          BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
 219      }
 220  
 221      // Mine 400 more blocks where everything is mined every block
 222      // Estimates should be below original estimates
 223      while (blocknum < 665) {
 224          for (int j = 0; j < 10; j++) { // For each fee multiple
 225              for (int k = 0; k < 4; k++) { // add 4 fee txs
 226                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
 227                  {
 228                      LOCK2(cs_main, mpool.cs);
 229                      TryAddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
 230                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
 231                      // not TryAddToMempool, we cheat and create one manually here
 232                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
 233                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
 234                                                                                        feeV[j],
 235                                                                                        virtual_size,
 236                                                                                        entry.nHeight,
 237                                                                                        /*mempool_limit_bypassed=*/false,
 238                                                                                        /*submitted_in_package=*/false,
 239                                                                                        /*chainstate_is_current=*/true,
 240                                                                                        /*has_no_mempool_parents=*/true)};
 241                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
 242                  }
 243                  CTransactionRef ptx = mpool.get(tx.GetHash());
 244                  if (ptx)
 245                      block.push_back(ptx);
 246  
 247              }
 248          }
 249  
 250          {
 251              LOCK(mpool.cs);
 252              mpool.removeForBlock(block, ++blocknum);
 253          }
 254  
 255          block.clear();
 256      }
 257      // Wait for fee estimator to catch up
 258      m_node.validation_signals->SyncWithValidationInterfaceQueue();
 259      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
 260      for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
 261          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
 262      }
 263  }
 264  
 265  BOOST_AUTO_TEST_SUITE_END()
 266