diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 7e94c6a7..93271b0e 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -1104,6 +1104,16 @@ class Chain extends AsyncEmitter { */ async saveAlternate(entry, block, prev, flags) { + // Do not accept forked chain older than the + // last checkpoint. + if (this.options.checkpoints) { + if (prev.height + 1 < this.network.lastCheckpoint) + throw new VerifyError(block, + 'checkpoint', + 'bad-fork-prior-to-checkpoint', + 100); + } + try { // Do as much verification // as we can before saving. diff --git a/test/chain-test.js b/test/chain-test.js index 9c915031..45cfbd10 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -121,17 +121,21 @@ chain.on('disconnect', (entry, block) => { describe('Chain', function() { this.timeout(process.browser ? 1200000 : 60000); - it('should open chain and miner', async () => { + before(async () => { await blocks.open(); await chain.open(); await miner.open(); - }); - it('should add addrs to miner', async () => { miner.addresses.length = 0; miner.addAddress(wallet.getReceive()); }); + after(async () => { + await miner.close(); + await chain.close(); + await blocks.close(); + }); + it('should mine 200 blocks', async () => { for (let i = 0; i < 200; i++) { const block = await cpu.mineBlock(); @@ -900,9 +904,44 @@ describe('Chain', function() { assert(fmt.includes('chainwork')); }); - it('should cleanup', async () => { - await miner.close(); - await chain.close(); - await blocks.close(); + describe('Checkpoints', function() { + before(async () => { + const entry = await chain.getEntry(chain.tip.height - 5); + assert(Buffer.isBuffer(entry.hash)); + assert(Number.isInteger(entry.height)); + + network.checkpointMap[entry.height] = entry.hash; + network.lastCheckpoint = entry.height; + }); + + after(async () => { + network.checkpointMap = {}; + network.lastCheckpoint = 0; + }); + + it('will reject blocks before last checkpoint', async () => { + const entry = await chain.getEntry(chain.tip.height - 10); + const block = await cpu.mineBlock(entry); + + let err = null; + + try { + await chain.add(block); + } catch (e) { + err = e; + } + + assert(err); + assert.equal(err.type, 'VerifyError'); + assert.equal(err.reason, 'bad-fork-prior-to-checkpoint'); + assert.equal(err.score, 100); + }); + + it('will accept blocks after last checkpoint', async () => { + const entry = await chain.getEntry(chain.tip.height - 4); + const block = await cpu.mineBlock(entry); + + assert(await chain.add(block)); + }); }); });