/ 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/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()