more chain refactoring.
This commit is contained in:
parent
1f3d2f4b96
commit
c580557993
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user