chain fixes.
This commit is contained in:
parent
29576c7bcc
commit
5f67f78170
@ -291,8 +291,6 @@ Chain.prototype._preload = function _preload(callback) {
|
||||
|
||||
// Verify the block headers. We don't want to
|
||||
// trust an external centralized source completely.
|
||||
// For very paranoid but slower validation:
|
||||
// if (!block.verify() || !block.verifyContext()) {
|
||||
if (!block.verify()) {
|
||||
start = Math.max(0, height - 2);
|
||||
stream.destroy();
|
||||
@ -305,14 +303,18 @@ Chain.prototype._preload = function _preload(callback) {
|
||||
delete entry.chainwork;
|
||||
entry.chainwork = entry.getChainwork();
|
||||
|
||||
// Skip the genesis block in case
|
||||
// it ends up being corrupt.
|
||||
if (height === 0) {
|
||||
height++;
|
||||
return;
|
||||
// Make sure the genesis block is correct.
|
||||
if (height === 0 && entry.hash !== network.genesis.hash) {
|
||||
stream.destroy();
|
||||
return callback(new Error('Bad genesis block.'), 0);
|
||||
}
|
||||
|
||||
self.db.save(entry);
|
||||
// Filthy hack to avoid writing
|
||||
// redundant blocks to disk!
|
||||
if (height < chainHeight)
|
||||
self.db._populate(entry);
|
||||
else
|
||||
self.db.save(entry);
|
||||
|
||||
height++;
|
||||
|
||||
@ -402,6 +404,17 @@ Chain.prototype._verify = function _verify(block, prev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For some reason bitcoind has p2sh in the
|
||||
// mandatory flags by default, when in reality
|
||||
// it wasn't activated until march 30th 2012.
|
||||
// The first p2sh output and redeem script
|
||||
// appeared on march 7th 2012, only it did
|
||||
// not have a signature. See:
|
||||
// https://blockchain.info/tx/6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192
|
||||
// https://blockchain.info/tx/9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6
|
||||
if (block.ts < constants.block.bip16time)
|
||||
flags &= ~constants.flags.VERIFY_P2SH;
|
||||
|
||||
// Only allow version 2 blocks (coinbase height)
|
||||
// once the majority of blocks are using it.
|
||||
if (block.version < 2 && prev.isOutdated(2)) {
|
||||
@ -553,8 +566,10 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
|
||||
// If we are an ancestor of a checkpoint, we can
|
||||
// skip the input verification.
|
||||
// if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
|
||||
// return callback(null, true);
|
||||
if (this.options.useCheckpoints) {
|
||||
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
this._fillBlock(block, function(err) {
|
||||
var i, j, input, hash;
|
||||
@ -579,9 +594,8 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)',
|
||||
block.rhash, tx.rhash,
|
||||
input.prevout.rhash + '/' + input.prevout.index);
|
||||
throw new Error('Spent inputs: '
|
||||
+ utils._inspect(input, false)
|
||||
+ JSON.stringify(input, null, 2));
|
||||
if (height < network.checkpoints.lastHeight)
|
||||
throw new Error('BUG: Spent inputs in historical data!');
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
@ -589,15 +603,15 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
if (!tx.verify(j, true, flags)) {
|
||||
utils.debug('Block has invalid inputs: %s (%s/%d)',
|
||||
block.rhash, tx.rhash, j);
|
||||
utils.debug('Signature Hash: %s', utils.toHex(tx.signatureHash(j, input.output.script, 'all')));
|
||||
utils.debug(input);
|
||||
utils.debug('raw: %s', utils.toHex(input.output.script._raw || []));
|
||||
utils.debug('encoded: %s', utils.toHex(bcoin.script.encode(input.output.script)));
|
||||
throw new Error('Bad inputs: '
|
||||
+ utils._inspect(input, false)
|
||||
+ JSON.stringify(input, null, 2)
|
||||
+ '\n'
|
||||
+ utils.toHex(tx.signatureHash(j, input.output.script, 'all')));
|
||||
utils.debug('Signature Hash: %s',
|
||||
utils.toHex(tx.signatureHash(j, input.output.script, 'all')));
|
||||
utils.debug('Raw Script: %s',
|
||||
utils.toHex(input.output.script._raw || []));
|
||||
utils.debug('Reserialized Script: %s',
|
||||
utils.toHex(bcoin.script.encode(input.output.script)));
|
||||
if (height < network.checkpoints.lastHeight)
|
||||
throw new Error('BUG: Bad inputs in historical data!');
|
||||
return callback(null, false);
|
||||
}
|
||||
}
|
||||
@ -671,7 +685,6 @@ Chain.prototype._addEntry = function _addEntry(entry, block, callback) {
|
||||
|
||||
Chain.prototype.resetHeight = function resetHeight(height) {
|
||||
var self = this;
|
||||
var count = this.db.count();
|
||||
var i, existing;
|
||||
|
||||
assert(!this.resetting);
|
||||
@ -695,7 +708,6 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
||||
|
||||
Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
|
||||
var self = this;
|
||||
var count = this.db.count();
|
||||
var i, lock;
|
||||
|
||||
assert(!this.resetting);
|
||||
@ -865,9 +877,6 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
|
||||
assert(!this.loading);
|
||||
|
||||
if (this.resetting || this.reverting || this.syncing)
|
||||
return callback();
|
||||
|
||||
if (this.locked) {
|
||||
this.pending.push([initial, peer, callback]);
|
||||
this.pendingBlocks[initial.hash('hex')] = true;
|
||||
@ -879,6 +888,9 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.resetting || this.reverting || this.syncing)
|
||||
return callback();
|
||||
|
||||
this.locked = true;
|
||||
|
||||
(function next(block) {
|
||||
@ -911,7 +923,7 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
if (block === initial && !block.verify()) {
|
||||
self.invalid[hash] = true;
|
||||
self.emit('invalid', block, {
|
||||
height: prevHeight + 1,
|
||||
height: prevHeight === -1 ? -1 : prevHeight + 1,
|
||||
hash: hash,
|
||||
seen: false,
|
||||
chain: false
|
||||
@ -927,23 +939,32 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
// reset the orphans and find a new peer.
|
||||
if (orphan.hash('hex') !== hash) {
|
||||
self.emit('purge', self.orphan.count, self.orphan.size, peer);
|
||||
|
||||
self.orphan.map = {};
|
||||
self.orphan.bmap = {};
|
||||
self.orphan.count = 0;
|
||||
self.orphan.size = 0;
|
||||
|
||||
self.emit('fork', block, {
|
||||
height: -1,
|
||||
expected: orphan.hash('hex'),
|
||||
received: hash,
|
||||
checkpoint: false
|
||||
}, peer);
|
||||
|
||||
// Clear the queue. No telling what other
|
||||
// blocks we're going to get after this.
|
||||
self.pending.length = 0;
|
||||
|
||||
return done();
|
||||
}
|
||||
|
||||
self.emit('orphan', block, {
|
||||
height: -1,
|
||||
hash: hash,
|
||||
seen: true
|
||||
}, peer);
|
||||
|
||||
return done();
|
||||
}
|
||||
|
||||
@ -956,7 +977,7 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
|
||||
// If previous block wasn't ever seen,
|
||||
// add it current to orphans and break.
|
||||
if (prevHeight == null) {
|
||||
if (prevHeight === -1) {
|
||||
self.orphan.count++;
|
||||
self.orphan.size += block.getSize();
|
||||
self.orphan.map[prevHash] = block;
|
||||
@ -981,13 +1002,7 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
height: prevHeight + 1
|
||||
});
|
||||
|
||||
// Fork at checkpoint
|
||||
// Block did not match the checkpoint. The
|
||||
// chain could be reset to the last sane
|
||||
// checkpoint, but it really isn't necessary,
|
||||
// so we don't do it. The misbehaving peer has
|
||||
// been killed and hopefully we find a peer
|
||||
// who isn't trying to fool us.
|
||||
// Verify the checkpoint.
|
||||
checkpoint = network.checkpoints[entry.height];
|
||||
if (checkpoint) {
|
||||
self.emit('checkpoint', block, {
|
||||
@ -995,18 +1010,25 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
hash: entry.hash,
|
||||
checkpoint: checkpoint
|
||||
}, peer);
|
||||
|
||||
// Block did not match the checkpoint. The
|
||||
// chain could be reset to the last sane
|
||||
// checkpoint, but it really isn't necessary,
|
||||
// so we don't do it. The misbehaving peer has
|
||||
// been killed and hopefully we find a peer
|
||||
// who isn't trying to fool us.
|
||||
if (entry.hash !== checkpoint) {
|
||||
// Resetting to the last checkpoint _really_ isn't
|
||||
// necessary (even bitcoind doesn't do it), but it
|
||||
// could be used if you want to be on the overly
|
||||
// safe (see: paranoid) side.
|
||||
// this.resetLastCheckpoint(entry.height);
|
||||
self.emit('fork', block, {
|
||||
height: entry.height,
|
||||
expected: network.checkpoints[entry.height],
|
||||
received: entry.hash,
|
||||
checkpoint: true
|
||||
}, peer);
|
||||
|
||||
// Clear the queue. No telling what other
|
||||
// blocks we're going to get after this.
|
||||
self.pending.length = 0;
|
||||
|
||||
return done();
|
||||
}
|
||||
}
|
||||
@ -1035,19 +1057,25 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
// don't store by hash so we can't compare
|
||||
// chainworks. We reset the chain, find a
|
||||
// new peer, and wait to see who wins.
|
||||
assert(self.db.getHeight(entry.hash) == null);
|
||||
assert(self.db.getHeight(entry.hash) === -1);
|
||||
|
||||
// The tip has more chainwork, it is a
|
||||
// higher height than the entry. This is
|
||||
// not an alternate tip. Ignore it.
|
||||
if (self.tip.chainwork.cmp(entry.chainwork) > 0)
|
||||
if (existing.chainwork.cmp(entry.chainwork) > 0)
|
||||
return done();
|
||||
|
||||
// NOTE: We should do contextual verification
|
||||
// here if we were a fullnode that actually
|
||||
// stored multiple chains, but since we backoff,
|
||||
// we can ignore that until the shorter chain
|
||||
// stops being propogated.
|
||||
|
||||
// The block has equal chainwork (an
|
||||
// alternate tip). Reset the chain, find
|
||||
// a new peer, and wait to see who wins.
|
||||
// return self.revertHeight(existing.height - 1, function(err) {
|
||||
return self._revertLast(existing, function(err, existingBlock) {
|
||||
// self.revertHeight(existing.height - 1, function(err) {
|
||||
self._revertLast(existing, function(err, existingBlock) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
@ -1058,13 +1086,17 @@ Chain.prototype.add = function add(initial, peer, callback) {
|
||||
checkpoint: false
|
||||
}, peer);
|
||||
|
||||
// Clear the queue. No telling what other
|
||||
// blocks we're going to get after this.
|
||||
self.pending.length = 0;
|
||||
|
||||
return done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add entry if we do not have it.
|
||||
assert(self.db.getHeight(entry.hash) == null);
|
||||
assert(self.db.getHeight(entry.hash) === -1);
|
||||
|
||||
// Lookup previous entry.
|
||||
// We can do this synchronously:
|
||||
@ -1201,7 +1233,7 @@ Chain.prototype.has = function has(hash) {
|
||||
};
|
||||
|
||||
Chain.prototype.byHeight = function byHeight(height) {
|
||||
if (height == null)
|
||||
if (height < 0 || height == null)
|
||||
return;
|
||||
return this.db.get(height);
|
||||
};
|
||||
@ -1217,7 +1249,7 @@ Chain.prototype.byHash = function byHash(hash) {
|
||||
|
||||
Chain.prototype.byTime = function byTime(ts) {
|
||||
var start = 0;
|
||||
var end = this.db.count();
|
||||
var end = this.height + 1;
|
||||
var pos, delta, entry;
|
||||
|
||||
if (ts >= this.tip.ts)
|
||||
@ -1324,14 +1356,14 @@ Chain.prototype.getLocator = function getLocator(start) {
|
||||
|
||||
if (typeof start === 'string') {
|
||||
top = this.db.getHeight(start);
|
||||
if (top == null) {
|
||||
if (top === -1) {
|
||||
// We could simply `return [start]` here,
|
||||
// but there is no standardized "spacing"
|
||||
// for locator hashes. Pretend this hash
|
||||
// is our tip. This is useful for getheaders
|
||||
// when not using headers-first.
|
||||
hashes.push(start);
|
||||
top = this.db.count() - 1;
|
||||
top = this.height;
|
||||
}
|
||||
} else if (typeof start === 'number') {
|
||||
top = start;
|
||||
@ -1362,7 +1394,7 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback) {
|
||||
var hashes = [];
|
||||
var top = this.height;
|
||||
var step = 1;
|
||||
var i, called;
|
||||
var i, called, pending;
|
||||
|
||||
if (start) {
|
||||
if (utils.isBuffer(start))
|
||||
@ -1373,19 +1405,23 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback) {
|
||||
|
||||
if (typeof start === 'string') {
|
||||
top = this.db.getHeight(start);
|
||||
if (top == null) {
|
||||
if (top === -1) {
|
||||
// We could simply `return [start]` here,
|
||||
// but there is no standardized "spacing"
|
||||
// for locator hashes. Pretend this hash
|
||||
// is our tip. This is useful for getheaders
|
||||
// when not using headers-first.
|
||||
hashes.push(start);
|
||||
top = this.db.count() - 1;
|
||||
top = this.height;
|
||||
}
|
||||
} else if (typeof start === 'number') {
|
||||
top = start;
|
||||
}
|
||||
|
||||
assert(this.db.has(top));
|
||||
|
||||
callback = utils.asyncify(callback);
|
||||
|
||||
function done(err) {
|
||||
if (called)
|
||||
return;
|
||||
@ -1398,14 +1434,6 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback) {
|
||||
return callback(null, hashes);
|
||||
}
|
||||
|
||||
try {
|
||||
assert(this.db.has(top));
|
||||
} catch (e) {
|
||||
return done(new Error('Potential reset.'));
|
||||
}
|
||||
|
||||
var pending;
|
||||
|
||||
i = top;
|
||||
for (;;) {
|
||||
hashes.push(i);
|
||||
@ -1463,11 +1491,7 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
|
||||
};
|
||||
|
||||
Chain.prototype.getHeight = function getHeight(hash) {
|
||||
var height = this.db.getHeight(hash);
|
||||
if (height == null)
|
||||
return -1;
|
||||
|
||||
return height;
|
||||
return this.db.getHeight(hash);
|
||||
};
|
||||
|
||||
Chain.prototype.getNextBlock = function getNextBlock(hash) {
|
||||
|
||||
@ -242,7 +242,12 @@ ChainDB.prototype.cache = function cache(entry) {
|
||||
};
|
||||
|
||||
ChainDB.prototype.getHeight = function getHeight(hash) {
|
||||
return this.heightLookup[hash];
|
||||
var height = this.heightLookup[hash];
|
||||
|
||||
if (height == null)
|
||||
return -1;
|
||||
|
||||
return height;
|
||||
};
|
||||
|
||||
ChainDB.prototype._populate = function _populate(entry) {
|
||||
@ -344,6 +349,9 @@ ChainDB.prototype.save = function save(entry, callback) {
|
||||
ChainDB.prototype.saveSync = function saveSync(entry) {
|
||||
var raw, offset;
|
||||
|
||||
assert(entry.height >= 0);
|
||||
assert(entry.height * BLOCK_SIZE === this.size);
|
||||
|
||||
// Cache the past 1001 blocks in memory
|
||||
// (necessary for isSuperMajority)
|
||||
this.cache(entry);
|
||||
@ -362,6 +370,9 @@ ChainDB.prototype.saveAsync = function saveAsync(entry, callback) {
|
||||
|
||||
callback = utils.asyncify(callback);
|
||||
|
||||
assert(entry.height >= 0);
|
||||
assert(entry.height * BLOCK_SIZE === this.size);
|
||||
|
||||
// Cache the past 1001 blocks in memory
|
||||
// (necessary for isSuperMajority)
|
||||
this.cache(entry);
|
||||
|
||||
@ -687,7 +687,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
|
||||
self.chain.orphan.count,
|
||||
self.request.activeBlocks,
|
||||
peer._blockQueue.length,
|
||||
self.chain.currentTarget(),
|
||||
self.chain.getCurrentTarget(),
|
||||
self.peers.all.length,
|
||||
self.chain.pending.length,
|
||||
self.chain.bestHeight);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user