Bitcoin Core 22.99.0
P2P Digital Currency
mempool_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2019 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/policy.h>
6#include <txmempool.h>
7#include <util/system.h>
8#include <util/time.h>
9
11
12#include <boost/test/unit_test.hpp>
13#include <vector>
14
16
17static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED;
18
19BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
20{
21 // Test CTxMemPool::remove functionality
22
24 // Parent transaction with three children,
25 // and three grand-children:
26 CMutableTransaction txParent;
27 txParent.vin.resize(1);
28 txParent.vin[0].scriptSig = CScript() << OP_11;
29 txParent.vout.resize(3);
30 for (int i = 0; i < 3; i++)
31 {
32 txParent.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
33 txParent.vout[i].nValue = 33000LL;
34 }
35 CMutableTransaction txChild[3];
36 for (int i = 0; i < 3; i++)
37 {
38 txChild[i].vin.resize(1);
39 txChild[i].vin[0].scriptSig = CScript() << OP_11;
40 txChild[i].vin[0].prevout.hash = txParent.GetHash();
41 txChild[i].vin[0].prevout.n = i;
42 txChild[i].vout.resize(1);
43 txChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
44 txChild[i].vout[0].nValue = 11000LL;
45 }
46 CMutableTransaction txGrandChild[3];
47 for (int i = 0; i < 3; i++)
48 {
49 txGrandChild[i].vin.resize(1);
50 txGrandChild[i].vin[0].scriptSig = CScript() << OP_11;
51 txGrandChild[i].vin[0].prevout.hash = txChild[i].GetHash();
52 txGrandChild[i].vin[0].prevout.n = 0;
53 txGrandChild[i].vout.resize(1);
54 txGrandChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
55 txGrandChild[i].vout[0].nValue = 11000LL;
56 }
57
58
59 CTxMemPool testPool;
60 LOCK2(cs_main, testPool.cs);
61
62 // Nothing in pool, remove should do nothing:
63 unsigned int poolSize = testPool.size();
65 BOOST_CHECK_EQUAL(testPool.size(), poolSize);
66
67 // Just the parent:
68 testPool.addUnchecked(entry.FromTx(txParent));
69 poolSize = testPool.size();
71 BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1);
72
73 // Parent, children, grandchildren:
74 testPool.addUnchecked(entry.FromTx(txParent));
75 for (int i = 0; i < 3; i++)
76 {
77 testPool.addUnchecked(entry.FromTx(txChild[i]));
78 testPool.addUnchecked(entry.FromTx(txGrandChild[i]));
79 }
80 // Remove Child[0], GrandChild[0] should be removed:
81 poolSize = testPool.size();
83 BOOST_CHECK_EQUAL(testPool.size(), poolSize - 2);
84 // ... make sure grandchild and child are gone:
85 poolSize = testPool.size();
86 testPool.removeRecursive(CTransaction(txGrandChild[0]), REMOVAL_REASON_DUMMY);
87 BOOST_CHECK_EQUAL(testPool.size(), poolSize);
88 poolSize = testPool.size();
90 BOOST_CHECK_EQUAL(testPool.size(), poolSize);
91 // Remove parent, all children/grandchildren should go:
92 poolSize = testPool.size();
94 BOOST_CHECK_EQUAL(testPool.size(), poolSize - 5);
95 BOOST_CHECK_EQUAL(testPool.size(), 0U);
96
97 // Add children and grandchildren, but NOT the parent (simulate the parent being in a block)
98 for (int i = 0; i < 3; i++)
99 {
100 testPool.addUnchecked(entry.FromTx(txChild[i]));
101 testPool.addUnchecked(entry.FromTx(txGrandChild[i]));
102 }
103 // Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be
104 // put into the mempool (maybe because it is non-standard):
105 poolSize = testPool.size();
107 BOOST_CHECK_EQUAL(testPool.size(), poolSize - 6);
108 BOOST_CHECK_EQUAL(testPool.size(), 0U);
109}
110
111template<typename name>
112static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
113{
114 BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size());
115 typename CTxMemPool::indexed_transaction_set::index<name>::type::iterator it = pool.mapTx.get<name>().begin();
116 int count=0;
117 for (; it != pool.mapTx.get<name>().end(); ++it, ++count) {
118 BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]);
119 }
120}
121
122BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
123{
124 CTxMemPool pool;
125 LOCK2(cs_main, pool.cs);
127
128 /* 3rd highest fee */
130 tx1.vout.resize(1);
131 tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
132 tx1.vout[0].nValue = 10 * COIN;
133 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
134
135 /* highest fee */
137 tx2.vout.resize(1);
138 tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
139 tx2.vout[0].nValue = 2 * COIN;
140 pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2));
141
142 /* lowest fee */
144 tx3.vout.resize(1);
145 tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
146 tx3.vout[0].nValue = 5 * COIN;
147 pool.addUnchecked(entry.Fee(0LL).FromTx(tx3));
148
149 /* 2nd highest fee */
151 tx4.vout.resize(1);
152 tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
153 tx4.vout[0].nValue = 6 * COIN;
154 pool.addUnchecked(entry.Fee(15000LL).FromTx(tx4));
155
156 /* equal fee rate to tx1, but newer */
158 tx5.vout.resize(1);
159 tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
160 tx5.vout[0].nValue = 11 * COIN;
161 entry.nTime = 1;
162 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5));
163 BOOST_CHECK_EQUAL(pool.size(), 5U);
164
165 std::vector<std::string> sortedOrder;
166 sortedOrder.resize(5);
167 sortedOrder[0] = tx3.GetHash().ToString(); // 0
168 sortedOrder[1] = tx5.GetHash().ToString(); // 10000
169 sortedOrder[2] = tx1.GetHash().ToString(); // 10000
170 sortedOrder[3] = tx4.GetHash().ToString(); // 15000
171 sortedOrder[4] = tx2.GetHash().ToString(); // 20000
172 CheckSort<descendant_score>(pool, sortedOrder);
173
174 /* low fee but with high fee child */
175 /* tx6 -> tx7 -> tx8, tx9 -> tx10 */
177 tx6.vout.resize(1);
178 tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
179 tx6.vout[0].nValue = 20 * COIN;
180 pool.addUnchecked(entry.Fee(0LL).FromTx(tx6));
181 BOOST_CHECK_EQUAL(pool.size(), 6U);
182 // Check that at this point, tx6 is sorted low
183 sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
184 CheckSort<descendant_score>(pool, sortedOrder);
185
186 CTxMemPool::setEntries setAncestors;
187 setAncestors.insert(pool.mapTx.find(tx6.GetHash()));
189 tx7.vin.resize(1);
190 tx7.vin[0].prevout = COutPoint(tx6.GetHash(), 0);
191 tx7.vin[0].scriptSig = CScript() << OP_11;
192 tx7.vout.resize(2);
193 tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
194 tx7.vout[0].nValue = 10 * COIN;
195 tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
196 tx7.vout[1].nValue = 1 * COIN;
197
198 CTxMemPool::setEntries setAncestorsCalculated;
199 std::string dummy;
200 BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true);
201 BOOST_CHECK(setAncestorsCalculated == setAncestors);
202
203 pool.addUnchecked(entry.FromTx(tx7), setAncestors);
204 BOOST_CHECK_EQUAL(pool.size(), 7U);
205
206 // Now tx6 should be sorted higher (high fee child): tx7, tx6, tx2, ...
207 sortedOrder.erase(sortedOrder.begin());
208 sortedOrder.push_back(tx6.GetHash().ToString());
209 sortedOrder.push_back(tx7.GetHash().ToString());
210 CheckSort<descendant_score>(pool, sortedOrder);
211
212 /* low fee child of tx7 */
214 tx8.vin.resize(1);
215 tx8.vin[0].prevout = COutPoint(tx7.GetHash(), 0);
216 tx8.vin[0].scriptSig = CScript() << OP_11;
217 tx8.vout.resize(1);
218 tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
219 tx8.vout[0].nValue = 10 * COIN;
220 setAncestors.insert(pool.mapTx.find(tx7.GetHash()));
221 pool.addUnchecked(entry.Fee(0LL).Time(2).FromTx(tx8), setAncestors);
222
223 // Now tx8 should be sorted low, but tx6/tx both high
224 sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
225 CheckSort<descendant_score>(pool, sortedOrder);
226
227 /* low fee child of tx7 */
229 tx9.vin.resize(1);
230 tx9.vin[0].prevout = COutPoint(tx7.GetHash(), 1);
231 tx9.vin[0].scriptSig = CScript() << OP_11;
232 tx9.vout.resize(1);
233 tx9.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
234 tx9.vout[0].nValue = 1 * COIN;
235 pool.addUnchecked(entry.Fee(0LL).Time(3).FromTx(tx9), setAncestors);
236
237 // tx9 should be sorted low
238 BOOST_CHECK_EQUAL(pool.size(), 9U);
239 sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString());
240 CheckSort<descendant_score>(pool, sortedOrder);
241
242 std::vector<std::string> snapshotOrder = sortedOrder;
243
244 setAncestors.insert(pool.mapTx.find(tx8.GetHash()));
245 setAncestors.insert(pool.mapTx.find(tx9.GetHash()));
246 /* tx10 depends on tx8 and tx9 and has a high fee*/
248 tx10.vin.resize(2);
249 tx10.vin[0].prevout = COutPoint(tx8.GetHash(), 0);
250 tx10.vin[0].scriptSig = CScript() << OP_11;
251 tx10.vin[1].prevout = COutPoint(tx9.GetHash(), 0);
252 tx10.vin[1].scriptSig = CScript() << OP_11;
253 tx10.vout.resize(1);
254 tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
255 tx10.vout[0].nValue = 10 * COIN;
256
257 setAncestorsCalculated.clear();
258 BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true);
259 BOOST_CHECK(setAncestorsCalculated == setAncestors);
260
261 pool.addUnchecked(entry.FromTx(tx10), setAncestors);
262
278 sortedOrder.erase(sortedOrder.begin(), sortedOrder.begin()+2); // take out tx9, tx8 from the beginning
279 sortedOrder.insert(sortedOrder.begin()+5, tx9.GetHash().ToString());
280 sortedOrder.insert(sortedOrder.begin()+6, tx8.GetHash().ToString());
281 sortedOrder.insert(sortedOrder.begin()+7, tx10.GetHash().ToString()); // tx10 is just before tx6
282 CheckSort<descendant_score>(pool, sortedOrder);
283
284 // there should be 10 transactions in the mempool
285 BOOST_CHECK_EQUAL(pool.size(), 10U);
286
287 // Now try removing tx10 and verify the sort order returns to normal
288 pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx(), REMOVAL_REASON_DUMMY);
289 CheckSort<descendant_score>(pool, snapshotOrder);
290
291 pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx(), REMOVAL_REASON_DUMMY);
292 pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx(), REMOVAL_REASON_DUMMY);
293}
294
295BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
296{
297 CTxMemPool pool;
298 LOCK2(cs_main, pool.cs);
300
301 /* 3rd highest fee */
303 tx1.vout.resize(1);
304 tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
305 tx1.vout[0].nValue = 10 * COIN;
306 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
307
308 /* highest fee */
310 tx2.vout.resize(1);
311 tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
312 tx2.vout[0].nValue = 2 * COIN;
313 pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2));
314 uint64_t tx2Size = GetVirtualTransactionSize(CTransaction(tx2));
315
316 /* lowest fee */
318 tx3.vout.resize(1);
319 tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
320 tx3.vout[0].nValue = 5 * COIN;
321 pool.addUnchecked(entry.Fee(0LL).FromTx(tx3));
322
323 /* 2nd highest fee */
325 tx4.vout.resize(1);
326 tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
327 tx4.vout[0].nValue = 6 * COIN;
328 pool.addUnchecked(entry.Fee(15000LL).FromTx(tx4));
329
330 /* equal fee rate to tx1, but newer */
332 tx5.vout.resize(1);
333 tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
334 tx5.vout[0].nValue = 11 * COIN;
335 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5));
336 BOOST_CHECK_EQUAL(pool.size(), 5U);
337
338 std::vector<std::string> sortedOrder;
339 sortedOrder.resize(5);
340 sortedOrder[0] = tx2.GetHash().ToString(); // 20000
341 sortedOrder[1] = tx4.GetHash().ToString(); // 15000
342 // tx1 and tx5 are both 10000
343 // Ties are broken by hash, not timestamp, so determine which
344 // hash comes first.
345 if (tx1.GetHash() < tx5.GetHash()) {
346 sortedOrder[2] = tx1.GetHash().ToString();
347 sortedOrder[3] = tx5.GetHash().ToString();
348 } else {
349 sortedOrder[2] = tx5.GetHash().ToString();
350 sortedOrder[3] = tx1.GetHash().ToString();
351 }
352 sortedOrder[4] = tx3.GetHash().ToString(); // 0
353
354 CheckSort<ancestor_score>(pool, sortedOrder);
355
356 /* low fee parent with high fee child */
357 /* tx6 (0) -> tx7 (high) */
359 tx6.vout.resize(1);
360 tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
361 tx6.vout[0].nValue = 20 * COIN;
362 uint64_t tx6Size = GetVirtualTransactionSize(CTransaction(tx6));
363
364 pool.addUnchecked(entry.Fee(0LL).FromTx(tx6));
365 BOOST_CHECK_EQUAL(pool.size(), 6U);
366 // Ties are broken by hash
367 if (tx3.GetHash() < tx6.GetHash())
368 sortedOrder.push_back(tx6.GetHash().ToString());
369 else
370 sortedOrder.insert(sortedOrder.end()-1,tx6.GetHash().ToString());
371
372 CheckSort<ancestor_score>(pool, sortedOrder);
373
375 tx7.vin.resize(1);
376 tx7.vin[0].prevout = COutPoint(tx6.GetHash(), 0);
377 tx7.vin[0].scriptSig = CScript() << OP_11;
378 tx7.vout.resize(1);
379 tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
380 tx7.vout[0].nValue = 10 * COIN;
381 uint64_t tx7Size = GetVirtualTransactionSize(CTransaction(tx7));
382
383 /* set the fee to just below tx2's feerate when including ancestor */
384 CAmount fee = (20000/tx2Size)*(tx7Size + tx6Size) - 1;
385
386 pool.addUnchecked(entry.Fee(fee).FromTx(tx7));
387 BOOST_CHECK_EQUAL(pool.size(), 7U);
388 sortedOrder.insert(sortedOrder.begin()+1, tx7.GetHash().ToString());
389 CheckSort<ancestor_score>(pool, sortedOrder);
390
391 /* after tx6 is mined, tx7 should move up in the sort */
392 std::vector<CTransactionRef> vtx;
393 vtx.push_back(MakeTransactionRef(tx6));
394 pool.removeForBlock(vtx, 1);
395
396 sortedOrder.erase(sortedOrder.begin()+1);
397 // Ties are broken by hash
398 if (tx3.GetHash() < tx6.GetHash())
399 sortedOrder.pop_back();
400 else
401 sortedOrder.erase(sortedOrder.end()-2);
402 sortedOrder.insert(sortedOrder.begin(), tx7.GetHash().ToString());
403 CheckSort<ancestor_score>(pool, sortedOrder);
404
405 // High-fee parent, low-fee child
406 // tx7 -> tx8
408 tx8.vin.resize(1);
409 tx8.vin[0].prevout = COutPoint(tx7.GetHash(), 0);
410 tx8.vin[0].scriptSig = CScript() << OP_11;
411 tx8.vout.resize(1);
412 tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
413 tx8.vout[0].nValue = 10*COIN;
414
415 // Check that we sort by min(feerate, ancestor_feerate):
416 // set the fee so that the ancestor feerate is above tx1/5,
417 // but the transaction's own feerate is lower
418 pool.addUnchecked(entry.Fee(5000LL).FromTx(tx8));
419 sortedOrder.insert(sortedOrder.end()-1, tx8.GetHash().ToString());
420 CheckSort<ancestor_score>(pool, sortedOrder);
421}
422
423
424BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
425{
426 CTxMemPool pool;
427 LOCK2(cs_main, pool.cs);
429
431 tx1.vin.resize(1);
432 tx1.vin[0].scriptSig = CScript() << OP_1;
433 tx1.vout.resize(1);
434 tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
435 tx1.vout[0].nValue = 10 * COIN;
436 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
437
439 tx2.vin.resize(1);
440 tx2.vin[0].scriptSig = CScript() << OP_2;
441 tx2.vout.resize(1);
442 tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
443 tx2.vout[0].nValue = 10 * COIN;
444 pool.addUnchecked(entry.Fee(5000LL).FromTx(tx2));
445
446 pool.TrimToSize(pool.DynamicMemoryUsage()); // should do nothing
449
450 pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // should remove the lower-feerate transaction
453
454 pool.addUnchecked(entry.FromTx(tx2));
456 tx3.vin.resize(1);
457 tx3.vin[0].prevout = COutPoint(tx2.GetHash(), 0);
458 tx3.vin[0].scriptSig = CScript() << OP_2;
459 tx3.vout.resize(1);
460 tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
461 tx3.vout[0].nValue = 10 * COIN;
462 pool.addUnchecked(entry.Fee(20000LL).FromTx(tx3));
463
464 pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // tx3 should pay for tx2 (CPFP)
468
469 pool.TrimToSize(GetVirtualTransactionSize(CTransaction(tx1))); // mempool is limited to tx1's size in memory usage, so nothing fits
473
475 BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
476
478 tx4.vin.resize(2);
479 tx4.vin[0].prevout.SetNull();
480 tx4.vin[0].scriptSig = CScript() << OP_4;
481 tx4.vin[1].prevout.SetNull();
482 tx4.vin[1].scriptSig = CScript() << OP_4;
483 tx4.vout.resize(2);
484 tx4.vout[0].scriptPubKey = CScript() << OP_4 << OP_EQUAL;
485 tx4.vout[0].nValue = 10 * COIN;
486 tx4.vout[1].scriptPubKey = CScript() << OP_4 << OP_EQUAL;
487 tx4.vout[1].nValue = 10 * COIN;
488
490 tx5.vin.resize(2);
491 tx5.vin[0].prevout = COutPoint(tx4.GetHash(), 0);
492 tx5.vin[0].scriptSig = CScript() << OP_4;
493 tx5.vin[1].prevout.SetNull();
494 tx5.vin[1].scriptSig = CScript() << OP_5;
495 tx5.vout.resize(2);
496 tx5.vout[0].scriptPubKey = CScript() << OP_5 << OP_EQUAL;
497 tx5.vout[0].nValue = 10 * COIN;
498 tx5.vout[1].scriptPubKey = CScript() << OP_5 << OP_EQUAL;
499 tx5.vout[1].nValue = 10 * COIN;
500
502 tx6.vin.resize(2);
503 tx6.vin[0].prevout = COutPoint(tx4.GetHash(), 1);
504 tx6.vin[0].scriptSig = CScript() << OP_4;
505 tx6.vin[1].prevout.SetNull();
506 tx6.vin[1].scriptSig = CScript() << OP_6;
507 tx6.vout.resize(2);
508 tx6.vout[0].scriptPubKey = CScript() << OP_6 << OP_EQUAL;
509 tx6.vout[0].nValue = 10 * COIN;
510 tx6.vout[1].scriptPubKey = CScript() << OP_6 << OP_EQUAL;
511 tx6.vout[1].nValue = 10 * COIN;
512
514 tx7.vin.resize(2);
515 tx7.vin[0].prevout = COutPoint(tx5.GetHash(), 0);
516 tx7.vin[0].scriptSig = CScript() << OP_5;
517 tx7.vin[1].prevout = COutPoint(tx6.GetHash(), 0);
518 tx7.vin[1].scriptSig = CScript() << OP_6;
519 tx7.vout.resize(2);
520 tx7.vout[0].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
521 tx7.vout[0].nValue = 10 * COIN;
522 tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
523 tx7.vout[1].nValue = 10 * COIN;
524
525 pool.addUnchecked(entry.Fee(7000LL).FromTx(tx4));
526 pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
527 pool.addUnchecked(entry.Fee(1100LL).FromTx(tx6));
528 pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
529
530 // we only require this to remove, at max, 2 txn, because it's not clear what we're really optimizing for aside from that
531 pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
535
536 if (!pool.exists(GenTxid::Txid(tx5.GetHash())))
537 pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
538 pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
539
540 pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
545
546 pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
547 pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
548
549 std::vector<CTransactionRef> vtx;
550 SetMockTime(42);
552 BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
553 // ... we should keep the same min fee until we get a block
554 pool.removeForBlock(vtx, 1);
556 BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/2.0));
557 // ... then feerate should drop 1/2 each halflife
558
560 BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/4.0));
561 // ... with a 1/2 halflife when mempool is < 1/2 its target size
562
564 BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/8.0));
565 // ... with a 1/4 halflife when mempool is < 1/4 its target size
566
568 BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 1000);
569 // ... but feerate should never drop below 1000
570
573 // ... unless it has gone all the way to 0 (after getting past 1000/2)
574}
575
576inline CTransactionRef make_tx(std::vector<CAmount>&& output_values, std::vector<CTransactionRef>&& inputs=std::vector<CTransactionRef>(), std::vector<uint32_t>&& input_indices=std::vector<uint32_t>())
577{
579 tx.vin.resize(inputs.size());
580 tx.vout.resize(output_values.size());
581 for (size_t i = 0; i < inputs.size(); ++i) {
582 tx.vin[i].prevout.hash = inputs[i]->GetHash();
583 tx.vin[i].prevout.n = input_indices.size() > i ? input_indices[i] : 0;
584 }
585 for (size_t i = 0; i < output_values.size(); ++i) {
586 tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
587 tx.vout[i].nValue = output_values[i];
588 }
589 return MakeTransactionRef(tx);
590}
591
592
593BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
594{
595 size_t ancestors, descendants;
596
597 CTxMemPool pool;
598 LOCK2(cs_main, pool.cs);
600
601 /* Base transaction */
602 //
603 // [tx1]
604 //
605 CTransactionRef tx1 = make_tx(/* output_values */ {10 * COIN});
606 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
607
608 // Ancestors / descendants should be 1 / 1 (itself / itself)
609 pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
610 BOOST_CHECK_EQUAL(ancestors, 1ULL);
611 BOOST_CHECK_EQUAL(descendants, 1ULL);
612
613 /* Child transaction */
614 //
615 // [tx1].0 <- [tx2]
616 //
617 CTransactionRef tx2 = make_tx(/* output_values */ {495 * CENT, 5 * COIN}, /* inputs */ {tx1});
618 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx2));
619
620 // Ancestors / descendants should be:
621 // transaction ancestors descendants
622 // ============ =========== ===========
623 // tx1 1 (tx1) 2 (tx1,2)
624 // tx2 2 (tx1,2) 2 (tx1,2)
625 pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
626 BOOST_CHECK_EQUAL(ancestors, 1ULL);
627 BOOST_CHECK_EQUAL(descendants, 2ULL);
628 pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
629 BOOST_CHECK_EQUAL(ancestors, 2ULL);
630 BOOST_CHECK_EQUAL(descendants, 2ULL);
631
632 /* Grand-child 1 */
633 //
634 // [tx1].0 <- [tx2].0 <- [tx3]
635 //
636 CTransactionRef tx3 = make_tx(/* output_values */ {290 * CENT, 200 * CENT}, /* inputs */ {tx2});
637 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx3));
638
639 // Ancestors / descendants should be:
640 // transaction ancestors descendants
641 // ============ =========== ===========
642 // tx1 1 (tx1) 3 (tx1,2,3)
643 // tx2 2 (tx1,2) 3 (tx1,2,3)
644 // tx3 3 (tx1,2,3) 3 (tx1,2,3)
645 pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
646 BOOST_CHECK_EQUAL(ancestors, 1ULL);
647 BOOST_CHECK_EQUAL(descendants, 3ULL);
648 pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
649 BOOST_CHECK_EQUAL(ancestors, 2ULL);
650 BOOST_CHECK_EQUAL(descendants, 3ULL);
651 pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants);
652 BOOST_CHECK_EQUAL(ancestors, 3ULL);
653 BOOST_CHECK_EQUAL(descendants, 3ULL);
654
655 /* Grand-child 2 */
656 //
657 // [tx1].0 <- [tx2].0 <- [tx3]
658 // |
659 // \---1 <- [tx4]
660 //
661 CTransactionRef tx4 = make_tx(/* output_values */ {290 * CENT, 250 * CENT}, /* inputs */ {tx2}, /* input_indices */ {1});
662 pool.addUnchecked(entry.Fee(10000LL).FromTx(tx4));
663
664 // Ancestors / descendants should be:
665 // transaction ancestors descendants
666 // ============ =========== ===========
667 // tx1 1 (tx1) 4 (tx1,2,3,4)
668 // tx2 2 (tx1,2) 4 (tx1,2,3,4)
669 // tx3 3 (tx1,2,3) 4 (tx1,2,3,4)
670 // tx4 3 (tx1,2,4) 4 (tx1,2,3,4)
671 pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
672 BOOST_CHECK_EQUAL(ancestors, 1ULL);
673 BOOST_CHECK_EQUAL(descendants, 4ULL);
674 pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
675 BOOST_CHECK_EQUAL(ancestors, 2ULL);
676 BOOST_CHECK_EQUAL(descendants, 4ULL);
677 pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants);
678 BOOST_CHECK_EQUAL(ancestors, 3ULL);
679 BOOST_CHECK_EQUAL(descendants, 4ULL);
680 pool.GetTransactionAncestry(tx4->GetHash(), ancestors, descendants);
681 BOOST_CHECK_EQUAL(ancestors, 3ULL);
682 BOOST_CHECK_EQUAL(descendants, 4ULL);
683
684 /* Make an alternate branch that is longer and connect it to tx3 */
685 //
686 // [ty1].0 <- [ty2].0 <- [ty3].0 <- [ty4].0 <- [ty5].0
687 // |
688 // [tx1].0 <- [tx2].0 <- [tx3].0 <- [ty6] --->--/
689 // |
690 // \---1 <- [tx4]
691 //
692 CTransactionRef ty1, ty2, ty3, ty4, ty5;
693 CTransactionRef* ty[5] = {&ty1, &ty2, &ty3, &ty4, &ty5};
694 CAmount v = 5 * COIN;
695 for (uint64_t i = 0; i < 5; i++) {
696 CTransactionRef& tyi = *ty[i];
697 tyi = make_tx(/* output_values */ {v}, /* inputs */ i > 0 ? std::vector<CTransactionRef>{*ty[i - 1]} : std::vector<CTransactionRef>{});
698 v -= 50 * CENT;
699 pool.addUnchecked(entry.Fee(10000LL).FromTx(tyi));
700 pool.GetTransactionAncestry(tyi->GetHash(), ancestors, descendants);
701 BOOST_CHECK_EQUAL(ancestors, i+1);
702 BOOST_CHECK_EQUAL(descendants, i+1);
703 }
704 CTransactionRef ty6 = make_tx(/* output_values */ {5 * COIN}, /* inputs */ {tx3, ty5});
705 pool.addUnchecked(entry.Fee(10000LL).FromTx(ty6));
706
707 // Ancestors / descendants should be:
708 // transaction ancestors descendants
709 // ============ =================== ===========
710 // tx1 1 (tx1) 5 (tx1,2,3,4, ty6)
711 // tx2 2 (tx1,2) 5 (tx1,2,3,4, ty6)
712 // tx3 3 (tx1,2,3) 5 (tx1,2,3,4, ty6)
713 // tx4 3 (tx1,2,4) 5 (tx1,2,3,4, ty6)
714 // ty1 1 (ty1) 6 (ty1,2,3,4,5,6)
715 // ty2 2 (ty1,2) 6 (ty1,2,3,4,5,6)
716 // ty3 3 (ty1,2,3) 6 (ty1,2,3,4,5,6)
717 // ty4 4 (y1234) 6 (ty1,2,3,4,5,6)
718 // ty5 5 (y12345) 6 (ty1,2,3,4,5,6)
719 // ty6 9 (tx123, ty123456) 6 (ty1,2,3,4,5,6)
720 pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
721 BOOST_CHECK_EQUAL(ancestors, 1ULL);
722 BOOST_CHECK_EQUAL(descendants, 5ULL);
723 pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
724 BOOST_CHECK_EQUAL(ancestors, 2ULL);
725 BOOST_CHECK_EQUAL(descendants, 5ULL);
726 pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants);
727 BOOST_CHECK_EQUAL(ancestors, 3ULL);
728 BOOST_CHECK_EQUAL(descendants, 5ULL);
729 pool.GetTransactionAncestry(tx4->GetHash(), ancestors, descendants);
730 BOOST_CHECK_EQUAL(ancestors, 3ULL);
731 BOOST_CHECK_EQUAL(descendants, 5ULL);
732 pool.GetTransactionAncestry(ty1->GetHash(), ancestors, descendants);
733 BOOST_CHECK_EQUAL(ancestors, 1ULL);
734 BOOST_CHECK_EQUAL(descendants, 6ULL);
735 pool.GetTransactionAncestry(ty2->GetHash(), ancestors, descendants);
736 BOOST_CHECK_EQUAL(ancestors, 2ULL);
737 BOOST_CHECK_EQUAL(descendants, 6ULL);
738 pool.GetTransactionAncestry(ty3->GetHash(), ancestors, descendants);
739 BOOST_CHECK_EQUAL(ancestors, 3ULL);
740 BOOST_CHECK_EQUAL(descendants, 6ULL);
741 pool.GetTransactionAncestry(ty4->GetHash(), ancestors, descendants);
742 BOOST_CHECK_EQUAL(ancestors, 4ULL);
743 BOOST_CHECK_EQUAL(descendants, 6ULL);
744 pool.GetTransactionAncestry(ty5->GetHash(), ancestors, descendants);
745 BOOST_CHECK_EQUAL(ancestors, 5ULL);
746 BOOST_CHECK_EQUAL(descendants, 6ULL);
747 pool.GetTransactionAncestry(ty6->GetHash(), ancestors, descendants);
748 BOOST_CHECK_EQUAL(ancestors, 9ULL);
749 BOOST_CHECK_EQUAL(descendants, 6ULL);
750
751 /* Ancestors represented more than once ("diamond") */
752 //
753 // [ta].0 <- [tb].0 -----<------- [td].0
754 // | |
755 // \---1 <- [tc].0 --<--/
756 //
757 CTransactionRef ta, tb, tc, td;
758 ta = make_tx(/* output_values */ {10 * COIN});
759 tb = make_tx(/* output_values */ {5 * COIN, 3 * COIN}, /* inputs */ {ta});
760 tc = make_tx(/* output_values */ {2 * COIN}, /* inputs */ {tb}, /* input_indices */ {1});
761 td = make_tx(/* output_values */ {6 * COIN}, /* inputs */ {tb, tc}, /* input_indices */ {0, 0});
762 pool.clear();
763 pool.addUnchecked(entry.Fee(10000LL).FromTx(ta));
764 pool.addUnchecked(entry.Fee(10000LL).FromTx(tb));
765 pool.addUnchecked(entry.Fee(10000LL).FromTx(tc));
766 pool.addUnchecked(entry.Fee(10000LL).FromTx(td));
767
768 // Ancestors / descendants should be:
769 // transaction ancestors descendants
770 // ============ =================== ===========
771 // ta 1 (ta 4 (ta,tb,tc,td)
772 // tb 2 (ta,tb) 4 (ta,tb,tc,td)
773 // tc 3 (ta,tb,tc) 4 (ta,tb,tc,td)
774 // td 4 (ta,tb,tc,td) 4 (ta,tb,tc,td)
775 pool.GetTransactionAncestry(ta->GetHash(), ancestors, descendants);
776 BOOST_CHECK_EQUAL(ancestors, 1ULL);
777 BOOST_CHECK_EQUAL(descendants, 4ULL);
778 pool.GetTransactionAncestry(tb->GetHash(), ancestors, descendants);
779 BOOST_CHECK_EQUAL(ancestors, 2ULL);
780 BOOST_CHECK_EQUAL(descendants, 4ULL);
781 pool.GetTransactionAncestry(tc->GetHash(), ancestors, descendants);
782 BOOST_CHECK_EQUAL(ancestors, 3ULL);
783 BOOST_CHECK_EQUAL(descendants, 4ULL);
784 pool.GetTransactionAncestry(td->GetHash(), ancestors, descendants);
785 BOOST_CHECK_EQUAL(ancestors, 4ULL);
786 BOOST_CHECK_EQUAL(descendants, 4ULL);
787}
788
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static constexpr CAmount COIN
The amount of satoshis in one BTC.
Definition: amount.h:15
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: validation.cpp:118
Fee rate in satoshis per kilobyte: CAmount / kB.
Definition: feerate.h:30
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:57
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:27
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:406
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:260
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
Definition: txmempool.h:424
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
Definition: txmempool.h:511
CFeeRate GetMinFee(size_t sizelimit) const
The minimum fee to get into the mempool, which may itself not be enough for larger-sized transactions...
Definition: txmempool.cpp:1109
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs)
Definition: txmempool.cpp:589
void check(const CCoinsViewCache &active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(void addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate=true) EXCLUSIVE_LOCKS_REQUIRED(cs
If sanity-checking is turned on, check makes sure the pool is consistent (does not contain two transa...
Definition: txmempool.h:582
void TrimToSize(size_t sizelimit, std::vector< COutPoint > *pvNoSpendsRemaining=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs)
Remove transactions from the mempool until its dynamic size is <= sizelimit.
Definition: txmempool.cpp:1141
size_t DynamicMemoryUsage() const
Definition: txmempool.cpp:1038
void GetTransactionAncestry(const uint256 &txid, size_t &ancestors, size_t &descendants, size_t *ancestorsize=nullptr, CAmount *ancestorfees=nullptr) const
Calculate the ancestor and descendant count for the given transaction.
Definition: txmempool.cpp:1206
void clear()
Definition: txmempool.cpp:721
bool CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents=true) const EXCLUSIVE_LOCKS_REQUIRED(cs)
Try to calculate all in-mempool ancestors of entry.
Definition: txmempool.cpp:291
static const int ROLLING_FEE_HALFLIFE
Definition: txmempool.h:450
std::set< txiter, CompareIteratorByHash > setEntries
Definition: txmempool.h:517
void removeForBlock(const std::vector< CTransactionRef > &vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs)
Called when a block is connected.
Definition: txmempool.cpp:679
bool exists(const GenTxid &gtxid) const
Definition: txmempool.h:725
unsigned long size() const
Definition: txmempool.h:707
static GenTxid Txid(const uint256 &hash)
Definition: transaction.h:397
std::string ToString() const
Definition: uint256.cpp:64
BOOST_AUTO_TEST_SUITE_END()
CTransactionRef make_tx(std::vector< CAmount > &&output_values, std::vector< CTransactionRef > &&inputs=std::vector< CTransactionRef >(), std::vector< uint32_t > &&input_indices=std::vector< uint32_t >())
static void CheckSort(CTxMemPool &pool, std::vector< std::string > &sortedOrder) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
static constexpr auto REMOVAL_REASON_DUMMY
BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
#define BOOST_FIXTURE_TEST_SUITE(a, b)
Definition: object.cpp:14
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop)
Compute the virtual transaction size (weight reinterpreted as bytes).
Definition: policy.cpp:285
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:387
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:386
const char * name
Definition: rest.cpp:43
@ OP_2
Definition: script.h:78
@ OP_EQUAL
Definition: script.h:139
@ OP_4
Definition: script.h:80
@ OP_1
Definition: script.h:76
@ OP_3
Definition: script.h:79
@ OP_11
Definition: script.h:87
@ OP_6
Definition: script.h:82
@ OP_7
Definition: script.h:83
@ OP_5
Definition: script.h:81
static constexpr CAmount CENT
Definition: setup_common.h:71
A mutable version of CTransaction.
Definition: transaction.h:345
uint256 GetHash() const
Compute the hash of this CMutableTransaction.
Definition: transaction.cpp:63
std::vector< CTxOut > vout
Definition: transaction.h:347
std::vector< CTxIn > vin
Definition: transaction.h:346
Definition: setup_common.h:183
TestMemPoolEntryHelper & Time(int64_t _time)
Definition: setup_common.h:201
int64_t nTime
Definition: setup_common.h:186
CTxMemPoolEntry FromTx(const CMutableTransaction &tx) const
TestMemPoolEntryHelper & Fee(CAmount _fee)
Definition: setup_common.h:200
Testing setup that configures a complete environment.
Definition: setup_common.h:99
#define LOCK2(cs1, cs2)
Definition: sync.h:227
static int count
Definition: tests.c:41
#define EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: threadsafety.h:49
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:101
MemPoolRemovalReason
Reason why a transaction was removed from the mempool, this is passed to the notification signal.
Definition: txmempool.h:341