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),
|
hash: utils.toHex(network.genesis._hash),
|
||||||
version: network.genesis.version,
|
version: network.genesis.version,
|
||||||
// prevBlock: utils.toHex(network.genesis.prevBlock),
|
prevBlock: utils.toHex(network.genesis.prevBlock),
|
||||||
ts: network.genesis.ts,
|
ts: network.genesis.ts,
|
||||||
bits: network.genesis.bits,
|
bits: network.genesis.bits,
|
||||||
height: 0
|
height: 0
|
||||||
@ -158,6 +158,11 @@ Chain.prototype._addIndex = function _addIndex(entry) {
|
|||||||
if (checkpoint) {
|
if (checkpoint) {
|
||||||
this.emit('checkpoint', entry.height, entry.hash, checkpoint);
|
this.emit('checkpoint', entry.height, entry.hash, checkpoint);
|
||||||
if (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);
|
this.emit('fork', entry.height, entry.hash, checkpoint);
|
||||||
return Chain.codes.badCheckpoint;
|
return Chain.codes.badCheckpoint;
|
||||||
}
|
}
|
||||||
@ -177,12 +182,18 @@ Chain.prototype._addIndex = function _addIndex(entry) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
|
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)
|
assert(index >= 0);
|
||||||
lastHeight = 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) {
|
Chain.prototype.resetHeight = function resetHeight(height) {
|
||||||
@ -191,14 +202,21 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
|||||||
|
|
||||||
assert(height < this.index.entries.length);
|
assert(height < this.index.entries.length);
|
||||||
|
|
||||||
|
// Nothing to do.
|
||||||
if (height === this.index.entries.length - 1)
|
if (height === this.index.entries.length - 1)
|
||||||
return;
|
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.map = {};
|
||||||
this.orphan.bmap = {};
|
this.orphan.bmap = {};
|
||||||
this.orphan.count = 0;
|
this.orphan.count = 0;
|
||||||
this.orphan.size = 0;
|
this.orphan.size = 0;
|
||||||
|
|
||||||
|
// Rebuild the index from our new (lower) height.
|
||||||
this.index.entries.length = height + 1;
|
this.index.entries.length = height + 1;
|
||||||
|
|
||||||
this.index.heights = this.index.entries.reduce(function(out, entry) {
|
this.index.heights = this.index.entries.reduce(function(out, entry) {
|
||||||
if (!self.options.fullNode) {
|
if (!self.options.fullNode) {
|
||||||
if (!entry)
|
if (!entry)
|
||||||
@ -207,6 +225,7 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
|||||||
out[entry.hash] = entry.height;
|
out[entry.hash] = entry.height;
|
||||||
return out;
|
return out;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
this.index.hashes.length = height + 1;
|
this.index.hashes.length = height + 1;
|
||||||
|
|
||||||
if (!this.options.fullNode)
|
if (!this.options.fullNode)
|
||||||
@ -214,14 +233,20 @@ Chain.prototype.resetHeight = function resetHeight(height) {
|
|||||||
else
|
else
|
||||||
this.index.count = height + 1;
|
this.index.count = height + 1;
|
||||||
|
|
||||||
|
// Set and emit our new (old) tip.
|
||||||
this.tip = this.index.entries[this.index.entries.length - 1];
|
this.tip = this.index.entries[this.index.entries.length - 1];
|
||||||
this.emit('tip', this.tip);
|
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 = Math.min(
|
||||||
this.index.lastTs,
|
this.index.lastTs,
|
||||||
this.index.entries[this.index.entries.length - 1].ts
|
this.index.entries[this.index.entries.length - 1].ts
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Delete all the blocks now above us.
|
||||||
ahead.forEach(function(entry) {
|
ahead.forEach(function(entry) {
|
||||||
if (!self.options.fullNode) {
|
if (!self.options.fullNode) {
|
||||||
if (!entry)
|
if (!entry)
|
||||||
@ -239,6 +264,10 @@ Chain.prototype.resetTime = function resetTime(ts) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.add = function add(block, peer) {
|
Chain.prototype.add = function add(block, peer) {
|
||||||
|
var initial = block;
|
||||||
|
var code = Chain.codes.unchanged;
|
||||||
|
var hash, prevHash, prevHeight, entry;
|
||||||
|
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
this.once('load', function() {
|
this.once('load', function() {
|
||||||
this.add(block);
|
this.add(block);
|
||||||
@ -246,33 +275,35 @@ Chain.prototype.add = function add(block, peer) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var initial = block;
|
|
||||||
var code = Chain.codes.unchanged;
|
|
||||||
var hash, prev, i, entry;
|
|
||||||
|
|
||||||
for (;;) {
|
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()) {
|
if (block === initial && !block.verify()) {
|
||||||
code = Chain.codes.invalid;
|
code = Chain.codes.invalid;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
hash = block.hash('hex');
|
hash = block.hash('hex');
|
||||||
prev = block.prevBlock;
|
prevHash = block.prevBlock;
|
||||||
|
|
||||||
// If the block is already known to be an orphan
|
// If the block is already known to be
|
||||||
if (this.orphan.map[prev]) {
|
// an orphan, ignore it.
|
||||||
|
if (this.orphan.map[prevHash]) {
|
||||||
code = Chain.codes.knownOrphan;
|
code = Chain.codes.knownOrphan;
|
||||||
break;
|
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 previous block wasn't ever seen,
|
||||||
if (i == null) {
|
// add it current to orphans and break.
|
||||||
|
if (prevHeight == null) {
|
||||||
this.orphan.count++;
|
this.orphan.count++;
|
||||||
this.orphan.size += block.size();
|
this.orphan.size += block.size();
|
||||||
this.orphan.map[prev] = block;
|
this.orphan.map[prevHash] = block;
|
||||||
this.orphan.bmap[hash] = block;
|
this.orphan.bmap[hash] = block;
|
||||||
code = Chain.codes.newOrphan;
|
code = Chain.codes.newOrphan;
|
||||||
break;
|
break;
|
||||||
@ -281,50 +312,67 @@ Chain.prototype.add = function add(block, peer) {
|
|||||||
entry = new ChainBlock(this, {
|
entry = new ChainBlock(this, {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
version: block.version,
|
version: block.version,
|
||||||
// prevBlock: prev,
|
prevBlock: prevHash,
|
||||||
ts: block.ts,
|
ts: block.ts,
|
||||||
bits: block.bits,
|
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) {
|
if (this.index.hashes[entry.height] !== hash) {
|
||||||
assert(this.index.heights[entry.hash] == null);
|
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.index.hashes[entry.height]) {
|
||||||
if (this.tip.chainwork.cmp(entry.chainwork) < 0) {
|
// The tip has more chainwork, it is a
|
||||||
if (!block.postVerify()) {
|
// higher height than the entry. This is
|
||||||
throw new Error;
|
// not an alternate tip. Ignore it.
|
||||||
//code = Chain.codes.invalid;
|
if (this.tip.chainwork.cmp(entry.chainwork) > 0) {
|
||||||
//break;
|
code = Chain.codes.unchanged;
|
||||||
}
|
|
||||||
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);
|
|
||||||
break;
|
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()) {
|
if (!block.postVerify()) {
|
||||||
throw new Error;
|
throw new Error;
|
||||||
//code = Chain.codes.invalid;
|
//code = Chain.codes.invalid;
|
||||||
//break;
|
//break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to add block to the chain index.
|
||||||
code = this._addIndex(entry);
|
code = this._addIndex(entry);
|
||||||
|
|
||||||
|
// Result should never be `unchanged` since
|
||||||
|
// we already verified there were no
|
||||||
|
// duplicate heights, etc.
|
||||||
assert(code !== Chain.codes.unchanged);
|
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)
|
if (code !== Chain.codes.okay)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Emit our block (and potentially resolved
|
||||||
|
// orphan) so the programmer can save it.
|
||||||
this.emit('block', block, peer);
|
this.emit('block', block, peer);
|
||||||
this.emit('entry', entry);
|
this.emit('entry', entry);
|
||||||
if (block !== initial)
|
if (block !== initial)
|
||||||
@ -337,7 +385,7 @@ Chain.prototype.add = function add(block, peer) {
|
|||||||
if (!this.orphan.map[hash])
|
if (!this.orphan.map[hash])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// We have orphan child for this block - add it to chain
|
// An orphan chain was found, start resolving.
|
||||||
block = this.orphan.map[hash];
|
block = this.orphan.map[hash];
|
||||||
delete this.orphan.bmap[block.hash('hex')];
|
delete this.orphan.bmap[block.hash('hex')];
|
||||||
delete this.orphan.map[hash];
|
delete this.orphan.map[hash];
|
||||||
@ -345,7 +393,8 @@ Chain.prototype.add = function add(block, peer) {
|
|||||||
this.orphan.size -= block.size();
|
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) {
|
if (this.orphan.size > 20971520) {
|
||||||
this.orphan.map = {};
|
this.orphan.map = {};
|
||||||
this.orphan.bmap = {};
|
this.orphan.bmap = {};
|
||||||
@ -353,7 +402,8 @@ Chain.prototype.add = function add(block, peer) {
|
|||||||
this.orphan.size = 0;
|
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.options.fullNode) {
|
||||||
// if (this.size() > 100000)
|
// if (this.size() > 100000)
|
||||||
// this.compact();
|
// this.compact();
|
||||||
@ -477,7 +527,11 @@ Chain.prototype.locatorHashes = function locatorHashes(start) {
|
|||||||
if (typeof start === 'string') {
|
if (typeof start === 'string') {
|
||||||
top = this.index.heights[start];
|
top = this.index.heights[start];
|
||||||
if (top == null) {
|
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);
|
hashes.push(start);
|
||||||
top = chain.length - 1;
|
top = chain.length - 1;
|
||||||
}
|
}
|
||||||
@ -574,7 +628,7 @@ Chain.prototype.target = function target(last, block) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Back 2 weeks
|
// Back 2 weeks
|
||||||
first = this.byHeight(last.height - (interval - 1));
|
first = this.index.entries[last.height - (interval - 1)];
|
||||||
|
|
||||||
if (!first)
|
if (!first)
|
||||||
return 0;
|
return 0;
|
||||||
@ -734,7 +788,7 @@ function ChainBlock(chain, data) {
|
|||||||
this.chain = chain;
|
this.chain = chain;
|
||||||
this.hash = data.hash;
|
this.hash = data.hash;
|
||||||
this.version = data.version;
|
this.version = data.version;
|
||||||
// this.prevBlock = data.prevBlock;
|
this.prevBlock = data.prevBlock;
|
||||||
this.ts = data.ts;
|
this.ts = data.ts;
|
||||||
this.bits = data.bits;
|
this.bits = data.bits;
|
||||||
this.height = data.height;
|
this.height = data.height;
|
||||||
@ -753,9 +807,7 @@ ChainBlock.prototype.__defineGetter__('proof', function() {
|
|||||||
var target = utils.fromCompact(this.bits);
|
var target = utils.fromCompact(this.bits);
|
||||||
if (target.isNeg() || target.cmpn(0) === 0)
|
if (target.isNeg() || target.cmpn(0) === 0)
|
||||||
return new bn(0);
|
return new bn(0);
|
||||||
// May be faster:
|
return new bn(1).ushln(256).div(target.addn(1));
|
||||||
// return new bn(1).shln(256).div(target.addn(1));
|
|
||||||
return new bn(2).pow(new bn(256)).div(target.addn(1));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ChainBlock.prototype.getChainwork = function() {
|
ChainBlock.prototype.getChainwork = function() {
|
||||||
@ -808,7 +860,7 @@ ChainBlock.prototype.toJSON = function() {
|
|||||||
// return [
|
// return [
|
||||||
// this.hash,
|
// this.hash,
|
||||||
// this.version,
|
// this.version,
|
||||||
// // this.prevBlock,
|
// this.prevBlock,
|
||||||
// this.ts,
|
// this.ts,
|
||||||
// this.bits,
|
// this.bits,
|
||||||
// this.height
|
// this.height
|
||||||
@ -816,7 +868,7 @@ ChainBlock.prototype.toJSON = function() {
|
|||||||
return {
|
return {
|
||||||
hash: this.hash,
|
hash: this.hash,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
// prevBlock: this.prevBlock,
|
prevBlock: this.prevBlock,
|
||||||
ts: this.ts,
|
ts: this.ts,
|
||||||
bits: this.bits,
|
bits: this.bits,
|
||||||
height: this.height
|
height: this.height
|
||||||
@ -827,9 +879,10 @@ ChainBlock.fromJSON = function(chain, json) {
|
|||||||
// return new ChainBlock(chain, {
|
// return new ChainBlock(chain, {
|
||||||
// hash: json[0],
|
// hash: json[0],
|
||||||
// version: json[1],
|
// version: json[1],
|
||||||
// ts: json[2],
|
// prevBlock: json[2],
|
||||||
// bits: json[3],
|
// ts: json[3],
|
||||||
// height: json[4]
|
// bits: json[4],
|
||||||
|
// height: json[5]
|
||||||
// });
|
// });
|
||||||
return new ChainBlock(chain, json);
|
return new ChainBlock(chain, json);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user