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)
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);
});
});
});

View File

@ -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