Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mempool: evict expired transactions regardless of current mempool size #997

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions lib/mempool/mempool.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,6 @@ class Mempool extends EventEmitter {
limitSize(added) {
const maxSize = this.options.maxSize;

if (this.size <= maxSize)
return false;

const threshold = maxSize - (maxSize / 10);
const expiryTime = this.options.expiryTime;

Expand Down
176 changes: 176 additions & 0 deletions test/mempool-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const Witness = require('../lib/script/witness');
const MemWallet = require('./util/memwallet');
const BlockStore = require('../lib/blockstore/level');
const {BufferSet} = require('buffer-map');
const util = require('../lib/utils/util');

const ALL = Script.hashType.ALL;
const VERIFY_NONE = common.flags.VERIFY_NONE;
Expand Down Expand Up @@ -1055,4 +1056,179 @@ describe('Mempool', function() {
throw err;
});
});

describe('Mempool eviction', function () {
// Computed in advance with MempoolEntry.memUsage()
const txMemUsage = 2288;
// Should allow 9 transactions in mempool.
// The 10th transaction will push the mempool size over 100% of the limit.
// Mempool will then remove two transactions to get under 90% limit.
const maxSize = txMemUsage * 10 - 1;
// 1 hour
const expiryTime = 60 * 60;

const workers = new WorkerPool({
enabled: true,
size: 2
});

const blocks = new BlockStore({
memory: true
});

const chain = new Chain({
memory: true,
workers,
blocks
});

const mempool = new Mempool({
chain,
workers,
memory: true,
maxSize,
expiryTime
});

before(async () => {
await blocks.open();
await mempool.open();
await chain.open();
await workers.open();
});

after(async () => {
await workers.close();
await chain.close();
await mempool.close();
await blocks.close();
});

// Number of coins available in
// chaincoins (100k satoshi per coin).
const N = 100;
const chaincoins = new MemWallet();
const wallet = new MemWallet();

it('should create txs in chain', async () => {
const mtx = new MTX();
mtx.addInput(new Input());

for (let i = 0; i < N; i++) {
const addr = chaincoins.createReceive().getAddress();
mtx.addOutput(addr, 100000);
}

const cb = mtx.toTX();
const block = await getMockBlock(chain, [cb], false);
const entry = await chain.add(block, VERIFY_NONE);

await mempool._addBlock(entry, block.txs);

// Add 100 blocks so we don't get premature
// spend of coinbase.
for (let i = 0; i < 100; i++) {
const block = await getMockBlock(chain);
const entry = await chain.add(block, VERIFY_NONE);

await mempool._addBlock(entry, block.txs);
}

chaincoins.addTX(cb);
});

it('should limit mempool size', async () => {
let expectedSize = 0;

for (let i = 0; i < N; i++) {
// Spend a different coin each time to avoid exceeding max ancestors.
const coin = chaincoins.getCoins()[i];
const addr = wallet.createReceive().getAddress();

const mtx = new MTX();
mtx.addCoin(coin);
// Increment fee with each TX so oldest TX gets evicted first.
// Otherwise the new TX might be the one that gets evicted,
// resulting in a "mempool full" error instead.
mtx.addOutput(addr, 90000 - (10 * i));
chaincoins.sign(mtx);
const tx = mtx.toTX();

expectedSize += txMemUsage;

if (expectedSize < maxSize) {
await mempool.addTX(tx);
} else {
assert(i >= 9);
let evicted = false;
mempool.once('remove entry', () => {
evicted = true;
// We've exceeded the max size by 1 TX
// Mempool will remove 2 TXs to get below 90% limit.
expectedSize -= txMemUsage * 2;
});
await mempool.addTX(tx);
assert(evicted);
}
}
});

it('should evict old transactions', async () => {
// Clear mempool. Note that TXs in last test were not
// added to the wallet: we can re-spend those coins.
await mempool.reset();

let now = 0;
const original = util.now;
try {
util.now = () => {
return now;
};

// After we cross the expiry threshold, one TX at a time
// will start to expire, starting with the oldest.
const sent = [];
let evicted = 0;
mempool.on('remove entry', (entry) => {
const expected = sent.shift();
assert.bufferEqual(entry.tx.hash(), expected);
pinheadmz marked this conversation as resolved.
Show resolved Hide resolved
evicted++;
});

for (let i = 0; i < N; i++) {
// Spend a different coin each time to avoid exceeding max ancestors.
const coin = chaincoins.getCoins()[i];
const addr = wallet.createReceive().getAddress();

const mtx = new MTX();
mtx.addCoin(coin);
mtx.addOutput(addr, 90000);
chaincoins.sign(mtx);
const tx = mtx.toTX();

sent.push(tx.hash());

// mempool size is not a factor
assert(mempool.size + (txMemUsage * 2) < maxSize);

await mempool.addTX(tx);

// Time travel forward ten minutes
now += 60 * 10;

// The first 6 TXs are added without incident.
// After that, a virtual hour will have passed, and
// each new TX will trigger the eviction of one old TX.
if (i < 6) {
assert(mempool.map.size === i + 1);
} else {
assert(mempool.map.size === 6);
assert.strictEqual(evicted, (i + 1) - 6);
}
}
} finally {
util.now = original;
}
});
});
});