Branch data Line data Source code
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 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
21 : : {
22 [ + - ]: 1 : CBlockPolicyEstimator feeEst{FeeestPath(*m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES};
23 [ + - + - ]: 1 : CTxMemPool& mpool = *Assert(m_node.mempool);
24 [ + - ]: 1 : m_node.validation_signals->RegisterValidationInterface(&feeEst);
25 : 1 : TestMemPoolEntryHelper entry;
26 : 1 : CAmount basefee(2000);
27 : 1 : CAmount deltaFee(100);
28 : 1 : std::vector<CAmount> feeV;
29 [ + - ]: 1 : feeV.reserve(10);
30 : :
31 : : // Populate vectors of increasing fees
32 : 11 : for (int j = 0; j < 10; j++) {
33 [ + - + + ]: 11 : 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 : 10 : std::vector<uint256> txHashes[10];
41 : :
42 : : // Create a transaction template
43 : 1 : CScript garbage;
44 [ + + ]: 129 : for (unsigned int i = 0; i < 128; i++)
45 : 128 : garbage.push_back('X');
46 [ + - ]: 1 : CMutableTransaction tx;
47 [ + - ]: 1 : tx.vin.resize(1);
48 : 1 : tx.vin[0].scriptSig = garbage;
49 [ + - ]: 1 : tx.vout.resize(1);
50 [ + - ]: 1 : tx.vout[0].nValue=0LL;
51 [ + - + - ]: 2 : CFeeRate baseRate(basefee, GetVirtualTransactionSize(CTransaction(tx)));
52 : :
53 : : // Create a fake block
54 : 1 : std::vector<CTransactionRef> block;
55 : 1 : 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 [ + + ]: 201 : while (blocknum < 200) {
61 [ + + ]: 2200 : for (int j = 0; j < 10; j++) { // For each fee
62 [ + + ]: 10000 : for (int k = 0; k < 4; k++) { // add 4 fee txs
63 [ + - ]: 8000 : tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
64 : 8000 : {
65 [ + - + - ]: 8000 : LOCK2(cs_main, mpool.cs);
66 [ + - + - ]: 8000 : mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
67 : : // Since TransactionAddedToMempool callbacks are generated in ATMP,
68 : : // not addUnchecked, we cheat and create one manually here
69 [ + - + - : 16000 : const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
+ - ]
70 [ + - ]: 8000 : const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
71 : 8000 : feeV[j],
72 : : virtual_size,
73 : : entry.nHeight,
74 : : /*mempool_limit_bypassed=*/false,
75 : : /*submitted_in_package=*/false,
76 : : /*chainstate_is_current=*/true,
77 [ + - + - ]: 16000 : /*has_no_mempool_parents=*/true)};
78 [ + - ]: 8000 : m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
79 [ + - + - ]: 16000 : }
80 [ + - ]: 8000 : uint256 hash = tx.GetHash();
81 [ + - ]: 8000 : txHashes[j].push_back(hash);
82 : : }
83 : : }
84 : : //Create blocks where higher fee txs are included more often
85 [ + + ]: 1300 : 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 [ + + ]: 9100 : while (txHashes[9-h].size()) {
90 [ + - ]: 8000 : CTransactionRef ptx = mpool.get(txHashes[9-h].back());
91 [ + - ]: 8000 : if (ptx)
92 [ + - ]: 8000 : block.push_back(ptx);
93 [ + - ]: 8000 : txHashes[9-h].pop_back();
94 : 8000 : }
95 : : }
96 : :
97 : 200 : {
98 [ + - ]: 200 : LOCK(mpool.cs);
99 [ + - ]: 200 : mpool.removeForBlock(block, ++blocknum);
100 : 0 : }
101 : :
102 : 200 : block.clear();
103 : : // Check after just a few txs that combining buckets works as expected
104 [ + + ]: 200 : if (blocknum == 3) {
105 : : // Wait for fee estimator to catch up
106 [ + - ]: 1 : 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 [ + - + - : 2 : BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
+ - + - ]
111 [ + - + - : 2 : BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() < 9*baseRate.GetFeePerK() + deltaFee);
+ - + - ]
112 [ + - + - : 2 : BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() > 9*baseRate.GetFeePerK() - deltaFee);
+ - ]
113 : : }
114 : : }
115 : :
116 : : // Wait for fee estimator to catch up
117 [ + - ]: 1 : m_node.validation_signals->SyncWithValidationInterfaceQueue();
118 : :
119 : 1 : 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 [ + + ]: 10 : for (int i = 1; i < 10;i++) {
127 [ + - + - ]: 9 : origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
128 [ + + ]: 9 : if (i > 2) { // Fee estimates should be monotonically decreasing
129 [ + - + - ]: 14 : BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]);
130 : : }
131 : 9 : int mult = 11-i;
132 [ + + ]: 9 : if (i % 2 == 0) { //At scale 2, test logic is only correct for even targets
133 [ + - + - : 8 : BOOST_CHECK(origFeeEst[i-1] < mult*baseRate.GetFeePerK() + deltaFee);
+ - ]
134 [ + - + - ]: 8 : BOOST_CHECK(origFeeEst[i-1] > mult*baseRate.GetFeePerK() - deltaFee);
135 : : }
136 : : }
137 : : // Fill out rest of the original estimates
138 [ + + ]: 40 : for (int i = 10; i <= 48; i++) {
139 [ + - + - ]: 39 : 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 [ + + ]: 51 : while (blocknum < 250) {
145 [ + - ]: 50 : LOCK(mpool.cs);
146 [ + - ]: 50 : mpool.removeForBlock(block, ++blocknum);
147 : 50 : }
148 : :
149 : : // Wait for fee estimator to catch up
150 [ + - ]: 1 : m_node.validation_signals->SyncWithValidationInterfaceQueue();
151 : :
152 [ + - + - : 2 : BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
+ - ]
153 [ + + ]: 9 : for (int i = 2; i < 10;i++) {
154 [ + - + - : 16 : BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee);
+ - + - ]
155 [ + - + - : 16 : 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 [ + + ]: 16 : while (blocknum < 265) {
162 [ + + ]: 165 : for (int j = 0; j < 10; j++) { // For each fee multiple
163 [ + + ]: 750 : for (int k = 0; k < 4; k++) { // add 4 fee txs
164 [ + - ]: 600 : tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
165 : 600 : {
166 [ + - + - ]: 600 : LOCK2(cs_main, mpool.cs);
167 [ + - + - ]: 600 : mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
168 : : // Since TransactionAddedToMempool callbacks are generated in ATMP,
169 : : // not addUnchecked, we cheat and create one manually here
170 [ + - + - : 1200 : const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
+ - ]
171 [ + - ]: 600 : const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
172 : 600 : feeV[j],
173 : : virtual_size,
174 : : entry.nHeight,
175 : : /*mempool_limit_bypassed=*/false,
176 : : /*submitted_in_package=*/false,
177 : : /*chainstate_is_current=*/true,
178 [ + - + - ]: 1200 : /*has_no_mempool_parents=*/true)};
179 [ + - ]: 600 : m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
180 [ + - + - ]: 1200 : }
181 [ + - ]: 600 : uint256 hash = tx.GetHash();
182 [ + - ]: 600 : txHashes[j].push_back(hash);
183 : : }
184 : : }
185 : 15 : {
186 [ + - ]: 15 : LOCK(mpool.cs);
187 [ + - ]: 15 : mpool.removeForBlock(block, ++blocknum);
188 : 15 : }
189 : : }
190 : :
191 : : // Wait for fee estimator to catch up
192 [ + - ]: 1 : m_node.validation_signals->SyncWithValidationInterfaceQueue();
193 : :
194 [ + + ]: 10 : for (int i = 1; i < 10;i++) {
195 [ + - + - : 18 : 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 [ + + ]: 11 : for (int j = 0; j < 10; j++) {
201 [ + + ]: 610 : while(txHashes[j].size()) {
202 [ + - ]: 600 : CTransactionRef ptx = mpool.get(txHashes[j].back());
203 [ + - ]: 600 : if (ptx)
204 [ + - ]: 600 : block.push_back(ptx);
205 [ + - ]: 600 : txHashes[j].pop_back();
206 : 600 : }
207 : : }
208 : :
209 : 1 : {
210 [ + - ]: 1 : LOCK(mpool.cs);
211 [ + - ]: 1 : mpool.removeForBlock(block, 266);
212 : 0 : }
213 : 1 : block.clear();
214 : :
215 : : // Wait for fee estimator to catch up
216 [ + - ]: 1 : m_node.validation_signals->SyncWithValidationInterfaceQueue();
217 : :
218 [ + - + - : 2 : BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
+ - ]
219 [ + + ]: 9 : for (int i = 2; i < 10;i++) {
220 [ + - + - : 16 : 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 [ + + ]: 401 : while (blocknum < 665) {
226 [ + + ]: 4400 : for (int j = 0; j < 10; j++) { // For each fee multiple
227 [ + + ]: 20000 : for (int k = 0; k < 4; k++) { // add 4 fee txs
228 [ + - ]: 16000 : tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
229 : 16000 : {
230 [ + - + - ]: 16000 : LOCK2(cs_main, mpool.cs);
231 [ + - + - ]: 16000 : mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
232 : : // Since TransactionAddedToMempool callbacks are generated in ATMP,
233 : : // not addUnchecked, we cheat and create one manually here
234 [ + - + - : 32000 : const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
+ - ]
235 [ + - ]: 16000 : const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
236 : 16000 : feeV[j],
237 : : virtual_size,
238 : : entry.nHeight,
239 : : /*mempool_limit_bypassed=*/false,
240 : : /*submitted_in_package=*/false,
241 : : /*chainstate_is_current=*/true,
242 [ + - + - ]: 32000 : /*has_no_mempool_parents=*/true)};
243 [ + - ]: 16000 : m_node.validation_signals->TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
244 [ + - + - ]: 32000 : }
245 [ + - ]: 16000 : uint256 hash = tx.GetHash();
246 [ + - ]: 16000 : CTransactionRef ptx = mpool.get(hash);
247 [ + - ]: 16000 : if (ptx)
248 [ + - ]: 16000 : block.push_back(ptx);
249 : :
250 : 16000 : }
251 : : }
252 : :
253 : 400 : {
254 [ + - ]: 400 : LOCK(mpool.cs);
255 [ + - ]: 400 : mpool.removeForBlock(block, ++blocknum);
256 : 0 : }
257 : :
258 : 400 : block.clear();
259 : : }
260 : : // Wait for fee estimator to catch up
261 [ + - ]: 1 : m_node.validation_signals->SyncWithValidationInterfaceQueue();
262 [ + - + - : 2 : BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
+ - ]
263 [ + + ]: 8 : for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
264 [ + - + - : 14 : BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
+ - ]
265 : : }
266 [ + + - - ]: 13 : }
267 : :
268 : : BOOST_AUTO_TEST_SUITE_END()
|