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