Merge pull request #81 from pnagurny/feature/blockchain-cache
Cache blockchain data for fast startup time
This commit is contained in:
commit
300086faec
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -38,7 +38,8 @@ var Index = {
|
|||
height: 'bh-', // bh-<hash> -> height (-1 means disconnected)
|
||||
tip: 'tip', // tip -> { hash: hex, height: int }, the latest tip
|
||||
work: 'wk-', // wk-<hash> -> amount of work for block
|
||||
header: 'header-' // header-<hash> -> JSON for block header
|
||||
header: 'header-', // header-<hash> -> 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;
|
||||
|
|
Loading…
Reference in New Issue