diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index e2d705a1..26c05dcd 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -295,7 +295,7 @@ class Mempool extends EventEmitter { // when the block one before the height that disables // GooSig is added to the mempool to prevent the // mining of invalid blocks. - if (block.height + 1 === this.network.goosigStop) { + if (nextHeight === this.network.goosigStop) { for (const [hash, entry] of this.airdrops.entries()) { const airdrop = this.getAirdrop(hash); const key = airdrop.getKey(); diff --git a/test/mempool-invalidation-test.js b/test/mempool-invalidation-test.js index 9e45da13..eaafb312 100644 --- a/test/mempool-invalidation-test.js +++ b/test/mempool-invalidation-test.js @@ -87,12 +87,12 @@ describe('Mempool Invalidation', function() { assert.strictEqual(node.mempool.claims.size, 1); // retain claim in mempool. - block = await mineBlock(node, { ignoreClaims: true }); + [block] = await mineBlock(node, { ignoreClaims: true }); assert.strictEqual(node.mempool.claims.size, 1); assert.strictEqual(block.txs[0].outputs.length, 1); // Now we can mine it. - block = await mineBlock(node); + [block] = await mineBlock(node); assert.strictEqual(node.mempool.claims.size, 0); assert.strictEqual(block.txs[0].outputs.length, 2); assert.strictEqual(block.txs[0].outputs[1].covenant.type, rules.types.CLAIM); @@ -110,7 +110,7 @@ describe('Mempool Invalidation', function() { await mineBlock(node); await node.mempool.insertClaim(claim); - block = await mineBlock(node, { ignoreClaims: true }); + [block] = await mineBlock(node, { ignoreClaims: true }); // Should invalidate the claim, because next block can't have claims. assert.strictEqual(node.mempool.claims.size, 0); @@ -128,7 +128,127 @@ describe('Mempool Invalidation', function() { assert.strictEqual(err.type, 'VerifyError'); assert.strictEqual(err.reason, 'invalid-covenant'); - block = await mineBlock(node); + [block] = await mineBlock(node); + assert.strictEqual(node.mempool.claims.size, 0); + assert.strictEqual(block.txs[0].outputs.length, 1); + }); + }); + + describe('Claim Invalidation on reorg (Integration)', function() { + this.timeout(50000); + + let node, wallet; + + // copy names + const TEST_CLAIMS = NAMES.slice(); + + before(async () => { + node = new FullNode({ + memory: true, + network: network.type, + plugins: [require('../lib/wallet/plugin')] + }); + + await node.ensure(); + await node.open(); + + // Ignore claim validation + ownership.ignore = true; + + const walletPlugin = node.require('walletdb'); + const wdb = walletPlugin.wdb; + wallet = await wdb.get('primary'); + + const addr = await wallet.receiveAddress('default'); + node.miner.addAddress(addr.toString()); + + // first interval maturity + // second interval mine claim + network.names.claimPeriod = treeInterval * 3; + + // third interval last block should invalidate. + }); + + after(async () => { + network.names.claimPeriod = ACTUAL_CLAIM_PERIOD; + + await node.close(); + }); + + it('should mine an interval', async () => { + for (let i = 0; i < treeInterval; i++) + await mineBlock(node); + }); + + it('should mine claims before claimPeriod timeout', async () => { + const name = TEST_CLAIMS.shift(); + + const claim = await wallet.makeFakeClaim(name); + let block; + + await node.mempool.insertClaim(claim); + assert.strictEqual(node.mempool.claims.size, 1); + + // retain claim in mempool. + [block] = await mineBlock(node, { ignoreClaims: true }); + assert.strictEqual(node.mempool.claims.size, 1); + assert.strictEqual(block.txs[0].outputs.length, 1); + + // Now we can mine it. + [block] = await mineBlock(node); + assert.strictEqual(node.mempool.claims.size, 0); + assert.strictEqual(block.txs[0].outputs.length, 2); + assert.strictEqual(block.txs[0].outputs[1].covenant.type, rules.types.CLAIM); + }); + + it('should invalidate claim after claimPeriod timeout', async () => { + const name = TEST_CLAIMS.shift(); + const claim = await wallet.makeFakeClaim(name); + + let block, entry; + + // Mempool treats txs in it as if they were mined in the next block, + // so we need next block to still be valid. + while (node.chain.tip.height < network.names.claimPeriod - 2) + await mineBlock(node); + + await node.mempool.insertClaim(claim); + // here we experience a reorg into the claim period. + const tip = node.chain.tip; + const prev = await node.chain.getPrevious(tip); + + [block, entry] = await mineBlock(node, { + ignoreClaims: true, + tip: prev, + blockWait: false + }); + + assert.strictEqual(node.mempool.claims.size, 1); + assert.strictEqual(block.txs[0].outputs.length, 1); + + // Now reorg. + [block, entry] = await mineBlock(node, { + ignoreClaims: true, + tip: entry + }); + + // Should invalidate the claim, because next block can't have claims. + assert.strictEqual(node.mempool.claims.size, 0); + assert.strictEqual(block.txs[0].outputs.length, 1); + + // Should fail to insert claim, as they can't be mined. + let err; + try { + err = await node.mempool.insertClaim(claim); + } catch (e) { + err = e; + } + + assert(err); + assert.strictEqual(err.type, 'VerifyError'); + assert.strictEqual(err.reason, 'invalid-covenant'); + + [block] = await mineBlock(node); assert.strictEqual(node.mempool.claims.size, 0); assert.strictEqual(block.txs[0].outputs.length, 1); }); @@ -140,9 +260,14 @@ async function mineBlock(node, opts = {}) { const chain = node.chain; const miner = node.miner; - const ignoreClaims = opts.ignoreClaims || false; + const ignoreClaims = opts.ignoreClaims ?? false; + const tip = opts.tip || chain.tip; + const blockWait = opts.blockWait ?? true; - const forBlock = forEvent(node, 'block', 1, 2000); + let forBlock = null; + + if (blockWait) + forBlock = forEvent(node, 'block', 1, 2000); let backupClaims = null; @@ -150,7 +275,7 @@ async function mineBlock(node, opts = {}) { backupClaims = node.mempool.claims; node.mempool.claims = new BufferMap(); } - const job = await miner.cpu.createJob(chain.tip); + const job = await miner.cpu.createJob(tip); job.refresh(); @@ -158,8 +283,10 @@ async function mineBlock(node, opts = {}) { node.mempool.claims = backupClaims; const block = await job.mineAsync(); - await chain.add(block); - await forBlock; + const entry = await chain.add(block); - return block; + if (blockWait) + await forBlock; + + return [block, entry]; }