text: // Copyright (c) 2011-2016 The Bitcoin Core developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "policy/policy.h"
  #include "policy/fees.h"
  #include "txmempool.h"
  #include "uint256.h"
  #include "util.h"
  
  #include "test/test_bitcoin.h"
  
  #include <boost/test/unit_test.hpp>
  
  BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, BasicTestingSetup)
  
  BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) {
      CTxMemPool mpool(CFeeRate(1000));
      TestMemPoolEntryHelper entry;
      CAmount basefee(2000);
      CAmount deltaFee(100);
      std::vector<CAmount> feeV;
  
      // Populate vectors of increasing fees
      for (int j = 0; j < 10; j++) {
          feeV.push_back(basefee * (j + 1));
      }
  
      // Store the hashes of transactions that have been added to the mempool by
      // their associate fee txHashes[j] is populated with transactions either of
      // fee = basefee * (j+1)
      std::vector<uint256> txHashes[10];
  
      // Create a transaction template
      CScript garbage;
      for (unsigned int i = 0; i < 128; i++)
          garbage.push_back('X');
      CMutableTransaction tx;
      tx.vin.resize(1);
      tx.vin[0].scriptSig = garbage;
      tx.vout.resize(1);
      tx.vout[0].nValue = 0LL;
      CFeeRate baseRate(basefee, GetTransactionSize(tx));
  
      // Create a fake block
      std::vector<CTransactionRef> block;
      int blocknum = 0;
  
      // Loop through 200 blocks
      // At a decay .998 and 4 fee transactions per block
      // This makes the tx count about 1.33 per bucket, above the 1 threshold
      while (blocknum < 200) {
          // For each fee
          for (int j = 0; j < 10; j++) {
              // add 4 fee txs
              for (int k = 0; k < 4; k++) {
                  // make transaction unique
                  tx.vin[0].prevout.n = 10000 * blocknum + 100 * j + k;
                  uint256 hash = tx.GetId();
                  mpool.addUnchecked(hash, entry.Fee(feeV[j])
                                               .Time(GetTime())
                                               .Priority(0)
                                               .Height(blocknum)
                                               .FromTx(tx, &mpool));
                  txHashes[j].push_back(hash);
              }
          }
          // Create blocks where higher fee txs are included more often
          for (int h = 0; h <= blocknum % 10; h++) {
              // 10/10 blocks add highest fee transactions
              // 9/10 blocks add 2nd highest and so on until ...
              // 1/10 blocks add lowest fee transactions
              while (txHashes[9 - h].size()) {
                  CTransactionRef ptx = mpool.get(txHashes[9 - h].back());
                  if (ptx) block.push_back(ptx);
                  txHashes[9 - h].pop_back();
              }
          }
          mpool.removeForBlock(block, ++blocknum);
          block.clear();
          if (blocknum == 30) {
              // At this point we should need to combine 5 buckets to get enough
              // data points. So estimateFee(1,2,3) should fail and estimateFee(4)
              // should return somewhere around 8*baserate.  estimateFee(4) %'s
              // are 100,100,100,100,90 = average 98%
              BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0));
              BOOST_CHECK(mpool.estimateFee(2) == CFeeRate(0));
              BOOST_CHECK(mpool.estimateFee(3) == CFeeRate(0));
              BOOST_CHECK(mpool.estimateFee(4).GetFeePerK() <
                          8 * baseRate.GetFeePerK() + deltaFee);
              BOOST_CHECK(mpool.estimateFee(4).GetFeePerK() >
                          8 * baseRate.GetFeePerK() - deltaFee);
              int answerFound;
              BOOST_CHECK(mpool.estimateSmartFee(1, &answerFound) ==
                              mpool.estimateFee(4) &&
                          answerFound == 4);
              BOOST_CHECK(mpool.estimateSmartFee(3, &answerFound) ==
                              mpool.estimateFee(4) &&
                          answerFound == 4);
              BOOST_CHECK(mpool.estimateSmartFee(4, &answerFound) ==
                              mpool.estimateFee(4) &&
                          answerFound == 4);
              BOOST_CHECK(mpool.estimateSmartFee(8, &answerFound) ==
                              mpool.estimateFee(8) &&
                          answerFound == 8);
          }
      }
  
      std::vector<Amount> origFeeEst;
      // Highest feerate is 10*baseRate and gets in all blocks, second highest
      // feerate is 9*baseRate and gets in 9/10 blocks = 90%, third highest
      // feerate is 8*base rate, and gets in 8/10 blocks = 80%, so estimateFee(1)
      // would return 10*baseRate but is hardcoded to return failure. Second
      // highest feerate has 100% chance of being included by 2 blocks, so
      // estimateFee(2) should return 9*baseRate etc...
      for (int i = 1; i < 10; i++) {
          origFeeEst.push_back(mpool.estimateFee(i).GetFeePerK());
          // Fee estimates should be monotonically decreasing
          if (i > 2) {
              BOOST_CHECK(origFeeEst[i - 1] <= origFeeEst[i - 2]);
          }
          int mult = 11 - i;
          if (i > 1) {
              BOOST_CHECK(origFeeEst[i - 1] <
                          mult * baseRate.GetFeePerK() + deltaFee);
              BOOST_CHECK(origFeeEst[i - 1] >
                          mult * baseRate.GetFeePerK() - deltaFee);
          } else {
              BOOST_CHECK(origFeeEst[i - 1] == CFeeRate(0).GetFeePerK());
          }
      }
  
      // Mine 50 more blocks with no transactions happening, estimates shouldn't
      // change. We haven't decayed the moving average enough so we still have
      // enough data points in every bucket
      while (blocknum < 250)
          mpool.removeForBlock(block, ++blocknum);
  
      BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0));
      for (int i = 2; i < 10; i++) {
          BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() <
                      origFeeEst[i - 1] + deltaFee);
          BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() >
                      origFeeEst[i - 1] - deltaFee);
      }
  
      // Mine 15 more blocks with lots of transactions happening and not getting
      // mined. Estimates should go up
      while (blocknum < 265) {
          // For each fee multiple
          for (int j = 0; j < 10; j++) {
              // add 4 fee txs
              for (int k = 0; k < 4; k++) {
                  tx.vin[0].prevout.n = 10000 * blocknum + 100 * j + k;
                  uint256 txid = tx.GetId();
                  mpool.addUnchecked(txid, entry.Fee(feeV[j])
                                               .Time(GetTime())
                                               .Priority(0)
                                               .Height(blocknum)
                                               .FromTx(tx, &mpool));
                  txHashes[j].push_back(txid);
              }
          }
          mpool.removeForBlock(block, ++blocknum);
      }
  
      int answerFound;
      for (int i = 1; i < 10; i++) {
          BOOST_CHECK(mpool.estimateFee(i) == CFeeRate(0) ||
                      mpool.estimateFee(i).GetFeePerK() >
                          origFeeEst[i - 1] - deltaFee);
          BOOST_CHECK(mpool.estimateSmartFee(i, &answerFound).GetFeePerK() >
                      origFeeEst[answerFound - 1] - deltaFee);
      }
  
      // Mine all those transactions
      // Estimates should still not be below original
      for (int j = 0; j < 10; j++) {
          while (txHashes[j].size()) {
              CTransactionRef ptx = mpool.get(txHashes[j].back());
              if (ptx) block.push_back(ptx);
              txHashes[j].pop_back();
          }
      }
      mpool.removeForBlock(block, 265);
      block.clear();
      BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0));
      for (int i = 2; i < 10; i++) {
          BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() >
                      origFeeEst[i - 1] - deltaFee);
      }
  
      // Mine 200 more blocks where everything is mined every block
      // Estimates should be below original estimates
      while (blocknum < 465) {
          // For each fee multiple
          for (int j = 0; j < 10; j++) {
              // add 4 fee txs
              for (int k = 0; k < 4; k++) {
                  tx.vin[0].prevout.n = 10000 * blocknum + 100 * j + k;
                  uint256 txid = tx.GetId();
                  mpool.addUnchecked(txid, entry.Fee(feeV[j])
                                               .Time(GetTime())
                                               .Priority(0)
                                               .Height(blocknum)
                                               .FromTx(tx, &mpool));
                  CTransactionRef ptx = mpool.get(txid);
                  if (ptx) block.push_back(ptx);
              }
          }
          mpool.removeForBlock(block, ++blocknum);
          block.clear();
      }
      BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0));
      for (int i = 2; i < 10; i++) {
          BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() <
                      origFeeEst[i - 1] - deltaFee);
      }
  
      // Test that if the mempool is limited, estimateSmartFee won't return a
      // value below the mempool min fee and that estimateSmartPriority returns
      // essentially an infinite value
      mpool.addUnchecked(
          tx.GetId(),
          entry.Fee(feeV[5]).Time(GetTime()).Priority(0).Height(blocknum).FromTx(
              tx, &mpool));
      // evict that transaction which should set a mempool min fee of
      // minRelayTxFee + feeV[5]
      mpool.TrimToSize(1);
      BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
      for (int i = 1; i < 10; i++) {
          BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >=
                      mpool.estimateFee(i).GetFeePerK());
          BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >=
                      mpool.GetMinFee(1).GetFeePerK());
          BOOST_CHECK(mpool.estimateSmartPriority(i) == INF_PRIORITY);
      }
  }
  
  BOOST_AUTO_TEST_SUITE_END()
  