/ src / test / policyestimator_tests.cpp
policyestimator_tests.cpp
  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.h>
  6  #include <policy/fees_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<uint256> 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                      AddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
 67                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
 68                      // not AddToMempool, 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                  uint256 hash = tx.GetHash();
 81                  txHashes[j].push_back(hash);
 82              }
 83          }
 84          //Create blocks where higher fee txs are included more often
 85          for (int h = 0; h <= blocknum%10; h++) {
 86              // 10/10 blocks add highest fee transactions
 87              // 9/10 blocks add 2nd highest and so on until ...
 88              // 1/10 blocks add lowest fee transactions
 89              while (txHashes[9-h].size()) {
 90                  CTransactionRef ptx = mpool.get(txHashes[9-h].back());
 91                  if (ptx)
 92                      block.push_back(ptx);
 93                  txHashes[9-h].pop_back();
 94              }
 95          }
 96  
 97          {
 98              LOCK(mpool.cs);
 99              mpool.removeForBlock(block, ++blocknum);
100          }
101  
102          block.clear();
103          // Check after just a few txs that combining buckets works as expected
104          if (blocknum == 3) {
105              // Wait for fee estimator to catch up
106              m_node.validation_signals->SyncWithValidationInterfaceQueue();
107              // At this point we should need to combine 3 buckets to get enough data points
108              // So estimateFee(1) should fail and estimateFee(2) should return somewhere around
109              // 9*baserate.  estimateFee(2) %'s are 100,100,90 = average 97%
110              BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
111              BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() < 9*baseRate.GetFeePerK() + deltaFee);
112              BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() > 9*baseRate.GetFeePerK() - deltaFee);
113          }
114      }
115  
116      // Wait for fee estimator to catch up
117      m_node.validation_signals->SyncWithValidationInterfaceQueue();
118  
119      std::vector<CAmount> origFeeEst;
120      // Highest feerate is 10*baseRate and gets in all blocks,
121      // second highest feerate is 9*baseRate and gets in 9/10 blocks = 90%,
122      // third highest feerate is 8*base rate, and gets in 8/10 blocks = 80%,
123      // so estimateFee(1) would return 10*baseRate but is hardcoded to return failure
124      // Second highest feerate has 100% chance of being included by 2 blocks,
125      // so estimateFee(2) should return 9*baseRate etc...
126      for (int i = 1; i < 10;i++) {
127          origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
128          if (i > 2) { // Fee estimates should be monotonically decreasing
129              BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]);
130          }
131          int mult = 11-i;
132          if (i % 2 == 0) { //At scale 2, test logic is only correct for even targets
133              BOOST_CHECK(origFeeEst[i-1] < mult*baseRate.GetFeePerK() + deltaFee);
134              BOOST_CHECK(origFeeEst[i-1] > mult*baseRate.GetFeePerK() - deltaFee);
135          }
136      }
137      // Fill out rest of the original estimates
138      for (int i = 10; i <= 48; i++) {
139          origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
140      }
141  
142      // Mine 50 more blocks with no transactions happening, estimates shouldn't change
143      // We haven't decayed the moving average enough so we still have enough data points in every bucket
144      while (blocknum < 250) {
145          LOCK(mpool.cs);
146          mpool.removeForBlock(block, ++blocknum);
147      }
148  
149      // Wait for fee estimator to catch up
150      m_node.validation_signals->SyncWithValidationInterfaceQueue();
151  
152      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
153      for (int i = 2; i < 10;i++) {
154          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee);
155          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
156      }
157  
158  
159      // Mine 15 more blocks with lots of transactions happening and not getting mined
160      // Estimates should go up
161      while (blocknum < 265) {
162          for (int j = 0; j < 10; j++) { // For each fee multiple
163              for (int k = 0; k < 4; k++) { // add 4 fee txs
164                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
165                  {
166                      LOCK2(cs_main, mpool.cs);
167                      AddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
168                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
169                      // not AddToMempool, we cheat and create one manually here
170                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
171                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
172                                                                                        feeV[j],
173                                                                                        virtual_size,
174                                                                                        entry.nHeight,
175                                                                                        /*mempool_limit_bypassed=*/false,
176                                                                                        /*submitted_in_package=*/false,
177                                                                                        /*chainstate_is_current=*/true,
178                                                                                        /*has_no_mempool_parents=*/true)};
179                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
180                  }
181                  uint256 hash = tx.GetHash();
182                  txHashes[j].push_back(hash);
183              }
184          }
185          {
186              LOCK(mpool.cs);
187              mpool.removeForBlock(block, ++blocknum);
188          }
189      }
190  
191      // Wait for fee estimator to catch up
192      m_node.validation_signals->SyncWithValidationInterfaceQueue();
193  
194      for (int i = 1; i < 10;i++) {
195          BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
196      }
197  
198      // Mine all those transactions
199      // Estimates should still not be below original
200      for (int j = 0; j < 10; j++) {
201          while(txHashes[j].size()) {
202              CTransactionRef ptx = mpool.get(txHashes[j].back());
203              if (ptx)
204                  block.push_back(ptx);
205              txHashes[j].pop_back();
206          }
207      }
208  
209      {
210          LOCK(mpool.cs);
211          mpool.removeForBlock(block, 266);
212      }
213      block.clear();
214  
215      // Wait for fee estimator to catch up
216      m_node.validation_signals->SyncWithValidationInterfaceQueue();
217  
218      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
219      for (int i = 2; i < 10;i++) {
220          BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
221      }
222  
223      // Mine 400 more blocks where everything is mined every block
224      // Estimates should be below original estimates
225      while (blocknum < 665) {
226          for (int j = 0; j < 10; j++) { // For each fee multiple
227              for (int k = 0; k < 4; k++) { // add 4 fee txs
228                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
229                  {
230                      LOCK2(cs_main, mpool.cs);
231                      AddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
232                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
233                      // not AddToMempool, we cheat and create one manually here
234                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
235                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
236                                                                                        feeV[j],
237                                                                                        virtual_size,
238                                                                                        entry.nHeight,
239                                                                                        /*mempool_limit_bypassed=*/false,
240                                                                                        /*submitted_in_package=*/false,
241                                                                                        /*chainstate_is_current=*/true,
242                                                                                        /*has_no_mempool_parents=*/true)};
243                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
244                  }
245                  uint256 hash = tx.GetHash();
246                  CTransactionRef ptx = mpool.get(hash);
247                  if (ptx)
248                      block.push_back(ptx);
249  
250              }
251          }
252  
253          {
254              LOCK(mpool.cs);
255              mpool.removeForBlock(block, ++blocknum);
256          }
257  
258          block.clear();
259      }
260      // Wait for fee estimator to catch up
261      m_node.validation_signals->SyncWithValidationInterfaceQueue();
262      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
263      for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
264          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
265      }
266  }
267  
268  BOOST_AUTO_TEST_SUITE_END()