more chain refactoring.

This commit is contained in:
Christopher Jeffrey 2016-06-30 12:54:52 -07:00
parent 1f3d2f4b96
commit c580557993
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 96 additions and 70 deletions

View File

@ -199,7 +199,7 @@ Chain.prototype._init = function _init() {
if (err) if (err)
return self.emit('error', err); return self.emit('error', err);
self._preload(function(err) { self.preload(function(err) {
if (err) if (err)
return self.emit('error', err); return self.emit('error', err);
@ -219,7 +219,7 @@ Chain.prototype._init = function _init() {
bcoin.profiler.snapshot(); bcoin.profiler.snapshot();
self._getInitialState(function(err) { self.getInitialState(function(err) {
if (err) if (err)
return self.emit('error', err); return self.emit('error', err);
@ -268,6 +268,12 @@ Chain.prototype.destroy = function destroy(callback) {
this.db.close(utils.ensure(callback)); this.db.close(utils.ensure(callback));
}; };
/**
* Invoke mutex lock.
* @private
* @returns {Function} unlock
*/
Chain.prototype._lock = function _lock(func, args, force) { Chain.prototype._lock = function _lock(func, args, force) {
return this.locker.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 * @param {Function} callback
*/ */
Chain.prototype._preload = function _preload(callback) { Chain.prototype.preload = function preload(callback) {
var self = this; var self = this;
var url = 'https://headers.electrum.org/blockchain_headers'; var url = 'https://headers.electrum.org/blockchain_headers';
var buf, height, stream;
var request = require('./http/request'); var request = require('./http/request');
var locker = new bcoin.locker(); var locker = new bcoin.locker();
var flushed = 0; var flushed = 0;
var chunks, size, height, stream, ended;
if (!this.options.preload) if (!this.options.preload)
return callback(); return callback();
@ -301,21 +307,6 @@ Chain.prototype._preload = function _preload(callback) {
bcoin.debug('Loading %s.', url); 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) { function save(entry, header) {
var unlock = locker.lock(save, [entry]); var unlock = locker.lock(save, [entry]);
if (!unlock) if (!unlock)
@ -331,7 +322,7 @@ Chain.prototype._preload = function _preload(callback) {
if ((++flushed % 50000) === 0) if ((++flushed % 50000) === 0)
utils.print('Flushed %d headers to DB.', flushed); utils.print('Flushed %d headers to DB.', flushed);
if (locker.jobs.length === 0 && save.ended) if (locker.jobs.length === 0 && ended)
return callback(); return callback();
unlock(); unlock();
@ -344,13 +335,11 @@ Chain.prototype._preload = function _preload(callback) {
stream = request({ method: 'GET', uri: url }); stream = request({ method: 'GET', uri: url });
height = 0; height = 0;
buf = { chunks = [];
data: [], size = 0;
size: 0
};
stream.on('response', function(res) { 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) { if (res.statusCode >= 400) {
stream.destroy(); stream.destroy();
return callback(new Error('Bad response code: ' + res.statusCode)); return callback(new Error('Bad response code: ' + res.statusCode));
@ -369,60 +358,54 @@ Chain.prototype._preload = function _preload(callback) {
stream.on('data', function(data) { stream.on('data', function(data) {
var blocks = []; var blocks = [];
var need = 80 - buf.size; var need = 80 - size;
var i, lastEntry, block, entry; var i, lastEntry, block, entry;
while (data.length >= need) { while (data.length >= need) {
buf.data.push(data.slice(0, need)); chunks.push(data.slice(0, need));
blocks.push(Buffer.concat(buf.data)); blocks.push(Buffer.concat(chunks));
buf.data.length = 0; chunks.length = 0;
buf.size = 0;
data = data.slice(need); data = data.slice(need);
need = 80 - buf.size; need = 80;
} }
if (data.length > 0) { if (data.length > 0) {
assert(data.length < 80); assert(data.length < 80);
buf.data.push(data); chunks.push(data);
buf.size += data.length; size += data.length;
} }
for (i = 0; i < blocks.length; i++) { for (i = 0; i < blocks.length; i++) {
data = blocks[i]; block = blocks[i];
try { try {
data = parseHeader(data); block = bcoin.headers.fromAbbr(block);
} catch (e) { } catch (e) {
stream.destroy(); stream.destroy();
return callback(e); return callback(e);
} }
data.height = height; block.setHeight(height);
// Make sure the genesis block is correct. // 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(); stream.destroy();
return callback(new Error('Bad genesis block.')); return callback(new Error('Bad genesis block.'));
} }
// Do some paranoid checks. // Do some paranoid checks.
if (lastEntry && data.prevBlock !== lastEntry.hash) { if (lastEntry && block.prevBlock !== lastEntry.hash) {
stream.destroy(); stream.destroy();
return callback(new Error('Corrupt headers.')); 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 // Verify the block headers. We don't want to
// trust an external centralized source completely. // trust an external centralized source completely.
if (!block.verifyHeaders()) { if (!block.verify()) {
stream.destroy(); stream.destroy();
return callback(new Error('Bad headers.')); return callback(new Error('Bad headers.'));
} }
block.setHeight(height);
// Create a chain entry. // Create a chain entry.
entry = bcoin.chainentry.fromBlock(self, block, lastEntry); entry = bcoin.chainentry.fromBlock(self, block, lastEntry);
@ -438,7 +421,7 @@ Chain.prototype._preload = function _preload(callback) {
}); });
stream.on('end', function() { stream.on('end', function() {
save.ended = true; ended = true;
if (!locker.busy && locker.jobs.length === 0) if (!locker.busy && locker.jobs.length === 0)
return callback(); return callback();
}); });
@ -453,18 +436,18 @@ Chain.prototype._preload = function _preload(callback) {
* @param {Function} callback - Returns [{@link VerifyError}]. * @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; var self = this;
this._verify(block, prev, function(err, state) { this.verify(block, prev, function(err, state) {
if (err) if (err)
return callback(err); return callback(err);
self._checkDuplicates(block, prev, function(err) { self.checkDuplicates(block, prev, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self._checkInputs(block, prev, state, function(err, view) { self.checkInputs(block, prev, state, function(err, view) {
if (err) if (err)
return callback(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) { Chain.prototype.isGenesis = function isGenesis(block) {
return block.hash('hex') === this.network.genesis.hash; return block.hash('hex') === this.network.genesis.hash;
}; };
@ -489,7 +478,7 @@ Chain.prototype.isGenesis = function isGenesis(block) {
* [{@link VerifyError}, {@link VerifyFlags}]. * [{@link VerifyError}, {@link VerifyFlags}].
*/ */
Chain.prototype._verify = function _verify(block, prev, callback) { Chain.prototype.verify = function verify(block, prev, callback) {
var self = this; var self = this;
var ret = {}; var ret = {};
var height, ts, i, tx, medianTime, commitmentHash; 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}]. * @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 self = this;
var height = prev.height + 1; var height = prev.height + 1;
@ -743,7 +732,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba
if (this.network.block.bip34height === -1 if (this.network.block.bip34height === -1
|| height <= this.network.block.bip34height) { || 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) { 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) if (entry && entry.hash === self.network.block.bip34hash)
return callback(); 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}]. * @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 self = this;
var height = prev.height + 1; var height = prev.height + 1;
@ -815,7 +804,7 @@ Chain.prototype._findDuplicates = function _findDuplicates(block, prev, callback
* @param {Function} callback - Returns [{@link VerifyError}]. * @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 self = this;
var height = prev.height + 1; var height = prev.height + 1;
var scriptCheck = true; 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) { Chain.prototype._getCachedHeight = function _getCachedHeight(hash) {
if (this.db.hasCache(hash)) if (this.db.hasCache(hash))
return this.db.getCache(hash).height; return this.db.getCache(hash).height;
@ -942,7 +938,7 @@ Chain.prototype._getCachedHeight = function _getCachedHeight(hash) {
* @param {Function} callback - Returns [{@link Error}, {@link ChainEntry}]. * @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() { (function find() {
if (fork.hash === longer.hash) if (fork.hash === longer.hash)
return callback(null, fork); return callback(null, fork);
@ -993,11 +989,11 @@ Chain.prototype._findFork = function _findFork(fork, longer, callback) {
* @param {Function} callback * @param {Function} callback
*/ */
Chain.prototype._reorganize = function _reorganize(entry, block, callback) { Chain.prototype.reorganize = function reorganize(entry, block, callback) {
var self = this; var self = this;
var tip = this.tip; var tip = this.tip;
return this._findFork(tip, entry, function(err, fork) { return this.findFork(tip, entry, function(err, fork) {
if (err) if (err)
return callback(err); return callback(err);
@ -1140,7 +1136,7 @@ Chain.prototype.connect = function connect(entry, callback) {
assert(prev); assert(prev);
self._verifyContext(block, prev, function(err, view) { self.verifyContext(block, prev, function(err, view) {
if (err) { if (err) {
if (err.type === 'VerifyError') { if (err.type === 'VerifyError') {
self.invalid[entry.hash] = true; self.invalid[entry.hash] = true;
@ -1185,7 +1181,7 @@ Chain.prototype.connect = function connect(entry, callback) {
* @param {Function} callback - Returns [{@link VerifyError}]. * @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; var self = this;
function done(err) { function done(err) {
@ -1195,7 +1191,7 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, prev, callb
// Do "contextual" verification on our block // Do "contextual" verification on our block
// now that we're certain its previous // now that we're certain its previous
// block is in the chain. // block is in the chain.
self._verifyContext(block, prev, function(err, view) { self.verifyContext(block, prev, function(err, view) {
if (err) { if (err) {
// Couldn't verify block. // Couldn't verify block.
// Revert the height. // Revert the height.
@ -1248,7 +1244,7 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, prev, callb
// A higher fork has arrived. // A higher fork has arrived.
// Time to reorganize the chain. // Time to reorganize the chain.
bcoin.debug('WARNING: Reorganizing 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. // Attempt to add block to the chain index.
self._setBestChain(entry, block, prev, function(err) { self.setBestChain(entry, block, prev, function(err) {
if (err) if (err)
return done(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 * Get the initial deployment state of the chain. Called on load.
* given time. Since segwit affects almost all of bitcoin, it
* is one deployment that needs to be checked frequently.
* @private * @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; var self = this;
if (this.segwitActive != null) if (this.segwitActive != null)
@ -2330,7 +2324,7 @@ Chain.prototype._getInitialState = function _getInitialState(callback) {
if (!this.tip) if (!this.tip)
return utils.nextTick(callback); return utils.nextTick(callback);
return this.tip.getPrevious(function(err, prev) { this.tip.getPrevious(function(err, prev) {
if (err) if (err)
return callback(err); return callback(err);
@ -2351,7 +2345,7 @@ Chain.prototype._getInitialState = function _getInitialState(callback) {
self.csvActive = state.hasCSV(); self.csvActive = state.hasCSV();
self.segwitActive = state.hasWitness(); self.segwitActive = state.hasWitness();
return callback(); return callback(null, state);
}); });
}); });
}); });

View File

@ -129,6 +129,38 @@ Headers.fromRaw = function fromRaw(data, enc) {
return new Headers().fromRaw(data); 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. * Instantiate headers from a chain entry.
* @param {ChainEntry} entry * @param {ChainEntry} entry