From b68207610c57139d409ca7958f9c8386f9b8002e Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 1 May 2019 16:20:33 -0700 Subject: [PATCH] blockchain: do not accept forked chain older than last checkpoint This has been patched in both bitcoind and btcd around February 20th, 2014. It was shortly followed by a headers-first synchronization of blocks. - https://github.com/bitcoin/bitcoin/commit/d8b4b49667f3eaf5ac16c218aaba2136ece907d8 - https://github.com/btcsuite/btcd/commit/50b6e10b5781772b0f6a506535f575d81a95dc7a --- lib/blockchain/chain.js | 10 ++++++++ test/chain-test.js | 53 +++++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 7 deletions(-) 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)); + }); }); });