diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index d5094285..d19a6301 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -199,7 +199,7 @@ Chain.prototype._init = function _init() { if (err) return self.emit('error', err); - self._preload(function(err) { + self.preload(function(err) { if (err) return self.emit('error', err); @@ -219,7 +219,7 @@ Chain.prototype._init = function _init() { bcoin.profiler.snapshot(); - self._getInitialState(function(err) { + self.getInitialState(function(err) { if (err) return self.emit('error', err); @@ -268,6 +268,12 @@ Chain.prototype.destroy = function destroy(callback) { this.db.close(utils.ensure(callback)); }; +/** + * Invoke mutex lock. + * @private + * @returns {Function} unlock + */ + Chain.prototype._lock = function _lock(func, args, force) { return this.locker.lock(func, args, force); }; @@ -282,13 +288,13 @@ Chain.prototype._lock = function _lock(func, args, force) { * @param {Function} callback */ -Chain.prototype._preload = function _preload(callback) { +Chain.prototype.preload = function preload(callback) { var self = this; var url = 'https://headers.electrum.org/blockchain_headers'; - var buf, height, stream; var request = require('./http/request'); var locker = new bcoin.locker(); var flushed = 0; + var chunks, size, height, stream, ended; if (!this.options.preload) return callback(); @@ -301,21 +307,6 @@ Chain.prototype._preload = function _preload(callback) { bcoin.debug('Loading %s.', url); - function parseHeader(data) { - var p = bcoin.reader(data, true); - var hash = utils.hash256(p.readBytes(80)).toString('hex'); - p.seek(-80); - return { - hash: hash, - version: p.readU32(), // Technically signed - prevBlock: p.readHash('hex'), - merkleRoot: p.readHash('hex'), - ts: p.readU32(), - bits: p.readU32(), - nonce: p.readU32() - }; - } - function save(entry, header) { var unlock = locker.lock(save, [entry]); if (!unlock) @@ -331,7 +322,7 @@ Chain.prototype._preload = function _preload(callback) { if ((++flushed % 50000) === 0) utils.print('Flushed %d headers to DB.', flushed); - if (locker.jobs.length === 0 && save.ended) + if (locker.jobs.length === 0 && ended) return callback(); unlock(); @@ -344,13 +335,11 @@ Chain.prototype._preload = function _preload(callback) { stream = request({ method: 'GET', uri: url }); height = 0; - buf = { - data: [], - size: 0 - }; + chunks = []; + size = 0; stream.on('response', function(res) { - var height = +res.headers['content-length'] / 80 | 0; + var height = Math.floor(res.headers['content-length'] / 80); if (res.statusCode >= 400) { stream.destroy(); return callback(new Error('Bad response code: ' + res.statusCode)); @@ -369,60 +358,54 @@ Chain.prototype._preload = function _preload(callback) { stream.on('data', function(data) { var blocks = []; - var need = 80 - buf.size; + var need = 80 - size; var i, lastEntry, block, entry; while (data.length >= need) { - buf.data.push(data.slice(0, need)); - blocks.push(Buffer.concat(buf.data)); - buf.data.length = 0; - buf.size = 0; + chunks.push(data.slice(0, need)); + blocks.push(Buffer.concat(chunks)); + chunks.length = 0; data = data.slice(need); - need = 80 - buf.size; + need = 80; } if (data.length > 0) { assert(data.length < 80); - buf.data.push(data); - buf.size += data.length; + chunks.push(data); + size += data.length; } for (i = 0; i < blocks.length; i++) { - data = blocks[i]; + block = blocks[i]; try { - data = parseHeader(data); + block = bcoin.headers.fromAbbr(block); } catch (e) { stream.destroy(); return callback(e); } - data.height = height; + block.setHeight(height); // Make sure the genesis block is correct. - if (data.height === 0 && data.hash !== self.network.genesis.hash) { + if (block.height === 0 && !self.isGenesis(block)) { stream.destroy(); return callback(new Error('Bad genesis block.')); } // Do some paranoid checks. - if (lastEntry && data.prevBlock !== lastEntry.hash) { + if (lastEntry && block.prevBlock !== lastEntry.hash) { stream.destroy(); return callback(new Error('Corrupt headers.')); } - // Create headers object for validation. - block = new bcoin.headers(data); - // Verify the block headers. We don't want to // trust an external centralized source completely. - if (!block.verifyHeaders()) { + if (!block.verify()) { stream.destroy(); return callback(new Error('Bad headers.')); } - block.setHeight(height); - // Create a chain entry. entry = bcoin.chainentry.fromBlock(self, block, lastEntry); @@ -438,7 +421,7 @@ Chain.prototype._preload = function _preload(callback) { }); stream.on('end', function() { - save.ended = true; + ended = true; if (!locker.busy && locker.jobs.length === 0) return callback(); }); @@ -453,18 +436,18 @@ Chain.prototype._preload = function _preload(callback) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) { +Chain.prototype.verifyContext = function verifyContext(block, prev, callback) { var self = this; - this._verify(block, prev, function(err, state) { + this.verify(block, prev, function(err, state) { if (err) return callback(err); - self._checkDuplicates(block, prev, function(err) { + self.checkDuplicates(block, prev, function(err) { if (err) return callback(err); - self._checkInputs(block, prev, state, function(err, view) { + self.checkInputs(block, prev, state, function(err, view) { if (err) return callback(err); @@ -474,6 +457,12 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) }); }; +/** + * Test whether a block is the genesis block. + * @param {Block} block + * @returns {Boolean} + */ + Chain.prototype.isGenesis = function isGenesis(block) { return block.hash('hex') === this.network.genesis.hash; }; @@ -489,7 +478,7 @@ Chain.prototype.isGenesis = function isGenesis(block) { * [{@link VerifyError}, {@link VerifyFlags}]. */ -Chain.prototype._verify = function _verify(block, prev, callback) { +Chain.prototype.verify = function verify(block, prev, callback) { var self = this; var ret = {}; var height, ts, i, tx, medianTime, commitmentHash; @@ -731,7 +720,7 @@ Chain.prototype.getDeployments = function getDeployments(block, prev, ancestors, * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) { +Chain.prototype.checkDuplicates = function checkDuplicates(block, prev, callback) { var self = this; var height = prev.height + 1; @@ -743,7 +732,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba if (this.network.block.bip34height === -1 || height <= this.network.block.bip34height) { - return this._findDuplicates(block, prev, callback); + return this.findDuplicates(block, prev, callback); } this.db.get(this.network.block.bip34height, function(err, entry) { @@ -756,7 +745,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba if (entry && entry.hash === self.network.block.bip34hash) return callback(); - return self._findDuplicates(block, prev, callback); + return self.findDuplicates(block, prev, callback); }); }; @@ -771,7 +760,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype._findDuplicates = function _findDuplicates(block, prev, callback) { +Chain.prototype.findDuplicates = function findDuplicates(block, prev, callback) { var self = this; var height = prev.height + 1; @@ -815,7 +804,7 @@ Chain.prototype._findDuplicates = function _findDuplicates(block, prev, callback * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype._checkInputs = function _checkInputs(block, prev, state, callback) { +Chain.prototype.checkInputs = function checkInputs(block, prev, state, callback) { var self = this; var height = prev.height + 1; var scriptCheck = true; @@ -927,6 +916,13 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, state, callbac }); }; +/** + * Get the cached height for a hash if present. + * @private + * @param {Hash} hash + * @returns {Number} + */ + Chain.prototype._getCachedHeight = function _getCachedHeight(hash) { if (this.db.hasCache(hash)) return this.db.getCache(hash).height; @@ -942,7 +938,7 @@ Chain.prototype._getCachedHeight = function _getCachedHeight(hash) { * @param {Function} callback - Returns [{@link Error}, {@link ChainEntry}]. */ -Chain.prototype._findFork = function _findFork(fork, longer, callback) { +Chain.prototype.findFork = function findFork(fork, longer, callback) { (function find() { if (fork.hash === longer.hash) return callback(null, fork); @@ -993,11 +989,11 @@ Chain.prototype._findFork = function _findFork(fork, longer, callback) { * @param {Function} callback */ -Chain.prototype._reorganize = function _reorganize(entry, block, callback) { +Chain.prototype.reorganize = function reorganize(entry, block, callback) { var self = this; var tip = this.tip; - return this._findFork(tip, entry, function(err, fork) { + return this.findFork(tip, entry, function(err, fork) { if (err) return callback(err); @@ -1140,7 +1136,7 @@ Chain.prototype.connect = function connect(entry, callback) { assert(prev); - self._verifyContext(block, prev, function(err, view) { + self.verifyContext(block, prev, function(err, view) { if (err) { if (err.type === 'VerifyError') { self.invalid[entry.hash] = true; @@ -1185,7 +1181,7 @@ Chain.prototype.connect = function connect(entry, callback) { * @param {Function} callback - Returns [{@link VerifyError}]. */ -Chain.prototype._setBestChain = function _setBestChain(entry, block, prev, callback) { +Chain.prototype.setBestChain = function setBestChain(entry, block, prev, callback) { var self = this; function done(err) { @@ -1195,7 +1191,7 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, prev, callb // Do "contextual" verification on our block // now that we're certain its previous // block is in the chain. - self._verifyContext(block, prev, function(err, view) { + self.verifyContext(block, prev, function(err, view) { if (err) { // Couldn't verify block. // Revert the height. @@ -1248,7 +1244,7 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, prev, callb // A higher fork has arrived. // Time to reorganize the chain. bcoin.debug('WARNING: Reorganizing chain.'); - return this._reorganize(entry, block, done); + return this.reorganize(entry, block, done); }; /** @@ -1574,7 +1570,7 @@ Chain.prototype.add = function add(block, callback, force) { } // Attempt to add block to the chain index. - self._setBestChain(entry, block, prev, function(err) { + self.setBestChain(entry, block, prev, function(err) { if (err) return done(err); @@ -2314,14 +2310,12 @@ Chain.prototype.computeBlockVersion = function computeBlockVersion(prev, callbac }; /** - * A helper function to test whether segwit is active at any - * given time. Since segwit affects almost all of bitcoin, it - * is one deployment that needs to be checked frequently. + * Get the initial deployment state of the chain. Called on load. * @private - * @param {Function} callback - Returns [Error, Boolean]. + * @param {Function} callback - Returns [Error, {@link DeploymentState}]. */ -Chain.prototype._getInitialState = function _getInitialState(callback) { +Chain.prototype.getInitialState = function getInitialState(callback) { var self = this; if (this.segwitActive != null) @@ -2330,7 +2324,7 @@ Chain.prototype._getInitialState = function _getInitialState(callback) { if (!this.tip) return utils.nextTick(callback); - return this.tip.getPrevious(function(err, prev) { + this.tip.getPrevious(function(err, prev) { if (err) return callback(err); @@ -2351,7 +2345,7 @@ Chain.prototype._getInitialState = function _getInitialState(callback) { self.csvActive = state.hasCSV(); self.segwitActive = state.hasWitness(); - return callback(); + return callback(null, state); }); }); }); diff --git a/lib/bcoin/headers.js b/lib/bcoin/headers.js index d6959ba3..eea9e1b3 100644 --- a/lib/bcoin/headers.js +++ b/lib/bcoin/headers.js @@ -129,6 +129,38 @@ Headers.fromRaw = function fromRaw(data, enc) { return new Headers().fromRaw(data); }; +/** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + +Headers.prototype.fromAbbr = function fromAbbr(data) { + var p = bcoin.reader(data); + + this.version = p.readU32(); // Technically signed + this.prevBlock = p.readHash('hex'); + this.merkleRoot = p.readHash('hex'); + this.ts = p.readU32(); + this.bits = p.readU32(); + this.nonce = p.readU32(); + + return this; +}; + +/** + * Instantiate headers from serialized data. + * @param {Buffer} data + * @param {String?} enc - Encoding, can be `'hex'` or null. + * @returns {Headers} + */ + +Headers.fromAbbr = function fromAbbr(data, enc) { + if (typeof data === 'string') + data = new Buffer(data, enc); + return new Headers().fromAbbr(data); +}; + /** * Instantiate headers from a chain entry. * @param {ChainEntry} entry