Line data Source code
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 :
10 : #include <test/util/setup_common.h>
11 :
12 : #include <boost/test/unit_test.hpp>
13 : #include <vector>
14 :
15 89 : BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup)
16 :
17 : static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED;
18 :
19 95 : BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
20 : {
21 : // Test CTxMemPool::remove functionality
22 :
23 1 : TestMemPoolEntryHelper entry;
24 : // Parent transaction with three children,
25 : // and three grand-children:
26 1 : CMutableTransaction txParent;
27 1 : txParent.vin.resize(1);
28 1 : txParent.vin[0].scriptSig = CScript() << OP_11;
29 1 : txParent.vout.resize(3);
30 4 : for (int i = 0; i < 3; i++)
31 : {
32 3 : txParent.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
33 3 : txParent.vout[i].nValue = 33000LL;
34 : }
35 3 : CMutableTransaction txChild[3];
36 4 : for (int i = 0; i < 3; i++)
37 : {
38 3 : txChild[i].vin.resize(1);
39 3 : txChild[i].vin[0].scriptSig = CScript() << OP_11;
40 3 : txChild[i].vin[0].prevout.hash = txParent.GetHash();
41 3 : txChild[i].vin[0].prevout.n = i;
42 3 : txChild[i].vout.resize(1);
43 3 : txChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
44 3 : txChild[i].vout[0].nValue = 11000LL;
45 : }
46 3 : CMutableTransaction txGrandChild[3];
47 4 : for (int i = 0; i < 3; i++)
48 : {
49 3 : txGrandChild[i].vin.resize(1);
50 3 : txGrandChild[i].vin[0].scriptSig = CScript() << OP_11;
51 3 : txGrandChild[i].vin[0].prevout.hash = txChild[i].GetHash();
52 3 : txGrandChild[i].vin[0].prevout.n = 0;
53 3 : txGrandChild[i].vout.resize(1);
54 3 : txGrandChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
55 3 : txGrandChild[i].vout[0].nValue = 11000LL;
56 : }
57 :
58 :
59 1 : CTxMemPool testPool;
60 1 : LOCK2(cs_main, testPool.cs);
61 :
62 : // Nothing in pool, remove should do nothing:
63 1 : unsigned int poolSize = testPool.size();
64 1 : testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY);
65 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize);
66 :
67 : // Just the parent:
68 1 : testPool.addUnchecked(entry.FromTx(txParent));
69 1 : poolSize = testPool.size();
70 1 : testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY);
71 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1);
72 :
73 : // Parent, children, grandchildren:
74 1 : testPool.addUnchecked(entry.FromTx(txParent));
75 4 : for (int i = 0; i < 3; i++)
76 : {
77 3 : testPool.addUnchecked(entry.FromTx(txChild[i]));
78 3 : testPool.addUnchecked(entry.FromTx(txGrandChild[i]));
79 : }
80 : // Remove Child[0], GrandChild[0] should be removed:
81 1 : poolSize = testPool.size();
82 1 : testPool.removeRecursive(CTransaction(txChild[0]), REMOVAL_REASON_DUMMY);
83 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize - 2);
84 : // ... make sure grandchild and child are gone:
85 1 : poolSize = testPool.size();
86 1 : testPool.removeRecursive(CTransaction(txGrandChild[0]), REMOVAL_REASON_DUMMY);
87 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize);
88 1 : poolSize = testPool.size();
89 1 : testPool.removeRecursive(CTransaction(txChild[0]), REMOVAL_REASON_DUMMY);
90 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize);
91 : // Remove parent, all children/grandchildren should go:
92 1 : poolSize = testPool.size();
93 1 : testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY);
94 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize - 5);
95 1 : BOOST_CHECK_EQUAL(testPool.size(), 0U);
96 :
97 : // Add children and grandchildren, but NOT the parent (simulate the parent being in a block)
98 4 : for (int i = 0; i < 3; i++)
99 : {
100 3 : testPool.addUnchecked(entry.FromTx(txChild[i]));
101 3 : 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 1 : poolSize = testPool.size();
106 1 : testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY);
107 1 : BOOST_CHECK_EQUAL(testPool.size(), poolSize - 6);
108 1 : BOOST_CHECK_EQUAL(testPool.size(), 0U);
109 5 : }
110 :
111 : template<typename name>
112 12 : static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
113 : {
114 12 : BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size());
115 12 : typename CTxMemPool::indexed_transaction_set::index<name>::type::iterator it = pool.mapTx.get<name>().begin();
116 : int count=0;
117 97 : for (; it != pool.mapTx.get<name>().end(); ++it, ++count) {
118 85 : BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]);
119 : }
120 12 : }
121 :
122 95 : BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
123 : {
124 1 : CTxMemPool pool;
125 1 : LOCK2(cs_main, pool.cs);
126 1 : TestMemPoolEntryHelper entry;
127 :
128 : /* 3rd highest fee */
129 1 : CMutableTransaction tx1 = CMutableTransaction();
130 1 : tx1.vout.resize(1);
131 1 : tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
132 1 : tx1.vout[0].nValue = 10 * COIN;
133 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
134 :
135 : /* highest fee */
136 1 : CMutableTransaction tx2 = CMutableTransaction();
137 1 : tx2.vout.resize(1);
138 1 : tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
139 1 : tx2.vout[0].nValue = 2 * COIN;
140 1 : pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2));
141 :
142 : /* lowest fee */
143 1 : CMutableTransaction tx3 = CMutableTransaction();
144 1 : tx3.vout.resize(1);
145 1 : tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
146 1 : tx3.vout[0].nValue = 5 * COIN;
147 1 : pool.addUnchecked(entry.Fee(0LL).FromTx(tx3));
148 :
149 : /* 2nd highest fee */
150 1 : CMutableTransaction tx4 = CMutableTransaction();
151 1 : tx4.vout.resize(1);
152 1 : tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
153 1 : tx4.vout[0].nValue = 6 * COIN;
154 1 : pool.addUnchecked(entry.Fee(15000LL).FromTx(tx4));
155 :
156 : /* equal fee rate to tx1, but newer */
157 1 : CMutableTransaction tx5 = CMutableTransaction();
158 1 : tx5.vout.resize(1);
159 1 : tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
160 1 : tx5.vout[0].nValue = 11 * COIN;
161 1 : entry.nTime = 1;
162 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5));
163 1 : BOOST_CHECK_EQUAL(pool.size(), 5U);
164 :
165 1 : std::vector<std::string> sortedOrder;
166 1 : sortedOrder.resize(5);
167 1 : sortedOrder[0] = tx3.GetHash().ToString(); // 0
168 1 : sortedOrder[1] = tx5.GetHash().ToString(); // 10000
169 1 : sortedOrder[2] = tx1.GetHash().ToString(); // 10000
170 1 : sortedOrder[3] = tx4.GetHash().ToString(); // 15000
171 1 : sortedOrder[4] = tx2.GetHash().ToString(); // 20000
172 1 : CheckSort<descendant_score>(pool, sortedOrder);
173 :
174 : /* low fee but with high fee child */
175 : /* tx6 -> tx7 -> tx8, tx9 -> tx10 */
176 1 : CMutableTransaction tx6 = CMutableTransaction();
177 1 : tx6.vout.resize(1);
178 1 : tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
179 1 : tx6.vout[0].nValue = 20 * COIN;
180 1 : pool.addUnchecked(entry.Fee(0LL).FromTx(tx6));
181 1 : BOOST_CHECK_EQUAL(pool.size(), 6U);
182 : // Check that at this point, tx6 is sorted low
183 1 : sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
184 1 : CheckSort<descendant_score>(pool, sortedOrder);
185 :
186 1 : CTxMemPool::setEntries setAncestors;
187 1 : setAncestors.insert(pool.mapTx.find(tx6.GetHash()));
188 1 : CMutableTransaction tx7 = CMutableTransaction();
189 1 : tx7.vin.resize(1);
190 1 : tx7.vin[0].prevout = COutPoint(tx6.GetHash(), 0);
191 1 : tx7.vin[0].scriptSig = CScript() << OP_11;
192 1 : tx7.vout.resize(2);
193 1 : tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
194 1 : tx7.vout[0].nValue = 10 * COIN;
195 1 : tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
196 1 : tx7.vout[1].nValue = 1 * COIN;
197 :
198 1 : CTxMemPool::setEntries setAncestorsCalculated;
199 1 : std::string dummy;
200 1 : BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true);
201 1 : BOOST_CHECK(setAncestorsCalculated == setAncestors);
202 :
203 1 : pool.addUnchecked(entry.FromTx(tx7), setAncestors);
204 1 : BOOST_CHECK_EQUAL(pool.size(), 7U);
205 :
206 : // Now tx6 should be sorted higher (high fee child): tx7, tx6, tx2, ...
207 1 : sortedOrder.erase(sortedOrder.begin());
208 1 : sortedOrder.push_back(tx6.GetHash().ToString());
209 1 : sortedOrder.push_back(tx7.GetHash().ToString());
210 1 : CheckSort<descendant_score>(pool, sortedOrder);
211 :
212 : /* low fee child of tx7 */
213 1 : CMutableTransaction tx8 = CMutableTransaction();
214 1 : tx8.vin.resize(1);
215 1 : tx8.vin[0].prevout = COutPoint(tx7.GetHash(), 0);
216 1 : tx8.vin[0].scriptSig = CScript() << OP_11;
217 1 : tx8.vout.resize(1);
218 1 : tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
219 1 : tx8.vout[0].nValue = 10 * COIN;
220 1 : setAncestors.insert(pool.mapTx.find(tx7.GetHash()));
221 1 : pool.addUnchecked(entry.Fee(0LL).Time(2).FromTx(tx8), setAncestors);
222 :
223 : // Now tx8 should be sorted low, but tx6/tx both high
224 1 : sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
225 1 : CheckSort<descendant_score>(pool, sortedOrder);
226 :
227 : /* low fee child of tx7 */
228 1 : CMutableTransaction tx9 = CMutableTransaction();
229 1 : tx9.vin.resize(1);
230 1 : tx9.vin[0].prevout = COutPoint(tx7.GetHash(), 1);
231 1 : tx9.vin[0].scriptSig = CScript() << OP_11;
232 1 : tx9.vout.resize(1);
233 1 : tx9.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
234 1 : tx9.vout[0].nValue = 1 * COIN;
235 1 : pool.addUnchecked(entry.Fee(0LL).Time(3).FromTx(tx9), setAncestors);
236 :
237 : // tx9 should be sorted low
238 1 : BOOST_CHECK_EQUAL(pool.size(), 9U);
239 1 : sortedOrder.insert(sortedOrder.begin(), tx9.GetHash().ToString());
240 1 : CheckSort<descendant_score>(pool, sortedOrder);
241 :
242 1 : std::vector<std::string> snapshotOrder = sortedOrder;
243 :
244 1 : setAncestors.insert(pool.mapTx.find(tx8.GetHash()));
245 1 : setAncestors.insert(pool.mapTx.find(tx9.GetHash()));
246 : /* tx10 depends on tx8 and tx9 and has a high fee*/
247 1 : CMutableTransaction tx10 = CMutableTransaction();
248 1 : tx10.vin.resize(2);
249 1 : tx10.vin[0].prevout = COutPoint(tx8.GetHash(), 0);
250 1 : tx10.vin[0].scriptSig = CScript() << OP_11;
251 1 : tx10.vin[1].prevout = COutPoint(tx9.GetHash(), 0);
252 1 : tx10.vin[1].scriptSig = CScript() << OP_11;
253 1 : tx10.vout.resize(1);
254 1 : tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
255 1 : tx10.vout[0].nValue = 10 * COIN;
256 :
257 1 : setAncestorsCalculated.clear();
258 1 : BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true);
259 1 : BOOST_CHECK(setAncestorsCalculated == setAncestors);
260 :
261 1 : pool.addUnchecked(entry.FromTx(tx10), setAncestors);
262 :
263 : /**
264 : * tx8 and tx9 should both now be sorted higher
265 : * Final order after tx10 is added:
266 : *
267 : * tx3 = 0 (1)
268 : * tx5 = 10000 (1)
269 : * tx1 = 10000 (1)
270 : * tx4 = 15000 (1)
271 : * tx2 = 20000 (1)
272 : * tx9 = 200k (2 txs)
273 : * tx8 = 200k (2 txs)
274 : * tx10 = 200k (1 tx)
275 : * tx6 = 2.2M (5 txs)
276 : * tx7 = 2.2M (4 txs)
277 : */
278 1 : sortedOrder.erase(sortedOrder.begin(), sortedOrder.begin()+2); // take out tx9, tx8 from the beginning
279 1 : sortedOrder.insert(sortedOrder.begin()+5, tx9.GetHash().ToString());
280 1 : sortedOrder.insert(sortedOrder.begin()+6, tx8.GetHash().ToString());
281 1 : sortedOrder.insert(sortedOrder.begin()+7, tx10.GetHash().ToString()); // tx10 is just before tx6
282 1 : CheckSort<descendant_score>(pool, sortedOrder);
283 :
284 : // there should be 10 transactions in the mempool
285 1 : BOOST_CHECK_EQUAL(pool.size(), 10U);
286 :
287 : // Now try removing tx10 and verify the sort order returns to normal
288 1 : pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx(), REMOVAL_REASON_DUMMY);
289 1 : CheckSort<descendant_score>(pool, snapshotOrder);
290 :
291 1 : pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx(), REMOVAL_REASON_DUMMY);
292 1 : pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx(), REMOVAL_REASON_DUMMY);
293 1 : }
294 :
295 95 : BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
296 : {
297 1 : CTxMemPool pool;
298 1 : LOCK2(cs_main, pool.cs);
299 1 : TestMemPoolEntryHelper entry;
300 :
301 : /* 3rd highest fee */
302 1 : CMutableTransaction tx1 = CMutableTransaction();
303 1 : tx1.vout.resize(1);
304 1 : tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
305 1 : tx1.vout[0].nValue = 10 * COIN;
306 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
307 :
308 : /* highest fee */
309 1 : CMutableTransaction tx2 = CMutableTransaction();
310 1 : tx2.vout.resize(1);
311 1 : tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
312 1 : tx2.vout[0].nValue = 2 * COIN;
313 1 : pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2));
314 1 : uint64_t tx2Size = GetVirtualTransactionSize(CTransaction(tx2));
315 :
316 : /* lowest fee */
317 1 : CMutableTransaction tx3 = CMutableTransaction();
318 1 : tx3.vout.resize(1);
319 1 : tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
320 1 : tx3.vout[0].nValue = 5 * COIN;
321 1 : pool.addUnchecked(entry.Fee(0LL).FromTx(tx3));
322 :
323 : /* 2nd highest fee */
324 1 : CMutableTransaction tx4 = CMutableTransaction();
325 1 : tx4.vout.resize(1);
326 1 : tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
327 1 : tx4.vout[0].nValue = 6 * COIN;
328 1 : pool.addUnchecked(entry.Fee(15000LL).FromTx(tx4));
329 :
330 : /* equal fee rate to tx1, but newer */
331 1 : CMutableTransaction tx5 = CMutableTransaction();
332 1 : tx5.vout.resize(1);
333 1 : tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
334 1 : tx5.vout[0].nValue = 11 * COIN;
335 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5));
336 1 : BOOST_CHECK_EQUAL(pool.size(), 5U);
337 :
338 1 : std::vector<std::string> sortedOrder;
339 1 : sortedOrder.resize(5);
340 1 : sortedOrder[0] = tx2.GetHash().ToString(); // 20000
341 1 : 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 1 : if (tx1.GetHash() < tx5.GetHash()) {
346 1 : sortedOrder[2] = tx1.GetHash().ToString();
347 1 : sortedOrder[3] = tx5.GetHash().ToString();
348 1 : } else {
349 0 : sortedOrder[2] = tx5.GetHash().ToString();
350 0 : sortedOrder[3] = tx1.GetHash().ToString();
351 : }
352 1 : sortedOrder[4] = tx3.GetHash().ToString(); // 0
353 :
354 1 : CheckSort<ancestor_score>(pool, sortedOrder);
355 :
356 : /* low fee parent with high fee child */
357 : /* tx6 (0) -> tx7 (high) */
358 1 : CMutableTransaction tx6 = CMutableTransaction();
359 1 : tx6.vout.resize(1);
360 1 : tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
361 1 : tx6.vout[0].nValue = 20 * COIN;
362 1 : uint64_t tx6Size = GetVirtualTransactionSize(CTransaction(tx6));
363 :
364 1 : pool.addUnchecked(entry.Fee(0LL).FromTx(tx6));
365 1 : BOOST_CHECK_EQUAL(pool.size(), 6U);
366 : // Ties are broken by hash
367 1 : if (tx3.GetHash() < tx6.GetHash())
368 0 : sortedOrder.push_back(tx6.GetHash().ToString());
369 : else
370 1 : sortedOrder.insert(sortedOrder.end()-1,tx6.GetHash().ToString());
371 :
372 1 : CheckSort<ancestor_score>(pool, sortedOrder);
373 :
374 1 : CMutableTransaction tx7 = CMutableTransaction();
375 1 : tx7.vin.resize(1);
376 1 : tx7.vin[0].prevout = COutPoint(tx6.GetHash(), 0);
377 1 : tx7.vin[0].scriptSig = CScript() << OP_11;
378 1 : tx7.vout.resize(1);
379 1 : tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
380 1 : tx7.vout[0].nValue = 10 * COIN;
381 1 : uint64_t tx7Size = GetVirtualTransactionSize(CTransaction(tx7));
382 :
383 : /* set the fee to just below tx2's feerate when including ancestor */
384 1 : CAmount fee = (20000/tx2Size)*(tx7Size + tx6Size) - 1;
385 :
386 1 : pool.addUnchecked(entry.Fee(fee).FromTx(tx7));
387 1 : BOOST_CHECK_EQUAL(pool.size(), 7U);
388 1 : sortedOrder.insert(sortedOrder.begin()+1, tx7.GetHash().ToString());
389 1 : CheckSort<ancestor_score>(pool, sortedOrder);
390 :
391 : /* after tx6 is mined, tx7 should move up in the sort */
392 1 : std::vector<CTransactionRef> vtx;
393 1 : vtx.push_back(MakeTransactionRef(tx6));
394 1 : pool.removeForBlock(vtx, 1);
395 :
396 1 : sortedOrder.erase(sortedOrder.begin()+1);
397 : // Ties are broken by hash
398 1 : if (tx3.GetHash() < tx6.GetHash())
399 0 : sortedOrder.pop_back();
400 : else
401 1 : sortedOrder.erase(sortedOrder.end()-2);
402 1 : sortedOrder.insert(sortedOrder.begin(), tx7.GetHash().ToString());
403 1 : CheckSort<ancestor_score>(pool, sortedOrder);
404 :
405 : // High-fee parent, low-fee child
406 : // tx7 -> tx8
407 1 : CMutableTransaction tx8 = CMutableTransaction();
408 1 : tx8.vin.resize(1);
409 1 : tx8.vin[0].prevout = COutPoint(tx7.GetHash(), 0);
410 1 : tx8.vin[0].scriptSig = CScript() << OP_11;
411 1 : tx8.vout.resize(1);
412 1 : tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
413 1 : 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 1 : pool.addUnchecked(entry.Fee(5000LL).FromTx(tx8));
419 1 : sortedOrder.insert(sortedOrder.end()-1, tx8.GetHash().ToString());
420 1 : CheckSort<ancestor_score>(pool, sortedOrder);
421 1 : }
422 :
423 :
424 95 : BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
425 : {
426 1 : CTxMemPool pool;
427 1 : LOCK2(cs_main, pool.cs);
428 1 : TestMemPoolEntryHelper entry;
429 :
430 1 : CMutableTransaction tx1 = CMutableTransaction();
431 1 : tx1.vin.resize(1);
432 1 : tx1.vin[0].scriptSig = CScript() << OP_1;
433 1 : tx1.vout.resize(1);
434 1 : tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
435 1 : tx1.vout[0].nValue = 10 * COIN;
436 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
437 :
438 1 : CMutableTransaction tx2 = CMutableTransaction();
439 1 : tx2.vin.resize(1);
440 1 : tx2.vin[0].scriptSig = CScript() << OP_2;
441 1 : tx2.vout.resize(1);
442 1 : tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
443 1 : tx2.vout[0].nValue = 10 * COIN;
444 1 : pool.addUnchecked(entry.Fee(5000LL).FromTx(tx2));
445 :
446 1 : pool.TrimToSize(pool.DynamicMemoryUsage()); // should do nothing
447 1 : BOOST_CHECK(pool.exists(tx1.GetHash()));
448 1 : BOOST_CHECK(pool.exists(tx2.GetHash()));
449 :
450 1 : pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // should remove the lower-feerate transaction
451 1 : BOOST_CHECK(pool.exists(tx1.GetHash()));
452 1 : BOOST_CHECK(!pool.exists(tx2.GetHash()));
453 :
454 1 : pool.addUnchecked(entry.FromTx(tx2));
455 1 : CMutableTransaction tx3 = CMutableTransaction();
456 1 : tx3.vin.resize(1);
457 1 : tx3.vin[0].prevout = COutPoint(tx2.GetHash(), 0);
458 1 : tx3.vin[0].scriptSig = CScript() << OP_2;
459 1 : tx3.vout.resize(1);
460 1 : tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
461 1 : tx3.vout[0].nValue = 10 * COIN;
462 1 : pool.addUnchecked(entry.Fee(20000LL).FromTx(tx3));
463 :
464 1 : pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // tx3 should pay for tx2 (CPFP)
465 1 : BOOST_CHECK(!pool.exists(tx1.GetHash()));
466 1 : BOOST_CHECK(pool.exists(tx2.GetHash()));
467 1 : BOOST_CHECK(pool.exists(tx3.GetHash()));
468 :
469 1 : pool.TrimToSize(GetVirtualTransactionSize(CTransaction(tx1))); // mempool is limited to tx1's size in memory usage, so nothing fits
470 1 : BOOST_CHECK(!pool.exists(tx1.GetHash()));
471 1 : BOOST_CHECK(!pool.exists(tx2.GetHash()));
472 1 : BOOST_CHECK(!pool.exists(tx3.GetHash()));
473 :
474 1 : CFeeRate maxFeeRateRemoved(25000, GetVirtualTransactionSize(CTransaction(tx3)) + GetVirtualTransactionSize(CTransaction(tx2)));
475 1 : BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
476 :
477 1 : CMutableTransaction tx4 = CMutableTransaction();
478 1 : tx4.vin.resize(2);
479 1 : tx4.vin[0].prevout.SetNull();
480 1 : tx4.vin[0].scriptSig = CScript() << OP_4;
481 1 : tx4.vin[1].prevout.SetNull();
482 1 : tx4.vin[1].scriptSig = CScript() << OP_4;
483 1 : tx4.vout.resize(2);
484 1 : tx4.vout[0].scriptPubKey = CScript() << OP_4 << OP_EQUAL;
485 1 : tx4.vout[0].nValue = 10 * COIN;
486 1 : tx4.vout[1].scriptPubKey = CScript() << OP_4 << OP_EQUAL;
487 1 : tx4.vout[1].nValue = 10 * COIN;
488 :
489 1 : CMutableTransaction tx5 = CMutableTransaction();
490 1 : tx5.vin.resize(2);
491 1 : tx5.vin[0].prevout = COutPoint(tx4.GetHash(), 0);
492 1 : tx5.vin[0].scriptSig = CScript() << OP_4;
493 1 : tx5.vin[1].prevout.SetNull();
494 1 : tx5.vin[1].scriptSig = CScript() << OP_5;
495 1 : tx5.vout.resize(2);
496 1 : tx5.vout[0].scriptPubKey = CScript() << OP_5 << OP_EQUAL;
497 1 : tx5.vout[0].nValue = 10 * COIN;
498 1 : tx5.vout[1].scriptPubKey = CScript() << OP_5 << OP_EQUAL;
499 1 : tx5.vout[1].nValue = 10 * COIN;
500 :
501 1 : CMutableTransaction tx6 = CMutableTransaction();
502 1 : tx6.vin.resize(2);
503 1 : tx6.vin[0].prevout = COutPoint(tx4.GetHash(), 1);
504 1 : tx6.vin[0].scriptSig = CScript() << OP_4;
505 1 : tx6.vin[1].prevout.SetNull();
506 1 : tx6.vin[1].scriptSig = CScript() << OP_6;
507 1 : tx6.vout.resize(2);
508 1 : tx6.vout[0].scriptPubKey = CScript() << OP_6 << OP_EQUAL;
509 1 : tx6.vout[0].nValue = 10 * COIN;
510 1 : tx6.vout[1].scriptPubKey = CScript() << OP_6 << OP_EQUAL;
511 1 : tx6.vout[1].nValue = 10 * COIN;
512 :
513 1 : CMutableTransaction tx7 = CMutableTransaction();
514 1 : tx7.vin.resize(2);
515 1 : tx7.vin[0].prevout = COutPoint(tx5.GetHash(), 0);
516 1 : tx7.vin[0].scriptSig = CScript() << OP_5;
517 1 : tx7.vin[1].prevout = COutPoint(tx6.GetHash(), 0);
518 1 : tx7.vin[1].scriptSig = CScript() << OP_6;
519 1 : tx7.vout.resize(2);
520 1 : tx7.vout[0].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
521 1 : tx7.vout[0].nValue = 10 * COIN;
522 1 : tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
523 1 : tx7.vout[1].nValue = 10 * COIN;
524 :
525 1 : pool.addUnchecked(entry.Fee(7000LL).FromTx(tx4));
526 1 : pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
527 1 : pool.addUnchecked(entry.Fee(1100LL).FromTx(tx6));
528 1 : 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 1 : pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
532 1 : BOOST_CHECK(pool.exists(tx4.GetHash()));
533 1 : BOOST_CHECK(pool.exists(tx6.GetHash()));
534 1 : BOOST_CHECK(!pool.exists(tx7.GetHash()));
535 :
536 1 : if (!pool.exists(tx5.GetHash()))
537 1 : pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
538 1 : pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
539 :
540 1 : pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
541 1 : BOOST_CHECK(pool.exists(tx4.GetHash()));
542 1 : BOOST_CHECK(!pool.exists(tx5.GetHash()));
543 1 : BOOST_CHECK(pool.exists(tx6.GetHash()));
544 1 : BOOST_CHECK(!pool.exists(tx7.GetHash()));
545 :
546 1 : pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
547 1 : pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
548 :
549 1 : std::vector<CTransactionRef> vtx;
550 1 : SetMockTime(42);
551 1 : SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE);
552 1 : BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
553 : // ... we should keep the same min fee until we get a block
554 1 : pool.removeForBlock(vtx, 1);
555 1 : SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE);
556 1 : BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/2.0));
557 : // ... then feerate should drop 1/2 each halflife
558 :
559 1 : SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2);
560 1 : 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 :
563 1 : SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
564 1 : 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 :
567 1 : SetMockTime(42 + 7*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
568 1 : BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 1000);
569 : // ... but feerate should never drop below 1000
570 :
571 1 : SetMockTime(42 + 8*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
572 1 : BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 0);
573 : // ... unless it has gone all the way to 0 (after getting past 1000/2)
574 :
575 1 : SetMockTime(0);
576 1 : }
577 :
578 14 : 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>())
579 : {
580 14 : CMutableTransaction tx = CMutableTransaction();
581 14 : tx.vin.resize(inputs.size());
582 14 : tx.vout.resize(output_values.size());
583 27 : for (size_t i = 0; i < inputs.size(); ++i) {
584 13 : tx.vin[i].prevout.hash = inputs[i]->GetHash();
585 13 : tx.vin[i].prevout.n = input_indices.size() > i ? input_indices[i] : 0;
586 : }
587 32 : for (size_t i = 0; i < output_values.size(); ++i) {
588 18 : tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
589 18 : tx.vout[i].nValue = output_values[i];
590 : }
591 14 : return MakeTransactionRef(tx);
592 14 : }
593 :
594 :
595 95 : BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
596 : {
597 1 : size_t ancestors, descendants;
598 :
599 1 : CTxMemPool pool;
600 1 : LOCK2(cs_main, pool.cs);
601 1 : TestMemPoolEntryHelper entry;
602 :
603 : /* Base transaction */
604 : //
605 : // [tx1]
606 : //
607 1 : CTransactionRef tx1 = make_tx(/* output_values */ {10 * COIN});
608 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
609 :
610 : // Ancestors / descendants should be 1 / 1 (itself / itself)
611 1 : pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
612 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
613 1 : BOOST_CHECK_EQUAL(descendants, 1ULL);
614 :
615 : /* Child transaction */
616 : //
617 : // [tx1].0 <- [tx2]
618 : //
619 1 : CTransactionRef tx2 = make_tx(/* output_values */ {495 * CENT, 5 * COIN}, /* inputs */ {tx1});
620 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx2));
621 :
622 : // Ancestors / descendants should be:
623 : // transaction ancestors descendants
624 : // ============ =========== ===========
625 : // tx1 1 (tx1) 2 (tx1,2)
626 : // tx2 2 (tx1,2) 2 (tx1,2)
627 1 : pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
628 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
629 1 : BOOST_CHECK_EQUAL(descendants, 2ULL);
630 1 : pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
631 1 : BOOST_CHECK_EQUAL(ancestors, 2ULL);
632 1 : BOOST_CHECK_EQUAL(descendants, 2ULL);
633 :
634 : /* Grand-child 1 */
635 : //
636 : // [tx1].0 <- [tx2].0 <- [tx3]
637 : //
638 1 : CTransactionRef tx3 = make_tx(/* output_values */ {290 * CENT, 200 * CENT}, /* inputs */ {tx2});
639 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx3));
640 :
641 : // Ancestors / descendants should be:
642 : // transaction ancestors descendants
643 : // ============ =========== ===========
644 : // tx1 1 (tx1) 3 (tx1,2,3)
645 : // tx2 2 (tx1,2) 3 (tx1,2,3)
646 : // tx3 3 (tx1,2,3) 3 (tx1,2,3)
647 1 : pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
648 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
649 1 : BOOST_CHECK_EQUAL(descendants, 3ULL);
650 1 : pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
651 1 : BOOST_CHECK_EQUAL(ancestors, 2ULL);
652 1 : BOOST_CHECK_EQUAL(descendants, 3ULL);
653 1 : pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants);
654 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
655 1 : BOOST_CHECK_EQUAL(descendants, 3ULL);
656 :
657 : /* Grand-child 2 */
658 : //
659 : // [tx1].0 <- [tx2].0 <- [tx3]
660 : // |
661 : // \---1 <- [tx4]
662 : //
663 1 : CTransactionRef tx4 = make_tx(/* output_values */ {290 * CENT, 250 * CENT}, /* inputs */ {tx2}, /* input_indices */ {1});
664 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tx4));
665 :
666 : // Ancestors / descendants should be:
667 : // transaction ancestors descendants
668 : // ============ =========== ===========
669 : // tx1 1 (tx1) 4 (tx1,2,3,4)
670 : // tx2 2 (tx1,2) 4 (tx1,2,3,4)
671 : // tx3 3 (tx1,2,3) 4 (tx1,2,3,4)
672 : // tx4 3 (tx1,2,4) 4 (tx1,2,3,4)
673 1 : pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
674 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
675 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
676 1 : pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
677 1 : BOOST_CHECK_EQUAL(ancestors, 2ULL);
678 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
679 1 : pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants);
680 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
681 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
682 1 : pool.GetTransactionAncestry(tx4->GetHash(), ancestors, descendants);
683 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
684 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
685 :
686 : /* Make an alternate branch that is longer and connect it to tx3 */
687 : //
688 : // [ty1].0 <- [ty2].0 <- [ty3].0 <- [ty4].0 <- [ty5].0
689 : // |
690 : // [tx1].0 <- [tx2].0 <- [tx3].0 <- [ty6] --->--/
691 : // |
692 : // \---1 <- [tx4]
693 : //
694 1 : CTransactionRef ty1, ty2, ty3, ty4, ty5;
695 1 : CTransactionRef* ty[5] = {&ty1, &ty2, &ty3, &ty4, &ty5};
696 : CAmount v = 5 * COIN;
697 6 : for (uint64_t i = 0; i < 5; i++) {
698 5 : CTransactionRef& tyi = *ty[i];
699 5 : tyi = make_tx(/* output_values */ {v}, /* inputs */ i > 0 ? std::vector<CTransactionRef>{*ty[i - 1]} : std::vector<CTransactionRef>{});
700 5 : v -= 50 * CENT;
701 5 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tyi));
702 5 : pool.GetTransactionAncestry(tyi->GetHash(), ancestors, descendants);
703 5 : BOOST_CHECK_EQUAL(ancestors, i+1);
704 5 : BOOST_CHECK_EQUAL(descendants, i+1);
705 : }
706 2 : CTransactionRef ty6 = make_tx(/* output_values */ {5 * COIN}, /* inputs */ {tx3, ty5});
707 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(ty6));
708 :
709 : // Ancestors / descendants should be:
710 : // transaction ancestors descendants
711 : // ============ =================== ===========
712 : // tx1 1 (tx1) 5 (tx1,2,3,4, ty6)
713 : // tx2 2 (tx1,2) 5 (tx1,2,3,4, ty6)
714 : // tx3 3 (tx1,2,3) 5 (tx1,2,3,4, ty6)
715 : // tx4 3 (tx1,2,4) 5 (tx1,2,3,4, ty6)
716 : // ty1 1 (ty1) 6 (ty1,2,3,4,5,6)
717 : // ty2 2 (ty1,2) 6 (ty1,2,3,4,5,6)
718 : // ty3 3 (ty1,2,3) 6 (ty1,2,3,4,5,6)
719 : // ty4 4 (y1234) 6 (ty1,2,3,4,5,6)
720 : // ty5 5 (y12345) 6 (ty1,2,3,4,5,6)
721 : // ty6 9 (tx123, ty123456) 6 (ty1,2,3,4,5,6)
722 1 : pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
723 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
724 1 : BOOST_CHECK_EQUAL(descendants, 5ULL);
725 1 : pool.GetTransactionAncestry(tx2->GetHash(), ancestors, descendants);
726 1 : BOOST_CHECK_EQUAL(ancestors, 2ULL);
727 1 : BOOST_CHECK_EQUAL(descendants, 5ULL);
728 1 : pool.GetTransactionAncestry(tx3->GetHash(), ancestors, descendants);
729 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
730 1 : BOOST_CHECK_EQUAL(descendants, 5ULL);
731 1 : pool.GetTransactionAncestry(tx4->GetHash(), ancestors, descendants);
732 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
733 1 : BOOST_CHECK_EQUAL(descendants, 5ULL);
734 1 : pool.GetTransactionAncestry(ty1->GetHash(), ancestors, descendants);
735 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
736 1 : BOOST_CHECK_EQUAL(descendants, 6ULL);
737 1 : pool.GetTransactionAncestry(ty2->GetHash(), ancestors, descendants);
738 1 : BOOST_CHECK_EQUAL(ancestors, 2ULL);
739 1 : BOOST_CHECK_EQUAL(descendants, 6ULL);
740 1 : pool.GetTransactionAncestry(ty3->GetHash(), ancestors, descendants);
741 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
742 1 : BOOST_CHECK_EQUAL(descendants, 6ULL);
743 1 : pool.GetTransactionAncestry(ty4->GetHash(), ancestors, descendants);
744 1 : BOOST_CHECK_EQUAL(ancestors, 4ULL);
745 1 : BOOST_CHECK_EQUAL(descendants, 6ULL);
746 1 : pool.GetTransactionAncestry(ty5->GetHash(), ancestors, descendants);
747 1 : BOOST_CHECK_EQUAL(ancestors, 5ULL);
748 1 : BOOST_CHECK_EQUAL(descendants, 6ULL);
749 1 : pool.GetTransactionAncestry(ty6->GetHash(), ancestors, descendants);
750 1 : BOOST_CHECK_EQUAL(ancestors, 9ULL);
751 1 : BOOST_CHECK_EQUAL(descendants, 6ULL);
752 :
753 : /* Ancestors represented more than once ("diamond") */
754 : //
755 : // [ta].0 <- [tb].0 -----<------- [td].0
756 : // | |
757 : // \---1 <- [tc].0 --<--/
758 : //
759 1 : CTransactionRef ta, tb, tc, td;
760 1 : ta = make_tx(/* output_values */ {10 * COIN});
761 1 : tb = make_tx(/* output_values */ {5 * COIN, 3 * COIN}, /* inputs */ {ta});
762 1 : tc = make_tx(/* output_values */ {2 * COIN}, /* inputs */ {tb}, /* input_indices */ {1});
763 2 : td = make_tx(/* output_values */ {6 * COIN}, /* inputs */ {tb, tc}, /* input_indices */ {0, 0});
764 1 : pool.clear();
765 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(ta));
766 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tb));
767 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(tc));
768 1 : pool.addUnchecked(entry.Fee(10000LL).FromTx(td));
769 :
770 : // Ancestors / descendants should be:
771 : // transaction ancestors descendants
772 : // ============ =================== ===========
773 : // ta 1 (ta 4 (ta,tb,tc,td)
774 : // tb 2 (ta,tb) 4 (ta,tb,tc,td)
775 : // tc 3 (ta,tb,tc) 4 (ta,tb,tc,td)
776 : // td 4 (ta,tb,tc,td) 4 (ta,tb,tc,td)
777 1 : pool.GetTransactionAncestry(ta->GetHash(), ancestors, descendants);
778 1 : BOOST_CHECK_EQUAL(ancestors, 1ULL);
779 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
780 1 : pool.GetTransactionAncestry(tb->GetHash(), ancestors, descendants);
781 1 : BOOST_CHECK_EQUAL(ancestors, 2ULL);
782 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
783 1 : pool.GetTransactionAncestry(tc->GetHash(), ancestors, descendants);
784 1 : BOOST_CHECK_EQUAL(ancestors, 3ULL);
785 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
786 1 : pool.GetTransactionAncestry(td->GetHash(), ancestors, descendants);
787 1 : BOOST_CHECK_EQUAL(ancestors, 4ULL);
788 1 : BOOST_CHECK_EQUAL(descendants, 4ULL);
789 1 : }
790 :
791 89 : BOOST_AUTO_TEST_SUITE_END()
|