diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index dedfc683..72cd7edd 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -6,6 +6,7 @@ var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; +var request = require('request'); var bcoin = require('../bcoin'); var bn = require('bn.js'); @@ -95,42 +96,137 @@ Chain.prototype._init = function _init() { utils.debug('Chain is loading.'); - utils.nextTick(function() { - var count = self.db.count(); - var lastEntry; - var i = 1; + this.preload(function(start) { + utils.nextTick(function() { + var count = self.db.count(); + var i = start || 1; + var lastEntry; - function done(height) { - if (height != null) { - utils.debug( - 'Blockchain is corrupt after height %d. Resetting.', - height); - self.resetHeight(height); - } else { - utils.debug('Chain successfully loaded.'); + function done(height) { + if (height != null) { + utils.debug( + 'Blockchain is corrupt after height %d. Resetting.', + height); + self.resetHeight(height); + } else { + utils.debug('Chain successfully loaded.'); + } + self.loading = false; + self.emit('load'); } - self.loading = false; - self.emit('load'); + + (function next() { + if (i >= count) + return done(); + + self.db.getAsync(i, function(err, entry) { + if (err) + throw err; + + // Do some paranoid checks. + if (lastEntry && entry.prevBlock !== lastEntry.hash) + return done(Math.max(0, i - 2)); + + lastEntry = entry; + self._saveEntry(entry); + i += 1; + next(); + }); + })(); + }); + }); +}; + +Chain.prototype.preload = function preload(callback) { + var self = this; + var url = 'https://headers.electrum.org/blockchain_headers'; + var chainHeight, buf, height, stream; + + if (this.options.preload === false) + return callback(); + + utils.debug('Loading %s', url); + + stream = request.get(url); + chainHeight = this.db.count() - 1; + height = 0; + buf = { + data: [], + size: 0 + }; + + stream.on('response', function(res) { + if (res.statusCode >= 400) { + utils.debug('Could not load electrum headers'); + utils.debug('Status: %d', res.statusCode); + stream.destroy(); + return callback(); + } + }); + + stream.on('error', function(err) { + utils.debug('Could not load electrum headers'); + utils.debug(err.stack + ''); + self.resetHeight(Math.max(0, height - 2)); + return callback(Math.max(0, height - 2)); + }); + + stream.on('data', function(data) { + var blocks = []; + var need = 80 - buf.size; + var i, lastEntry; + + while (data.length >= need) { + buf.data.push(data.slice(0, need)); + blocks.push(Buffer.concat(buf.data)); + buf.data.length = 0; + buf.size = 0; + data = data.slice(need); + need = 80 - buf.size; } - (function next() { - if (i >= count) - return done(); + if (data.length > 0) { + assert(data.length < 80); + buf.data.push(data); + buf.size += data.length; + } - self.db.getAsync(i, function(err, entry) { - if (err) - throw err; + if (blocks.length === 0) + return; - // Do some paranoid checks. - if (lastEntry && entry.prevBlock !== lastEntry.hash) - return done(Math.max(0, i - 2)); + blocks.forEach(function(data) { + var entry = bcoin.chainblock.fromRaw(self, height, data); - self._saveEntry(entry); - lastEntry = entry; - i += 1; - next(); - }); - })(); + // Do some paranoid checks. + if (lastEntry && entry.prevBlock !== lastEntry.hash) { + utils.debug('Electrum headers are corrupt. Resetting.'); + stream.destroy(); + self.resetHeight(Math.max(0, height - 2)); + return callback(Math.max(0, height - 2)); + } + + lastEntry = entry; + + delete entry.chainwork; + entry.chainwork = entry.getChainwork(); + + // Skip the genesis block in case it ends up being corrupt + if (height === 0) { + height++; + return; + } + + self._saveEntry(entry, height > chainHeight); + + height++; + + if ((height + 1) % 50000 === 0) + utils.debug('Received %d headers from electrum.org.', height + 1); + }); + }); + + stream.on('end', function() { + return callback(null, height + 1); }); };