Skip to content

Commit

Permalink
mempool: replace by fee
Browse files Browse the repository at this point in the history
All the BIP125 rules have been implemented to reject invalid
replacement TXs. This is where the actual replacement happens,
adding the replacement TX and evicting all the conflicts and their
descendants.
  • Loading branch information
pinheadmz committed Jul 8, 2019
1 parent 733c536 commit ae3c68e
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 4 deletions.
20 changes: 18 additions & 2 deletions lib/mempool/mempool.js
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,20 @@ class Mempool extends EventEmitter {
const entry = MempoolEntry.fromTX(tx, view, height);

// Contextual verification.
await this.verify(entry, view);
const conflicts = await this.verify(entry, view);

// Remove conflicting TXs and their descendants
if (conflicts.length) {
assert(this.options.replaceByFee);

for (const conflict of conflicts) {
this.logger.debug(
'Replacing tx %h with %h',
conflict.tx.hash(),
tx.hash());
this.evictEntry(conflict);
}
}

// Add and index the entry.
await this.addEntry(entry, view);
Expand All @@ -862,10 +875,11 @@ class Mempool extends EventEmitter {

/**
* Verify a transaction with mempool standards.
* Returns an array of conflicting TXs
* @method
* @param {TX} tx
* @param {CoinView} view
* @returns {Promise}
* @returns {Promise} - Returns {@link MempoolEntry}[]
*/

async verify(entry, view) {
Expand Down Expand Up @@ -1061,6 +1075,8 @@ class Mempool extends EventEmitter {
assert(await this.verifyResult(tx, view, flags),
'BUG: Verify failed for mandatory but not standard.');
}

return conflicts;
}

/**
Expand Down
56 changes: 54 additions & 2 deletions test/mempool-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,20 @@ describe('Mempool', function() {
type: 'VerifyError',
reason: 'insufficient fee'
});

// Try again with higher fee
const mtx3 = new MTX();
mtx3.addCoin(coin);
mtx3.addOutput(addr2, coin.value - 1200); // 1200 satoshi fee
chaincoins.sign(mtx3);
assert(mtx3.verify());
const tx3 = mtx3.toTX();

await mempool.addTX(tx3);

// tx1 has been replaced by tx3
assert(!mempool.has(tx1.hash()));
assert(mempool.has(tx3.hash()));
});

it('should reject replacement that doesnt pay all child fees', async() => {
Expand All @@ -1256,6 +1270,7 @@ describe('Mempool', function() {
let coin = originalCoin;

// Generate chain of 10 transactions, each paying 1000 sat fee
const childHashes = [];
for (let i = 0; i < 10; i++) {
const mtx = new MTX();
mtx.addCoin(coin);
Expand All @@ -1266,6 +1281,8 @@ describe('Mempool', function() {
const tx = mtx.toTX();
await mempool.addTX(tx);

childHashes.push(tx.hash());

coin = Coin.fromTX(tx, 0, -1);
}

Expand All @@ -1289,6 +1306,21 @@ describe('Mempool', function() {
type: 'VerifyError',
reason: 'insufficient fee'
});

// Try again with higher fee
const mtx3 = new MTX();
mtx3.addCoin(originalCoin);
mtx3.addOutput(addr2, originalCoin.value - fee);
chaincoins.sign(mtx3);
assert(mtx3.verify());
const tx3 = mtx3.toTX();

await mempool.addTX(tx3);

// All child TXs have been replaced by tx3
for (const hash of childHashes)
assert(!mempool.has(hash));
assert(mempool.has(tx3.hash()));
});

it('should reject replacement including new unconfirmed UTXO', async() => {
Expand Down Expand Up @@ -1365,21 +1397,28 @@ describe('Mempool', function() {
await mempool.addTX(tx1);

// Spend each of those outputs individually
let tx;
const hashes = [];
for (let i = 0; i < 100; i++) {
const mtx = new MTX();
const coin = Coin.fromTX(tx1, i, -1);
mtx.addCoin(coin);
mtx.addOutput(addr1, coin.value - 1000);
chaincoins.sign(mtx);
assert(mtx.verify());
const tx = mtx.toTX();
tx = mtx.toTX();

hashes.push(tx.hash());

await mempool.addTX(tx);
}

// Attempt to evict the whole batch by replacing the first TX (tx1)
const mtx2 = new MTX();
mtx2.addCoin(coin0);
mtx2.addOutput(addr1, coin0.value - 1000);
mtx2.addCoin(coin1);
// Send with massive fee to pay for 100 evicted TXs
mtx2.addOutput(addr1, 5000);
chaincoins.sign(mtx2);
assert(mtx2.verify());
const tx2 = mtx2.toTX();
Expand All @@ -1390,6 +1429,19 @@ describe('Mempool', function() {
type: 'VerifyError',
reason: 'too many potential replacements'
});

// Manually remove one of the descendants in advance
const entry = mempool.getEntry(tx.hash());
mempool.evictEntry(entry);

// Send back the same TX
await mempool.addTX(tx2);

// Entire mess has been replaced by tx2
assert(mempool.has(tx2.hash()));
assert(!mempool.has(tx1.hash()));
for (const hash of hashes)
assert(!mempool.has(hash));
});
});
});

0 comments on commit ae3c68e

Please sign in to comment.