Skip to content

Commit 074cb15

Browse files
committed
Add reasonable test case for mempool trimming
1 parent d355cf4 commit 074cb15

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

src/test/mempool_tests.cpp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,158 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
281281
CheckSort(pool, snapshotOrder);
282282
}
283283

284+
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
285+
{
286+
CTxMemPool pool(CFeeRate(1000));
287+
288+
CMutableTransaction tx1 = CMutableTransaction();
289+
tx1.vin.resize(1);
290+
tx1.vin[0].scriptSig = CScript() << OP_1;
291+
tx1.vout.resize(1);
292+
tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
293+
tx1.vout[0].nValue = 10 * COIN;
294+
pool.addUnchecked(tx1.GetHash(), CTxMemPoolEntry(tx1, 10000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx1)));
295+
296+
CMutableTransaction tx2 = CMutableTransaction();
297+
tx2.vin.resize(1);
298+
tx2.vin[0].scriptSig = CScript() << OP_2;
299+
tx2.vout.resize(1);
300+
tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
301+
tx2.vout[0].nValue = 10 * COIN;
302+
pool.addUnchecked(tx2.GetHash(), CTxMemPoolEntry(tx2, 5000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx2)));
303+
304+
pool.TrimToSize(pool.DynamicMemoryUsage()); // should do nothing
305+
BOOST_CHECK(pool.exists(tx1.GetHash()));
306+
BOOST_CHECK(pool.exists(tx2.GetHash()));
307+
308+
pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // should remove the lower-feerate transaction
309+
BOOST_CHECK(pool.exists(tx1.GetHash()));
310+
BOOST_CHECK(!pool.exists(tx2.GetHash()));
311+
312+
pool.addUnchecked(tx2.GetHash(), CTxMemPoolEntry(tx2, 5000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx2)));
313+
CMutableTransaction tx3 = CMutableTransaction();
314+
tx3.vin.resize(1);
315+
tx3.vin[0].prevout = COutPoint(tx2.GetHash(), 0);
316+
tx3.vin[0].scriptSig = CScript() << OP_2;
317+
tx3.vout.resize(1);
318+
tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
319+
tx3.vout[0].nValue = 10 * COIN;
320+
pool.addUnchecked(tx3.GetHash(), CTxMemPoolEntry(tx3, 20000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx3)));
321+
322+
pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // tx3 should pay for tx2 (CPFP)
323+
BOOST_CHECK(!pool.exists(tx1.GetHash()));
324+
BOOST_CHECK(pool.exists(tx2.GetHash()));
325+
BOOST_CHECK(pool.exists(tx3.GetHash()));
326+
327+
pool.TrimToSize(::GetSerializeSize(CTransaction(tx1), SER_NETWORK, PROTOCOL_VERSION)); // mempool is limited to tx1's size in memory usage, so nothing fits
328+
BOOST_CHECK(!pool.exists(tx1.GetHash()));
329+
BOOST_CHECK(!pool.exists(tx2.GetHash()));
330+
BOOST_CHECK(!pool.exists(tx3.GetHash()));
331+
332+
CFeeRate maxFeeRateRemoved(25000, ::GetSerializeSize(CTransaction(tx3), SER_NETWORK, PROTOCOL_VERSION) + ::GetSerializeSize(CTransaction(tx2), SER_NETWORK, PROTOCOL_VERSION));
333+
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
334+
335+
CMutableTransaction tx4 = CMutableTransaction();
336+
tx4.vin.resize(2);
337+
tx4.vin[0].prevout.SetNull();
338+
tx4.vin[0].scriptSig = CScript() << OP_4;
339+
tx4.vin[1].prevout.SetNull();
340+
tx4.vin[1].scriptSig = CScript() << OP_4;
341+
tx4.vout.resize(2);
342+
tx4.vout[0].scriptPubKey = CScript() << OP_4 << OP_EQUAL;
343+
tx4.vout[0].nValue = 10 * COIN;
344+
tx4.vout[1].scriptPubKey = CScript() << OP_4 << OP_EQUAL;
345+
tx4.vout[1].nValue = 10 * COIN;
346+
347+
CMutableTransaction tx5 = CMutableTransaction();
348+
tx5.vin.resize(2);
349+
tx5.vin[0].prevout = COutPoint(tx4.GetHash(), 0);
350+
tx5.vin[0].scriptSig = CScript() << OP_4;
351+
tx5.vin[1].prevout.SetNull();
352+
tx5.vin[1].scriptSig = CScript() << OP_5;
353+
tx5.vout.resize(2);
354+
tx5.vout[0].scriptPubKey = CScript() << OP_5 << OP_EQUAL;
355+
tx5.vout[0].nValue = 10 * COIN;
356+
tx5.vout[1].scriptPubKey = CScript() << OP_5 << OP_EQUAL;
357+
tx5.vout[1].nValue = 10 * COIN;
358+
359+
CMutableTransaction tx6 = CMutableTransaction();
360+
tx6.vin.resize(2);
361+
tx6.vin[0].prevout = COutPoint(tx4.GetHash(), 1);
362+
tx6.vin[0].scriptSig = CScript() << OP_4;
363+
tx6.vin[1].prevout.SetNull();
364+
tx6.vin[1].scriptSig = CScript() << OP_6;
365+
tx6.vout.resize(2);
366+
tx6.vout[0].scriptPubKey = CScript() << OP_6 << OP_EQUAL;
367+
tx6.vout[0].nValue = 10 * COIN;
368+
tx6.vout[1].scriptPubKey = CScript() << OP_6 << OP_EQUAL;
369+
tx6.vout[1].nValue = 10 * COIN;
370+
371+
CMutableTransaction tx7 = CMutableTransaction();
372+
tx7.vin.resize(2);
373+
tx7.vin[0].prevout = COutPoint(tx5.GetHash(), 0);
374+
tx7.vin[0].scriptSig = CScript() << OP_5;
375+
tx7.vin[1].prevout = COutPoint(tx6.GetHash(), 0);
376+
tx7.vin[1].scriptSig = CScript() << OP_6;
377+
tx7.vout.resize(2);
378+
tx7.vout[0].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
379+
tx7.vout[0].nValue = 10 * COIN;
380+
tx7.vout[0].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
381+
tx7.vout[0].nValue = 10 * COIN;
382+
383+
pool.addUnchecked(tx4.GetHash(), CTxMemPoolEntry(tx4, 7000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx4)));
384+
pool.addUnchecked(tx5.GetHash(), CTxMemPoolEntry(tx5, 1000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx5)));
385+
pool.addUnchecked(tx6.GetHash(), CTxMemPoolEntry(tx6, 1100LL, 0, 10.0, 1, pool.HasNoInputsOf(tx6)));
386+
pool.addUnchecked(tx7.GetHash(), CTxMemPoolEntry(tx7, 9000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx7)));
387+
388+
// we only require this remove, at max, 2 txn, because its not clear what we're really optimizing for aside from that
389+
pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
390+
BOOST_CHECK(pool.exists(tx4.GetHash()));
391+
BOOST_CHECK(pool.exists(tx6.GetHash()));
392+
BOOST_CHECK(!pool.exists(tx7.GetHash()));
393+
394+
if (!pool.exists(tx5.GetHash()))
395+
pool.addUnchecked(tx5.GetHash(), CTxMemPoolEntry(tx5, 1000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx5)));
396+
pool.addUnchecked(tx7.GetHash(), CTxMemPoolEntry(tx7, 9000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx7)));
397+
398+
pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
399+
BOOST_CHECK(pool.exists(tx4.GetHash()));
400+
BOOST_CHECK(!pool.exists(tx5.GetHash()));
401+
BOOST_CHECK(pool.exists(tx6.GetHash()));
402+
BOOST_CHECK(!pool.exists(tx7.GetHash()));
403+
404+
pool.addUnchecked(tx5.GetHash(), CTxMemPoolEntry(tx5, 1000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx5)));
405+
pool.addUnchecked(tx7.GetHash(), CTxMemPoolEntry(tx7, 9000LL, 0, 10.0, 1, pool.HasNoInputsOf(tx7)));
406+
407+
std::vector<CTransaction> vtx;
408+
std::list<CTransaction> conflicts;
409+
SetMockTime(42);
410+
SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE);
411+
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
412+
// ... we should keep the same min fee until we get a block
413+
pool.removeForBlock(vtx, 1, conflicts);
414+
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE);
415+
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), (maxFeeRateRemoved.GetFeePerK() + 1000)/2);
416+
// ... then feerate should drop 1/2 each halflife
417+
418+
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2);
419+
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), (maxFeeRateRemoved.GetFeePerK() + 1000)/4);
420+
// ... with a 1/2 halflife when mempool is < 1/2 its target size
421+
422+
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
423+
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), (maxFeeRateRemoved.GetFeePerK() + 1000)/8);
424+
// ... with a 1/4 halflife when mempool is < 1/4 its target size
425+
426+
SetMockTime(42 + 7*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
427+
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 1000);
428+
// ... but feerate should never drop below 1000
429+
430+
SetMockTime(42 + 8*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
431+
pool.GetMinFee(1);
432+
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 0);
433+
// ... unless it has gone all the way to 0 (after getting past 1000/2)
434+
435+
SetMockTime(0);
436+
}
437+
284438
BOOST_AUTO_TEST_SUITE_END()

src/txmempool.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,13 @@ class CTxMemPool
290290
mutable int64_t lastRollingFeeUpdate;
291291
mutable bool blockSinceLastRollingFeeBump;
292292
mutable double rollingMinimumFeeRate; //! minimum fee to get into the pool, decreases exponentially
293-
static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12;
294293

295294
void trackPackageRemoved(const CFeeRate& rate);
296295

297296
public:
297+
298+
static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; // public only for testing
299+
298300
typedef boost::multi_index_container<
299301
CTxMemPoolEntry,
300302
boost::multi_index::indexed_by<

0 commit comments

Comments
 (0)