diff --git a/lib/blockchain.js b/lib/blockchain.js index 2ba21d43..a792073d 100644 --- a/lib/blockchain.js +++ b/lib/blockchain.js @@ -18,6 +18,7 @@ function BlockChain() { }; this.next = {}; this.prev = {}; + this.cache = []; } BlockChain.NULL = NULL; @@ -112,6 +113,7 @@ BlockChain.prototype.confirm = function(hash) { this.next[prevHash] = hash; this.hashByHeight[height] = hash; this.height[hash] = height; + this.cache.push(this.getCachedDataForHash(hash)); }; BlockChain.prototype.unconfirm = function(hash) { @@ -123,6 +125,7 @@ BlockChain.prototype.unconfirm = function(hash) { delete this.next[prevHash]; delete this.hashByHeight[height]; delete this.height[hash]; + this.cache.pop(); }; BlockChain.prototype.hasData = function(hash) { @@ -178,4 +181,24 @@ BlockChain.prototype.getCurrentHeight = function() { return this.height[this.tip]; }; +BlockChain.prototype.loadFromCache = function(cache) { + for(var i = 0; i < cache.length; i++) { + this.prev[cache[i].hash] = cache[i].prevHash; + this.work[cache[i].hash] = cache[i].work; + this.confirm(cache[i].hash); + } +}; + +BlockChain.prototype.getCache = function() { + return this.cache; +}; + +BlockChain.prototype.getCachedDataForHash = function(hash) { + return { + hash: hash, + prevHash: this.prev[hash], + work: this.work[hash] + }; +}; + module.exports = BlockChain; diff --git a/lib/node.js b/lib/node.js index 41445051..a4019db3 100644 --- a/lib/node.js +++ b/lib/node.js @@ -128,6 +128,12 @@ BitcoreNode.prototype.initialize = function() { delete self.blockCache[deleteHash]; } }) + .then(function() { + // Update header cache every 100 blocks + if(block.height % 100 === 0) { + return self.blockService.saveBlockchainCache(self.blockchain.getCache()); + } + }) .catch(function(error) { self.stop(error); }); diff --git a/lib/services/block.js b/lib/services/block.js index 0032897e..b676675c 100644 --- a/lib/services/block.js +++ b/lib/services/block.js @@ -38,7 +38,8 @@ var Index = { height: 'bh-', // bh- -> height (-1 means disconnected) tip: 'tip', // tip -> { hash: hex, height: int }, the latest tip work: 'wk-', // wk- -> amount of work for block - header: 'header-' // header- -> JSON for block header + header: 'header-', // header- -> JSON for block header + blockchain: 'bc-' }; _.extend(Index, { getNextBlock: helper(Index.next), @@ -466,39 +467,61 @@ BlockService.prototype.getBlockchain = function() { var blockchain = new BlockChain(); var headers = []; - console.log('Fetching headers from db...'); - var fetchHeader = function(blockHash) { - if (blockHash === BlockChain.NULL) { - console.log('All headers fetched, total =', headers.length); - return; - } - var headerKey = Index.getBlockHeader(blockHash); - return self.database.getAsync(headerKey) - .then(function(json) { - return bitcore.Block.BlockHeader.fromJSON(json); - }) - .then(function(header) { - headers.push(header); - return fetchHeader(BufferUtil.reverse(header.prevHash).toString('hex')); - }); - }; + console.log('Fetching hashes from db...'); - return self._getLatestHash() - .then(function(tip) { - if (!tip) { - console.log('No tip found, syncing blockchain from genesis block'); - return null; + return self.database.getAsync(Index.blockchain, {valueEncoding: 'json'}) + .catch(function(err) { + if(err instanceof LevelUp.errors.NotFoundError) { + return []; } - console.log('Tip is', tip); - return fetchHeader(tip) - .then(function() { - while (headers.length !== 0) { - var header = headers.pop(); - blockchain.proposeNewHeader(header); + throw err; + }) + .then(function(blockchainCache) { + console.log(blockchainCache.length + ' headers cached'); + + var fetchHeader = function(blockHash) { + if (blockHash === BlockChain.NULL) { + console.log('All headers fetched, total =', headers.length); + return Promise.resolve(); + } else if(blockchainCache.length && blockchainCache[blockchainCache.length - 1].hash === blockHash) { + console.log(headers.length + ' non-cached headers loaded'); + return Promise.resolve(); + } + var headerKey = Index.getBlockHeader(blockHash); + return self.database.getAsync(headerKey) + .then(function(json) { + return bitcore.Block.BlockHeader.fromJSON(json); + }) + .then(function(header) { + headers.push(header); + return fetchHeader(BufferUtil.reverse(header.prevHash).toString('hex')); + }); + }; + + return self._getLatestHash() + .then(function(tip) { + if (!tip) { + console.log('No tip found, syncing blockchain from genesis block'); + return null; } - return blockchain; - }); + console.log('Tip is', tip); + + return fetchHeader(tip) + .then(function() { + blockchain.loadFromCache(blockchainCache); + + while (headers.length !== 0) { + var header = headers.pop(); + blockchain.proposeNewHeader(header); + } + return blockchain; + }); + }) }); }; +BlockService.prototype.saveBlockchainCache = function(cache) { + return this.database.putAsync(Index.blockchain, JSON.stringify(cache)); +} + module.exports = BlockService;