chain: more refactoring.

This commit is contained in:
Christopher Jeffrey 2016-11-30 16:00:22 -08:00
parent cfd7ec8a64
commit 311b9841fb
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 142 additions and 81 deletions

View File

@ -15,6 +15,7 @@ var constants = require('../protocol/constants');
var util = require('../utils/util');
var btcutils = require('../btc/utils');
var Locker = require('../utils/locker');
var LRU = require('../utils/lru');
var ChainEntry = require('./chainentry');
var CoinView = require('./coinview');
var assert = require('assert');
@ -80,7 +81,7 @@ function Chain(options) {
this.currentBlock = null;
this.orphanLimit = options.orphanLimit || (20 << 20);
this.locker = new Locker(true);
this.invalid = {};
this.invalid = new LRU(100);
this.bestHeight = -1;
this.tip = null;
this.height = -1;
@ -133,17 +134,17 @@ Chain.prototype._init = function _init() {
block.rhash, entry.height);
});
this.on('checkpoint', function(block, height) {
this.on('checkpoint', function(hash, height) {
self.logger.debug('Hit checkpoint block %s (%d).',
block.rhash, height);
util.revHex(hash), height);
});
this.on('fork', function(block, height, expected) {
this.on('fork', function(hash, height, expected) {
self.logger.warning(
'Fork at height %d: expected=%s received=%s',
height,
util.revHex(expected),
block.rhash
util.revHex(hash)
);
});
@ -901,7 +902,7 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
} catch (e) {
if (e.type === 'VerifyError') {
if (!e.malleated)
this.invalid[entry.hash] = true;
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
throw e;
@ -967,7 +968,7 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
if (e.type === 'VerifyError') {
if (!e.malleated)
this.invalid[entry.hash] = true;
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
@ -1006,7 +1007,7 @@ Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
if (e.type === 'VerifyError') {
if (!e.malleated)
this.invalid[entry.hash] = true;
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
@ -1210,7 +1211,7 @@ Chain.prototype.add = co(function* add(block) {
Chain.prototype._add = co(function* add(block) {
var ret = new VerifyResult();
var initial = true;
var hash, height, orphan, entry, prev;
var hash, height, entry, prev;
while (block) {
hash = block.hash('hex');
@ -1222,13 +1223,6 @@ Chain.prototype._add = co(function* add(block) {
if (hash === this.network.genesis.hash)
break;
// Do not revalidate known invalid blocks.
if (this.invalid[hash] || this.invalid[block.prevBlock]) {
this.emit('invalid', block, block.getCoinbaseHeight());
this.invalid[hash] = true;
throw new VerifyError(block, 'duplicate', 'duplicate', 100);
}
// Do we already have this block?
if (this.hasPending(hash)) {
this.emit('exists', block, block.getCoinbaseHeight());
@ -1237,42 +1231,22 @@ Chain.prototype._add = co(function* add(block) {
// If the block is already known to be
// an orphan, ignore it.
// if (this.hasOrphan(hash)) {
// this.emit('orphan', block, block.getCoinbaseHeight());
// throw new VerifyError(block, 'duplicate', 'duplicate', 0);
// }
// The orphan chain may have forked.
// if (this.isOrphanFork(block))
// throw new VerifyError(block, 'invalid', 'bad-prevblk', 0);
// If the block is already known to be
// an orphan, ignore it.
orphan = this.orphan.map[block.prevBlock];
if (orphan) {
if (orphan.hash('hex') !== hash) {
this.emit('fork', block,
block.getCoinbaseHeight(),
orphan.hash('hex'));
this.resolveOrphan(block.prevBlock);
this.storeOrphan(block);
throw new VerifyError(block, 'invalid', 'bad-prevblk', 0);
}
if (this.seenOrphan(hash)) {
this.emit('orphan', block, block.getCoinbaseHeight());
throw new VerifyError(block, 'duplicate', 'duplicate', 0);
}
// Do not revalidate known invalid blocks.
if (this.hasInvalid(hash, block)) {
this.emit('invalid', block, block.getCoinbaseHeight());
throw new VerifyError(block, 'duplicate', 'duplicate', 100);
}
// Validate the block we want to add.
// This is only necessary for new
// blocks coming in, not the resolving
// orphans.
if (initial && !block.verify(ret)) {
if (ret.reason === 'high-hash')
this.invalid[hash] = true;
if (!block.verify(ret)) {
this.emit('invalid', block, block.getCoinbaseHeight());
throw new VerifyError(block, 'invalid', ret.reason, ret.score);
}
@ -1438,31 +1412,6 @@ Chain.prototype.finish = function finish(block, entry) {
time);
};
/**
* Test whether a block is a on a forked orphan chain.
* @private
* @param {Block} block
* @returns {Boolean}
*/
Chain.prototype.isOrphanFork = function isOrphanFork(block) {
var orphan = this.orphan.map[block.prevBlock];
if (!orphan)
return false;
assert(orphan.hash('hex') !== hash);
this.emit('fork', block,
block.getCoinbaseHeight(),
orphan.hash('hex'));
this.resolveOrphan(block.prevBlock);
this.storeOrphan(block);
return true;
};
/**
* Verify a block hash and height against the checkpoints.
* @private
@ -1483,7 +1432,7 @@ Chain.prototype.verifyCheckpoint = function verifyCheckpoint(hash, height) {
return true;
if (hash === checkpoint) {
this.emit('checkpoint', block, height);
this.emit('checkpoint', hash, height);
return true;
}
@ -1495,11 +1444,44 @@ Chain.prototype.verifyCheckpoint = function verifyCheckpoint(hash, height) {
this.purgeOrphans();
this.emit('fork', block, height, checkpoint);
this.emit('fork', hash, height, checkpoint);
return false;
};
/**
* Verify we do not already have an orphan.
* Throw if there is an orphan fork.
* @private
* @param {Block} block
* @returns {Boolean}
* @throws {VerifyError}
*/
Chain.prototype.seenOrphan = function seenOrphan(block) {
var orphan = this.orphan.map[block.prevBlock];
var hash;
if (!orphan)
return false;
hash = block.hash('hex');
// The orphan chain forked.
if (orphan.hash('hex') !== hash) {
this.emit('fork', hash,
block.getCoinbaseHeight(),
orphan.hash('hex'));
this.resolveOrphan(block.prevBlock);
this.storeOrphan(block);
throw new VerifyError(block, 'invalid', 'bad-prevblk', 0);
}
return true;
};
/**
* Store an orphan.
* @private
@ -1624,6 +1606,46 @@ Chain.prototype.pruneOrphans = function pruneOrphans() {
return true;
};
/**
* Test whether an invalid block hash has been seen.
* @private
* @param {Hash} hash
* @param {Block} block
* @returns {Boolean}
*/
Chain.prototype.hasInvalid = function hasInvalid(hash, block) {
if (this.invalid.has(hash))
return true;
if (this.invalid.has(block.prevBlock)) {
this.setInvalid(hash);
return true;
}
return false;
};
/**
* Mark a block as invalid.
* @private
* @param {Hash} hash
*/
Chain.prototype.setInvalid = function setInvalid(hash) {
this.invalid.set(hash, true);
};
/**
* Forget an invalid block hash.
* @private
* @param {Hash} hash
*/
Chain.prototype.removeInvalid = function removeInvalid(hash) {
this.invalid.remove(hash);
};
/**
* Test the chain to see if it has a block, orphan, or pending block.
* @param {Hash} hash

View File

@ -2604,7 +2604,7 @@ RPC.prototype.invalidateblock = function invalidateblock(args) {
if (!hash)
return Promise.reject(new RPCError('Block not found.'));
this.chain.invalid[hash] = true;
this.chain.setInvalid(hash);
return Promise.resolve();
};
@ -2620,7 +2620,7 @@ RPC.prototype.reconsiderblock = function reconsiderblock(args) {
if (!hash)
return Promise.reject(new RPCError('Block not found.'));
delete this.chain.invalid[hash];
this.chain.removeInvalid(hash);
return Promise.resolve();
};

View File

@ -54,6 +54,7 @@ function AbstractBlock(options) {
this.mutable = false;
this._valid = null;
this._validHeaders = null;
this._hash = null;
this._hhash = null;
this._size = null;
@ -199,6 +200,18 @@ AbstractBlock.prototype.verify = function verify(ret) {
return valid;
};
/**
* Verify the block.
* @private
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
AbstractBlock.prototype._verify = function verify(ret) {
throw new Error('Abstract method.');
};
/**
* Verify the block headers (called by `verify()` in
* all objects which inherit from AbstractBlock).
@ -208,6 +221,27 @@ AbstractBlock.prototype.verify = function verify(ret) {
*/
AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
var valid = this._validHeaders;
if (valid == null) {
valid = this._verifyHeaders(ret);
if (!this.mutable)
this._validHeaders = valid;
}
return valid;
};
/**
* Verify the block headers (called by `verify()` in
* all objects which inherit from AbstractBlock).
* @private
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
AbstractBlock.prototype._verifyHeaders = function verifyHeaders(ret) {
if (!ret)
ret = new VerifyResult();

View File

@ -205,6 +205,7 @@ MemBlock.prototype.toBlock = function toBlock() {
var block = Block.fromRaw(this.raw);
block._hash = this._hash;
block._cbHeight = this.coinbaseHeight;
block._validHeaders = this._validHeaders;
this.raw = null;
return block;
};

View File

@ -164,12 +164,14 @@ describe('Block', function() {
var block2 = new bcoin.block(block);
block2.hash();
block2.merkleRoot = constants.NULL_HASH;
delete block2._valid;
block2._valid = null;
block2._validHeaders = null;
var ret = {};
assert(!block2.verify(ret));
assert.equal(ret.reason, 'bad-txnmrklroot');
delete block2._valid;
delete block2._hash;
block2._valid = null;
block2._validHeaders = null;
block2._hash = null;
block2.merkleRoot = block.merkleRoot;
assert(block2.verify());
});
@ -181,9 +183,10 @@ describe('Block', function() {
var ret = {};
assert(!mblock2.verify(ret));
assert.equal(ret.reason, 'bad-txnmrklroot');
delete mblock2._validPartial;
delete mblock2._valid;
delete mblock2._hash;
mblock2._valid = null;
mblock2._validHeaders = null;
mblock2._validPartial = null;
mblock2._hash = null;
mblock2.merkleRoot = mblock.merkleRoot;
assert(mblock2.verify());
});
@ -195,8 +198,9 @@ describe('Block', function() {
var ret = {};
assert(!block2.verify(ret));
assert.equal(ret.reason, 'high-hash');
delete block2._valid;
delete block2._hash;
block2._valid = null;
block2._validHeaders = null;
block2._hash = null;
block2.bits = block.bits;
assert(block2.verify());
});