From ec537432d1f3671534e89287b9da362d1e2fba64 Mon Sep 17 00:00:00 2001 From: Anunay Jain Date: Mon, 28 Feb 2022 15:10:31 +0530 Subject: [PATCH 1/2] Fix CPFP when tx fee is less than MIN_RELAY Mempool inclusion policy does not consider descendant fee when checking transaction for minimum fee, this would allow you to incentivize the miner to include your transaction without having to wait for a block with low weight or your transaction to get evicted. --- lib/mining/template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mining/template.js b/lib/mining/template.js index 57bd8033d..eaa475abc 100644 --- a/lib/mining/template.js +++ b/lib/mining/template.js @@ -654,7 +654,7 @@ class BlockEntry { item.fee = entry.getFee(); item.rate = entry.getDeltaRate(); item.priority = entry.getPriority(attempt.height); - item.free = entry.getDeltaFee() < policy.getMinFee(entry.size); + item.free = entry.descFee < policy.getMinFee(entry.descSize); item.sigops = entry.sigops; item.descRate = entry.getDescRate(); return item; From 28b1486680cda7a4e0c68a76a82c8d4110abf076 Mon Sep 17 00:00:00 2001 From: Anunay Jain Date: Mon, 28 Feb 2022 15:17:28 +0530 Subject: [PATCH 2/2] test: Child pays for parent --- test/miner-test.js | 80 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/test/miner-test.js b/test/miner-test.js index 40afc8aa0..d7e6a98e9 100644 --- a/test/miner-test.js +++ b/test/miner-test.js @@ -9,6 +9,7 @@ const Mempool = require('../lib/mempool/mempool'); const Miner = require('../lib/mining/miner'); const Address = require('../lib/primitives/address'); const MTX = require('../lib/primitives/mtx'); +const Coin = require('../lib/primitives/coin'); const MemWallet = require('./util/memwallet'); const {BufferSet} = require('buffer-map'); @@ -77,6 +78,7 @@ describe('Miner', function() { let walletAddr; const txids = new BufferSet(); + let coin, parentTX; it('should generate 20 blocks to wallet address', async () => { walletAddr = wallet.createReceive().getAddress(); @@ -110,7 +112,6 @@ describe('Miner', function() { } assert.strictEqual(mempool.map.size, 10); - const block = await miner.mineBlock(chain.tip, addr); await chain.add(block); @@ -180,4 +181,81 @@ describe('Miner', function() { assert(txids.has(block.txs[i].hash())); } }); + + it('should not include free transaction in a block', async () => { + // Miner does not have any room for free TXs + miner.options.minWeight = 0; + + const value = 1 * 1e6; + const fee = 0; + + // Get a change address + const change = wallet.createChange().getAddress(); + const mtx = new MTX(); + coin = wallet.getCoins()[0]; + mtx.addCoin(coin); + mtx.addOutput(addr, value); + mtx.addOutput(change, coin.value - value - fee); // no fee + wallet.sign(mtx); + parentTX = mtx.toTX(); + + await mempool.addTX(parentTX, -1); + assert.strictEqual(mempool.map.size, 1); + + const block = await miner.mineBlock(chain.tip, addr); + await chain.add(block); + + // TX is still in mempool, nothing in block except coinbase + assert.strictEqual(mempool.map.size, 1); + assert.strictEqual(block.txs.length, 1); + }); + + it('should fail to double spend the coin - duplicate tx in mempool', async () => { + const value = 1 * 1e6; + const fee = 1000; + const mtx = new MTX(); + + const change = wallet.createChange().getAddress(); + mtx.addCoin(coin); + mtx.addOutput(addr, value); + mtx.addOutput(change, coin.value - value - fee); + wallet.sign(mtx); + const tx = mtx.toTX(); + + assert.rejects( + async () => await mempool.addTX(tx, -1), + { + code: 'duplicate', + reason: 'bad-txns-inputs-spent' + } + ); + // orignal tx is still in mempool + assert.strictEqual(mempool.map.size, 1); + }); + + it('should include child transaction if child pays enough fee (CPFP)', async () => { + const fee = 1000; + + // Fee should be enough for both the first transation and second transaction + assert(fee > 140 + 108); + + const mtx = new MTX(); + const change = wallet.createChange().getAddress(); + const coin = Coin.fromTX(parentTX, 1, -1); + + mtx.addCoin(coin); + mtx.addOutput(change, coin.value - fee); + wallet.sign(mtx); + const tx = mtx.toTX(); + await mempool.addTX(tx, -1); + // Both transactions in mempool + assert.strictEqual(mempool.map.size, 2); + + const block = await miner.mineBlock(chain.tip, addr); + await chain.add(block); + + // Both transactions should get mined into the block + assert.strictEqual(mempool.map.size, 0); + assert.strictEqual(block.txs.length, 3); + }); });