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