/ src / test / policyestimator_tests.cpp
policyestimator_tests.cpp
  1  // Copyright (c) 2011-2022 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/policy.h>
  7  #include <test/util/txmempool.h>
  8  #include <txmempool.h>
  9  #include <uint256.h>
 10  #include <util/time.h>
 11  #include <validationinterface.h>
 12  
 13  #include <test/util/setup_common.h>
 14  
 15  #include <boost/test/unit_test.hpp>
 16  
 17  BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, ChainTestingSetup)
 18  
 19  BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
 20  {
 21      CBlockPolicyEstimator& feeEst = *Assert(m_node.fee_estimator);
 22      CTxMemPool& mpool = *Assert(m_node.mempool);
 23      m_node.validation_signals->RegisterValidationInterface(&feeEst);
 24      TestMemPoolEntryHelper entry;
 25      CAmount basefee(2000);
 26      CAmount deltaFee(100);
 27      std::vector<CAmount> feeV;
 28      feeV.reserve(10);
 29  
 30      // Populate vectors of increasing fees
 31      for (int j = 0; j < 10; j++) {
 32          feeV.push_back(basefee * (j+1));
 33      }
 34  
 35      // Store the hashes of transactions that have been
 36      // added to the mempool by their associate fee
 37      // txHashes[j] is populated with transactions either of
 38      // fee = basefee * (j+1)
 39      std::vector<uint256> txHashes[10];
 40  
 41      // Create a transaction template
 42      CScript garbage;
 43      for (unsigned int i = 0; i < 128; i++)
 44          garbage.push_back('X');
 45      CMutableTransaction tx;
 46      tx.vin.resize(1);
 47      tx.vin[0].scriptSig = garbage;
 48      tx.vout.resize(1);
 49      tx.vout[0].nValue=0LL;
 50      CFeeRate baseRate(basefee, GetVirtualTransactionSize(CTransaction(tx)));
 51  
 52      // Create a fake block
 53      std::vector<CTransactionRef> block;
 54      int blocknum = 0;
 55  
 56      // Loop through 200 blocks
 57      // At a decay .9952 and 4 fee transactions per block
 58      // This makes the tx count about 2.5 per bucket, well above the 0.1 threshold
 59      while (blocknum < 200) {
 60          for (int j = 0; j < 10; j++) { // For each fee
 61              for (int k = 0; k < 4; k++) { // add 4 fee txs
 62                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
 63                  {
 64                      LOCK2(cs_main, mpool.cs);
 65                      mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
 66                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
 67                      // not addUnchecked, we cheat and create one manually here
 68                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
 69                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
 70                                                                                        feeV[j],
 71                                                                                        virtual_size,
 72                                                                                        entry.nHeight,
 73                                                                                        /*mempool_limit_bypassed=*/false,
 74                                                                                        /*submitted_in_package=*/false,
 75                                                                                        /*chainstate_is_current=*/true,
 76                                                                                        /*has_no_mempool_parents=*/true)};
 77                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
 78                  }
 79                  uint256 hash = tx.GetHash();
 80                  txHashes[j].push_back(hash);
 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                      mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
167                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
168                      // not addUnchecked, 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                  uint256 hash = tx.GetHash();
181                  txHashes[j].push_back(hash);
182              }
183          }
184          {
185              LOCK(mpool.cs);
186              mpool.removeForBlock(block, ++blocknum);
187          }
188      }
189  
190      // Wait for fee estimator to catch up
191      m_node.validation_signals->SyncWithValidationInterfaceQueue();
192  
193      for (int i = 1; i < 10;i++) {
194          BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
195      }
196  
197      // Mine all those transactions
198      // Estimates should still not be below original
199      for (int j = 0; j < 10; j++) {
200          while(txHashes[j].size()) {
201              CTransactionRef ptx = mpool.get(txHashes[j].back());
202              if (ptx)
203                  block.push_back(ptx);
204              txHashes[j].pop_back();
205          }
206      }
207  
208      {
209          LOCK(mpool.cs);
210          mpool.removeForBlock(block, 266);
211      }
212      block.clear();
213  
214      // Wait for fee estimator to catch up
215      m_node.validation_signals->SyncWithValidationInterfaceQueue();
216  
217      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
218      for (int i = 2; i < 10;i++) {
219          BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
220      }
221  
222      // Mine 400 more blocks where everything is mined every block
223      // Estimates should be below original estimates
224      while (blocknum < 665) {
225          for (int j = 0; j < 10; j++) { // For each fee multiple
226              for (int k = 0; k < 4; k++) { // add 4 fee txs
227                  tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
228                  {
229                      LOCK2(cs_main, mpool.cs);
230                      mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
231                      // Since TransactionAddedToMempool callbacks are generated in ATMP,
232                      // not addUnchecked, we cheat and create one manually here
233                      const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
234                      const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
235                                                                                        feeV[j],
236                                                                                        virtual_size,
237                                                                                        entry.nHeight,
238                                                                                        /*mempool_limit_bypassed=*/false,
239                                                                                        /*submitted_in_package=*/false,
240                                                                                        /*chainstate_is_current=*/true,
241                                                                                        /*has_no_mempool_parents=*/true)};
242                      m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
243                  }
244                  uint256 hash = tx.GetHash();
245                  CTransactionRef ptx = mpool.get(hash);
246                  if (ptx)
247                      block.push_back(ptx);
248  
249              }
250          }
251  
252          {
253              LOCK(mpool.cs);
254              mpool.removeForBlock(block, ++blocknum);
255          }
256  
257          block.clear();
258      }
259      // Wait for fee estimator to catch up
260      m_node.validation_signals->SyncWithValidationInterfaceQueue();
261      BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
262      for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
263          BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
264      }
265  }
266  
267  BOOST_AUTO_TEST_SUITE_END()