handle chain forks better.
This commit is contained in:
parent
680bf01e98
commit
ee97c7ed12
@ -58,7 +58,7 @@ function Chain(options) {
|
||||
{
|
||||
hash: utils.toHex(network.genesis._hash),
|
||||
version: network.genesis.version,
|
||||
// prevBlock: utils.toHex(network.genesis.prevBlock),
|
||||
prevBlock: utils.toHex(network.genesis.prevBlock),
|
||||
ts: network.genesis.ts,
|
||||
bits: network.genesis.bits,
|
||||
height: 0
|
||||
@ -158,6 +158,11 @@ Chain.prototype._addIndex = function _addIndex(entry) {
|
||||
if (checkpoint) {
|
||||
this.emit('checkpoint', entry.height, entry.hash, checkpoint);
|
||||
if (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);
|
||||
this.emit('fork', entry.height, entry.hash, checkpoint);
|
||||
return Chain.codes.badCheckpoint;
|
||||
}
|
||||
@ -177,12 +182,18 @@ Chain.prototype._addIndex = function _addIndex(entry) {
|
||||
};
|
||||
|
||||
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
|
||||
var lastHeight = Object.keys(network.checkpoints).sort().indexOf(height) - 1;
|
||||
var heights = Object.keys(network.checkpoints).sort();
|
||||
var index = heights.indexOf(height) - 1;
|
||||
var checkpoint = network.checkpoint[index];
|
||||
|
||||
if (lastHeight < 0)
|
||||
lastHeight = 0;
|
||||
assert(index >= 0);
|
||||
assert(checkpoint);
|
||||
|
||||
this.resetHeight(lastHeight);
|
||||
// This is the safest way to do it, the other possibility is to simply reset
|
||||
// ignore the bad checkpoint block. The likelihood of someone carrying on an
|
||||
// entire fork between to checkpoints is absurd, so this is probably _a lot_
|
||||
// of work for nothing.
|
||||
this.resetHeight(checkpoint.height);
|
||||
};
|
||||
|
||||
Chain.prototype.resetHeight = function resetHeight(height) {
|
||||
@ -191,14 +202,21 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
||||
|
||||
assert(height < this.index.entries.length);
|
||||
|
||||
// Nothing to do.
|
||||
if (height === this.index.entries.length - 1)
|
||||
return;
|
||||
|
||||
// Reset the orphan map completely. There may
|
||||
// have been some orphans on a forked chain we
|
||||
// no longer need.
|
||||
this.orphan.map = {};
|
||||
this.orphan.bmap = {};
|
||||
this.orphan.count = 0;
|
||||
this.orphan.size = 0;
|
||||
|
||||
// Rebuild the index from our new (lower) height.
|
||||
this.index.entries.length = height + 1;
|
||||
|
||||
this.index.heights = this.index.entries.reduce(function(out, entry) {
|
||||
if (!self.options.fullNode) {
|
||||
if (!entry)
|
||||
@ -207,6 +225,7 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
||||
out[entry.hash] = entry.height;
|
||||
return out;
|
||||
}, {});
|
||||
|
||||
this.index.hashes.length = height + 1;
|
||||
|
||||
if (!this.options.fullNode)
|
||||
@ -214,14 +233,20 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
||||
else
|
||||
this.index.count = height + 1;
|
||||
|
||||
// Set and emit our new (old) tip.
|
||||
this.tip = this.index.entries[this.index.entries.length - 1];
|
||||
this.emit('tip', this.tip);
|
||||
|
||||
// The lastTs is supposed to be the last ts
|
||||
// after the preload, but we're not sure where
|
||||
// we're resetting to. It may be lower, it may
|
||||
// be higher. Reset it if necessary.
|
||||
this.index.lastTs = Math.min(
|
||||
this.index.lastTs,
|
||||
this.index.entries[this.index.entries.length - 1].ts
|
||||
);
|
||||
|
||||
// Delete all the blocks now above us.
|
||||
ahead.forEach(function(entry) {
|
||||
if (!self.options.fullNode) {
|
||||
if (!entry)
|
||||
@ -239,6 +264,10 @@ Chain.prototype.resetTime = function resetTime(ts) {
|
||||
};
|
||||
|
||||
Chain.prototype.add = function add(block, peer) {
|
||||
var initial = block;
|
||||
var code = Chain.codes.unchanged;
|
||||
var hash, prevHash, prevHeight, entry;
|
||||
|
||||
if (this.loading) {
|
||||
this.once('load', function() {
|
||||
this.add(block);
|
||||
@ -246,33 +275,35 @@ Chain.prototype.add = function add(block, peer) {
|
||||
return;
|
||||
}
|
||||
|
||||
var initial = block;
|
||||
var code = Chain.codes.unchanged;
|
||||
var hash, prev, i, entry;
|
||||
|
||||
for (;;) {
|
||||
// Only validate the initial block (orphans were already validated)
|
||||
// Validate the block we want to add.
|
||||
// This is only necessary for new
|
||||
// blocks coming in, not the resolving
|
||||
// orphans.
|
||||
if (block === initial && !block.verify()) {
|
||||
code = Chain.codes.invalid;
|
||||
break;
|
||||
}
|
||||
|
||||
hash = block.hash('hex');
|
||||
prev = block.prevBlock;
|
||||
prevHash = block.prevBlock;
|
||||
|
||||
// If the block is already known to be an orphan
|
||||
if (this.orphan.map[prev]) {
|
||||
// If the block is already known to be
|
||||
// an orphan, ignore it.
|
||||
if (this.orphan.map[prevHash]) {
|
||||
code = Chain.codes.knownOrphan;
|
||||
break;
|
||||
}
|
||||
|
||||
i = this.index.heights[prev];
|
||||
// Find the previous block height/index.
|
||||
prevHeight = this.index.heights[prevHash];
|
||||
|
||||
// If previous block wasn't ever seen - add current to orphans
|
||||
if (i == null) {
|
||||
// If previous block wasn't ever seen,
|
||||
// add it current to orphans and break.
|
||||
if (prevHeight == null) {
|
||||
this.orphan.count++;
|
||||
this.orphan.size += block.size();
|
||||
this.orphan.map[prev] = block;
|
||||
this.orphan.map[prevHash] = block;
|
||||
this.orphan.bmap[hash] = block;
|
||||
code = Chain.codes.newOrphan;
|
||||
break;
|
||||
@ -281,50 +312,67 @@ Chain.prototype.add = function add(block, peer) {
|
||||
entry = new ChainBlock(this, {
|
||||
hash: hash,
|
||||
version: block.version,
|
||||
// prevBlock: prev,
|
||||
prevBlock: prevHash,
|
||||
ts: block.ts,
|
||||
bits: block.bits,
|
||||
height: i + 1
|
||||
height: prevHeight + 1
|
||||
});
|
||||
|
||||
// Add entry if we do not have it (or there is another hash at its height)
|
||||
// Add entry if we do not have it (or if
|
||||
// there is another entry at its height)
|
||||
if (this.index.hashes[entry.height] !== hash) {
|
||||
assert(this.index.heights[entry.hash] == null);
|
||||
|
||||
// If we have a block at the same height, use chain with higher work
|
||||
// A valid block with an already existing
|
||||
// height came in, that spells fork. We
|
||||
// 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.
|
||||
if (this.index.hashes[entry.height]) {
|
||||
if (this.tip.chainwork.cmp(entry.chainwork) < 0) {
|
||||
if (!block.postVerify()) {
|
||||
throw new Error;
|
||||
//code = Chain.codes.invalid;
|
||||
//break;
|
||||
}
|
||||
this.resetHeight(entry.height - 1);
|
||||
code = this._addIndex(entry);
|
||||
assert(code !== Chain.codes.unchanged);
|
||||
if (code !== Chain.codes.okay)
|
||||
break;
|
||||
code = Chain.codes.forked;
|
||||
// Breaking here only works because
|
||||
// we deleted the orphan map in resetHeight.
|
||||
this.emit('block', block, peer);
|
||||
this.emit('entry', entry);
|
||||
if (block !== initial)
|
||||
this.emit('resolved', entry);
|
||||
// The tip has more chainwork, it is a
|
||||
// higher height than the entry. This is
|
||||
// not an alternate tip. Ignore it.
|
||||
if (this.tip.chainwork.cmp(entry.chainwork) > 0) {
|
||||
code = Chain.codes.unchanged;
|
||||
break;
|
||||
}
|
||||
// The block has equal chainwork (an
|
||||
// alternate tip). Reset the chain, find
|
||||
// a new peer, and wait to see who wins.
|
||||
this.resetHeight(entry.height - 1);
|
||||
this.emit('fork', entry.height, entry.hash);
|
||||
code = Chain.codes.forked;
|
||||
break;
|
||||
}
|
||||
|
||||
// Validated known block at this point - add it to index
|
||||
// Do "contextual" verification on our block
|
||||
// now that we're certain it's previous
|
||||
// block is in the chain.
|
||||
if (!block.postVerify()) {
|
||||
throw new Error;
|
||||
//code = Chain.codes.invalid;
|
||||
//break;
|
||||
}
|
||||
|
||||
// Attempt to add block to the chain index.
|
||||
code = this._addIndex(entry);
|
||||
|
||||
// Result should never be `unchanged` since
|
||||
// we already verified there were no
|
||||
// duplicate heights, etc.
|
||||
assert(code !== Chain.codes.unchanged);
|
||||
|
||||
// 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 (code !== Chain.codes.okay)
|
||||
break;
|
||||
|
||||
// Emit our block (and potentially resolved
|
||||
// orphan) so the programmer can save it.
|
||||
this.emit('block', block, peer);
|
||||
this.emit('entry', entry);
|
||||
if (block !== initial)
|
||||
@ -337,7 +385,7 @@ Chain.prototype.add = function add(block, peer) {
|
||||
if (!this.orphan.map[hash])
|
||||
break;
|
||||
|
||||
// We have orphan child for this block - add it to chain
|
||||
// An orphan chain was found, start resolving.
|
||||
block = this.orphan.map[hash];
|
||||
delete this.orphan.bmap[block.hash('hex')];
|
||||
delete this.orphan.map[hash];
|
||||
@ -345,7 +393,8 @@ Chain.prototype.add = function add(block, peer) {
|
||||
this.orphan.size -= block.size();
|
||||
}
|
||||
|
||||
// Failsafe for large orphan chains
|
||||
// Failsafe for large orphan chains. Do not
|
||||
// allow more than 20mb stored in memory.
|
||||
if (this.orphan.size > 20971520) {
|
||||
this.orphan.map = {};
|
||||
this.orphan.bmap = {};
|
||||
@ -353,7 +402,8 @@ Chain.prototype.add = function add(block, peer) {
|
||||
this.orphan.size = 0;
|
||||
}
|
||||
|
||||
// No need to have a huge chain
|
||||
// Potentially compact the chain here. A
|
||||
// full chain is not necessary for spv.
|
||||
// if (!this.options.fullNode) {
|
||||
// if (this.size() > 100000)
|
||||
// this.compact();
|
||||
@ -477,7 +527,11 @@ Chain.prototype.locatorHashes = function locatorHashes(start) {
|
||||
if (typeof start === 'string') {
|
||||
top = this.index.heights[start];
|
||||
if (top == null) {
|
||||
// return [start];
|
||||
// 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 = chain.length - 1;
|
||||
}
|
||||
@ -574,7 +628,7 @@ Chain.prototype.target = function target(last, block) {
|
||||
}
|
||||
|
||||
// Back 2 weeks
|
||||
first = this.byHeight(last.height - (interval - 1));
|
||||
first = this.index.entries[last.height - (interval - 1)];
|
||||
|
||||
if (!first)
|
||||
return 0;
|
||||
@ -734,7 +788,7 @@ function ChainBlock(chain, data) {
|
||||
this.chain = chain;
|
||||
this.hash = data.hash;
|
||||
this.version = data.version;
|
||||
// this.prevBlock = data.prevBlock;
|
||||
this.prevBlock = data.prevBlock;
|
||||
this.ts = data.ts;
|
||||
this.bits = data.bits;
|
||||
this.height = data.height;
|
||||
@ -753,9 +807,7 @@ ChainBlock.prototype.__defineGetter__('proof', function() {
|
||||
var target = utils.fromCompact(this.bits);
|
||||
if (target.isNeg() || target.cmpn(0) === 0)
|
||||
return new bn(0);
|
||||
// May be faster:
|
||||
// return new bn(1).shln(256).div(target.addn(1));
|
||||
return new bn(2).pow(new bn(256)).div(target.addn(1));
|
||||
return new bn(1).ushln(256).div(target.addn(1));
|
||||
});
|
||||
|
||||
ChainBlock.prototype.getChainwork = function() {
|
||||
@ -808,7 +860,7 @@ ChainBlock.prototype.toJSON = function() {
|
||||
// return [
|
||||
// this.hash,
|
||||
// this.version,
|
||||
// // this.prevBlock,
|
||||
// this.prevBlock,
|
||||
// this.ts,
|
||||
// this.bits,
|
||||
// this.height
|
||||
@ -816,7 +868,7 @@ ChainBlock.prototype.toJSON = function() {
|
||||
return {
|
||||
hash: this.hash,
|
||||
version: this.version,
|
||||
// prevBlock: this.prevBlock,
|
||||
prevBlock: this.prevBlock,
|
||||
ts: this.ts,
|
||||
bits: this.bits,
|
||||
height: this.height
|
||||
@ -827,9 +879,10 @@ ChainBlock.fromJSON = function(chain, json) {
|
||||
// return new ChainBlock(chain, {
|
||||
// hash: json[0],
|
||||
// version: json[1],
|
||||
// ts: json[2],
|
||||
// bits: json[3],
|
||||
// height: json[4]
|
||||
// prevBlock: json[2],
|
||||
// ts: json[3],
|
||||
// bits: json[4],
|
||||
// height: json[5]
|
||||
// });
|
||||
return new ChainBlock(chain, json);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user