policyestimator_tests.cpp raw
1 // Copyright (c) 2011-present The Limenka 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 empty_ignore_rejects,
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 empty_ignore_rejects,
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 empty_ignore_rejects,
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()
269