chain/block: reorder some consensus checks.
This commit is contained in:
parent
71c7c3e300
commit
0f529d5f8b
@ -286,7 +286,8 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
||||
if (this.isGenesis(block))
|
||||
return this.state;
|
||||
|
||||
if (!block.verify(now, ret)) {
|
||||
// Non-contextual checks.
|
||||
if (!block.verify(ret)) {
|
||||
err = new VerifyError(block,
|
||||
'invalid',
|
||||
ret.reason,
|
||||
@ -295,11 +296,7 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
||||
// High hash is the only thing an
|
||||
// adversary couldn't mutate in
|
||||
// otherwise valid non-contextual
|
||||
// checks. The block timestamp
|
||||
// can't be mutated, but our
|
||||
// adjusted time might be off.
|
||||
// We may be able to accept the
|
||||
// block later.
|
||||
// checks.
|
||||
if (ret.reason !== 'high-hash')
|
||||
err.malleated = true;
|
||||
|
||||
@ -322,17 +319,7 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
height = prev.height + 1;
|
||||
ancestors = yield prev.getRetargetAncestors();
|
||||
medianTime = prev.getMedianTime(ancestors);
|
||||
|
||||
// Ensure the timestamp is correct.
|
||||
if (block.ts <= medianTime) {
|
||||
throw new VerifyError(block,
|
||||
'invalid',
|
||||
'time-too-old',
|
||||
0);
|
||||
}
|
||||
|
||||
// Ensure the POW is what we expect.
|
||||
if (block.bits !== this.getTarget(block, prev, ancestors)) {
|
||||
@ -342,9 +329,48 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
||||
100);
|
||||
}
|
||||
|
||||
// Ensure the timestamp is correct.
|
||||
medianTime = prev.getMedianTime(ancestors);
|
||||
|
||||
if (block.ts <= medianTime) {
|
||||
throw new VerifyError(block,
|
||||
'invalid',
|
||||
'time-too-old',
|
||||
0);
|
||||
}
|
||||
|
||||
// Check timestamp against adj-time+2hours.
|
||||
// If this fails we may be able to accept
|
||||
// the block later.
|
||||
if (block.ts > now + 2 * 60 * 60) {
|
||||
err = new VerifyError(block,
|
||||
'invalid',
|
||||
'time-too-new',
|
||||
0);
|
||||
err.malleated = true;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Get the new deployment state.
|
||||
state = yield this.getDeployments(block, prev);
|
||||
|
||||
// Get timestamp for tx.isFinal().
|
||||
ts = state.hasMTP() ? medianTime : block.ts;
|
||||
height = prev.height + 1;
|
||||
|
||||
// Transactions must be finalized with
|
||||
// regards to nSequence and nLockTime.
|
||||
for (i = 0; i < block.txs.length; i++) {
|
||||
tx = block.txs[i];
|
||||
|
||||
if (!tx.isFinal(height, ts)) {
|
||||
throw new VerifyError(block,
|
||||
'invalid',
|
||||
'bad-txns-nonfinal',
|
||||
10);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the height contained
|
||||
// in the coinbase is correct.
|
||||
if (state.hasBIP34()) {
|
||||
@ -408,22 +434,6 @@ Chain.prototype.verify = co(function* verify(block, prev) {
|
||||
100);
|
||||
}
|
||||
|
||||
// Get timestamp for tx.isFinal().
|
||||
ts = state.hasMTP() ? medianTime : block.ts;
|
||||
|
||||
// Transactions must be finalized with
|
||||
// regards to nSequence and nLockTime.
|
||||
for (i = 0; i < block.txs.length; i++) {
|
||||
tx = block.txs[i];
|
||||
|
||||
if (!tx.isFinal(height, ts)) {
|
||||
throw new VerifyError(block,
|
||||
'invalid',
|
||||
'bad-txns-nonfinal',
|
||||
10);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
@ -441,17 +451,6 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev) {
|
||||
var state = new DeploymentState();
|
||||
var active;
|
||||
|
||||
// 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:
|
||||
// 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192
|
||||
// 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6
|
||||
if (block.ts >= constants.block.BIP16_TIME)
|
||||
state.flags |= constants.flags.VERIFY_P2SH;
|
||||
|
||||
// Only allow version 2 blocks (coinbase height)
|
||||
// once the majority of blocks are using it.
|
||||
if (block.version < 2 && height >= this.network.block.bip34height)
|
||||
@ -474,6 +473,17 @@ Chain.prototype.getDeployments = co(function* getDeployments(block, prev) {
|
||||
throw new VerifyError(block, 'obsolete', 'bad-version', 0);
|
||||
}
|
||||
|
||||
// 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:
|
||||
// 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192
|
||||
// 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6
|
||||
if (block.ts >= constants.block.BIP16_TIME)
|
||||
state.flags |= constants.flags.VERIFY_P2SH;
|
||||
|
||||
// Coinbase heights are now enforced (bip34).
|
||||
if (height >= this.network.block.bip34height)
|
||||
state.bip34 = true;
|
||||
@ -1249,7 +1259,7 @@ Chain.prototype._add = co(function* add(block) {
|
||||
// This is only necessary for new
|
||||
// blocks coming in, not the resolving
|
||||
// orphans.
|
||||
if (initial && !block.verify(now, ret)) {
|
||||
if (initial && !block.verify(ret)) {
|
||||
if (ret.reason === 'high-hash')
|
||||
this.invalid[hash] = true;
|
||||
this.emit('invalid', block, block.getCoinbaseHeight());
|
||||
|
||||
@ -1241,7 +1241,6 @@ RPC.prototype.gettxoutproof = co(function* gettxoutproof(args) {
|
||||
});
|
||||
|
||||
RPC.prototype.verifytxoutproof = co(function* verifytxoutproof(args) {
|
||||
var now = this.network.now();
|
||||
var res = [];
|
||||
var i, block, hash, entry;
|
||||
|
||||
@ -1250,7 +1249,7 @@ RPC.prototype.verifytxoutproof = co(function* verifytxoutproof(args) {
|
||||
|
||||
block = MerkleBlock.fromRaw(toString(args[0]), 'hex');
|
||||
|
||||
if (!block.verify(now))
|
||||
if (!block.verify())
|
||||
return res;
|
||||
|
||||
entry = yield this.chain.db.getEntry(block.hash('hex'));
|
||||
@ -1311,7 +1310,6 @@ RPC.prototype._submitwork = co(function* _submitwork(data) {
|
||||
});
|
||||
|
||||
RPC.prototype.__submitwork = co(function* _submitwork(data) {
|
||||
var now = this.network.now();
|
||||
var attempt = this.attempt;
|
||||
var block, header, cb, cur;
|
||||
|
||||
@ -1333,7 +1331,7 @@ RPC.prototype.__submitwork = co(function* _submitwork(data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!header.verify(now))
|
||||
if (!header.verify())
|
||||
return false;
|
||||
|
||||
cb = this.coinbase[header.merkleRoot];
|
||||
@ -4012,7 +4010,6 @@ RPC.prototype.walletpassphrase = co(function* walletpassphrase(args) {
|
||||
});
|
||||
|
||||
RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) {
|
||||
var now = this.network.now();
|
||||
var tx, block, label, height;
|
||||
|
||||
if (args.help || args.length < 2 || args.length > 3) {
|
||||
@ -4032,7 +4029,7 @@ RPC.prototype.importprunedfunds = co(function* importprunedfunds(args) {
|
||||
if (args.length === 3)
|
||||
label = toString(args[2]);
|
||||
|
||||
if (!block.verify(now))
|
||||
if (!block.verify())
|
||||
throw new RPCError('Invalid proof.');
|
||||
|
||||
if (!block.hasTX(tx))
|
||||
|
||||
@ -52,8 +52,8 @@ function CompactBlock(options) {
|
||||
|
||||
util.inherits(CompactBlock, AbstractBlock);
|
||||
|
||||
CompactBlock.prototype._verify = function _verify(now, ret) {
|
||||
return this.verifyHeaders(now, ret);
|
||||
CompactBlock.prototype._verify = function _verify(ret) {
|
||||
return this.verifyHeaders(ret);
|
||||
};
|
||||
|
||||
CompactBlock.prototype.fromOptions = function fromOptions(options) {
|
||||
|
||||
@ -756,12 +756,11 @@ Pool.prototype._handleHeaders = co(function* _handleHeaders(headers, peer) {
|
||||
*/
|
||||
|
||||
Pool.prototype.__handleHeaders = co(function* _handleHeaders(headers, peer) {
|
||||
var i, now, ret, header, hash, last;
|
||||
var i, ret, header, hash, last;
|
||||
|
||||
if (!this.options.headers)
|
||||
return;
|
||||
|
||||
now = this.network.now();
|
||||
ret = new VerifyResult();
|
||||
|
||||
this.logger.debug(
|
||||
@ -787,7 +786,7 @@ Pool.prototype.__handleHeaders = co(function* _handleHeaders(headers, peer) {
|
||||
throw new Error('Bad header chain.');
|
||||
}
|
||||
|
||||
if (!header.verify(now, ret)) {
|
||||
if (!header.verify(ret)) {
|
||||
peer.reject(header, 'invalid', ret.reason, 100);
|
||||
throw new Error('Invalid header.');
|
||||
}
|
||||
|
||||
@ -187,11 +187,11 @@ AbstractBlock.prototype.abbr = function abbr(writer) {
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
AbstractBlock.prototype.verify = function verify(now, ret) {
|
||||
AbstractBlock.prototype.verify = function verify(ret) {
|
||||
var valid = this._valid;
|
||||
|
||||
if (valid == null) {
|
||||
valid = this._verify(now, ret);
|
||||
valid = this._verify(ret);
|
||||
if (!this.mutable)
|
||||
this._valid = valid;
|
||||
}
|
||||
@ -202,16 +202,12 @@ AbstractBlock.prototype.verify = function verify(now, ret) {
|
||||
/**
|
||||
* Verify the block headers (called by `verify()` in
|
||||
* all objects which inherit from AbstractBlock).
|
||||
* @param {Number|null} - Adjusted time.
|
||||
* @param {Object?} ret - Return object, may be
|
||||
* set with properties `reason` and `score`.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
AbstractBlock.prototype.verifyHeaders = function verifyHeaders(now, ret) {
|
||||
if (!now)
|
||||
now = util.now();
|
||||
|
||||
AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
|
||||
if (!ret)
|
||||
ret = new VerifyResult();
|
||||
|
||||
@ -222,13 +218,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(now, ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check timestamp against adjusted-time + 2 hours.
|
||||
if (this.ts > now + 2 * 60 * 60) {
|
||||
ret.reason = 'time-too-new';
|
||||
ret.score = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@ -427,13 +427,12 @@ Block.prototype.getCommitmentHash = function getCommitmentHash(enc) {
|
||||
* Do non-contextual verification on the block. Including checking the block
|
||||
* size, the coinbase and the merkle root. This is consensus-critical.
|
||||
* @alias Block#verify
|
||||
* @param {Number|null} - Adjusted time.
|
||||
* @param {Object?} ret - Return object, may be
|
||||
* set with properties `reason` and `score`.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Block.prototype._verify = function _verify(now, ret) {
|
||||
Block.prototype._verify = function _verify(ret) {
|
||||
var sigops = 0;
|
||||
var scale = constants.WITNESS_SCALE_FACTOR;
|
||||
var i, tx, merkle;
|
||||
@ -441,11 +440,28 @@ Block.prototype._verify = function _verify(now, ret) {
|
||||
if (!ret)
|
||||
ret = new VerifyResult();
|
||||
|
||||
if (!this.verifyHeaders(now, ret))
|
||||
if (!this.verifyHeaders(ret))
|
||||
return false;
|
||||
|
||||
// Size can't be bigger than MAX_BLOCK_SIZE
|
||||
if (this.txs.length > constants.block.MAX_SIZE
|
||||
// Check merkle root
|
||||
merkle = this.createMerkleRoot('hex');
|
||||
|
||||
// If the merkle is mutated,
|
||||
// we have duplicate txs.
|
||||
if (!merkle) {
|
||||
ret.reason = 'bad-txns-duplicate';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.merkleRoot !== merkle) {
|
||||
ret.reason = 'bad-txnmrklroot';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.txs.length === 0
|
||||
|| this.txs.length > constants.block.MAX_SIZE
|
||||
|| this.getBaseSize() > constants.block.MAX_SIZE) {
|
||||
ret.reason = 'bad-blk-length';
|
||||
ret.score = 100;
|
||||
@ -483,23 +499,6 @@ Block.prototype._verify = function _verify(now, ret) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check merkle root
|
||||
merkle = this.createMerkleRoot('hex');
|
||||
|
||||
// If the merkle is mutated,
|
||||
// we have duplicate txs.
|
||||
if (!merkle) {
|
||||
ret.reason = 'bad-txns-duplicate';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.merkleRoot !== merkle) {
|
||||
ret.reason = 'bad-txnmrklroot';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -584,12 +583,9 @@ Block.prototype.getPrevout = function getPrevout() {
|
||||
var prevout = {};
|
||||
var i, j, tx, input;
|
||||
|
||||
for (i = 0; i < this.txs.length; i++) {
|
||||
for (i = 1; i < this.txs.length; i++) {
|
||||
tx = this.txs[i];
|
||||
|
||||
if (tx.isCoinbase())
|
||||
continue;
|
||||
|
||||
for (j = 0; j < tx.inputs.length; j++) {
|
||||
input = tx.inputs[j];
|
||||
prevout[input.prevout.hash] = true;
|
||||
|
||||
@ -32,14 +32,13 @@ util.inherits(Headers, AbstractBlock);
|
||||
/**
|
||||
* Do non-contextual verification on the headers.
|
||||
* @alias Headers#verify
|
||||
* @param {Number|null} - Adjusted time.
|
||||
* @param {Object?} ret - Return object, may be
|
||||
* set with properties `reason` and `score`.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Headers.prototype._verify = function _verify(now, ret) {
|
||||
return this.verifyHeaders(now, ret);
|
||||
Headers.prototype._verify = function _verify(ret) {
|
||||
return this.verifyHeaders(ret);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -106,14 +106,13 @@ MemBlock.prototype.getSize = function getSize() {
|
||||
/**
|
||||
* Verify the block headers.
|
||||
* @alias MemBlock#verify
|
||||
* @param {Number|null} - Adjusted time.
|
||||
* @param {Object?} ret - Return object, may be
|
||||
* set with properties `reason` and `score`.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
MemBlock.prototype._verify = function _verify(now, ret) {
|
||||
return this.verifyHeaders(now, ret);
|
||||
MemBlock.prototype._verify = function _verify(ret) {
|
||||
return this.verifyHeaders(ret);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -279,17 +279,16 @@ MerkleBlock.prototype.extractTree = function extractTree() {
|
||||
* Do non-contextual verification on the block.
|
||||
* Verify the headers and the partial merkle tree.
|
||||
* @alias MerkleBlock#verify
|
||||
* @param {Number|null} - Adjusted time.
|
||||
* @param {Object?} ret - Return object, may be
|
||||
* set with properties `reason` and `score`.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
MerkleBlock.prototype._verify = function _verify(now, ret) {
|
||||
MerkleBlock.prototype._verify = function _verify(ret) {
|
||||
if (!ret)
|
||||
ret = new VerifyResult();
|
||||
|
||||
if (!this.verifyHeaders(now, ret))
|
||||
if (!this.verifyHeaders(ret))
|
||||
return false;
|
||||
|
||||
if (!this.verifyPartial()) {
|
||||
|
||||
@ -166,7 +166,7 @@ describe('Block', function() {
|
||||
block2.merkleRoot = constants.NULL_HASH;
|
||||
delete block2._valid;
|
||||
var ret = {};
|
||||
assert(!block2.verify(0, ret));
|
||||
assert(!block2.verify(ret));
|
||||
assert.equal(ret.reason, 'bad-txnmrklroot');
|
||||
delete block2._valid;
|
||||
delete block2._hash;
|
||||
@ -179,7 +179,7 @@ describe('Block', function() {
|
||||
mblock2.hash();
|
||||
mblock2.merkleRoot = constants.NULL_HASH;
|
||||
var ret = {};
|
||||
assert(!mblock2.verify(0, ret));
|
||||
assert(!mblock2.verify(ret));
|
||||
assert.equal(ret.reason, 'bad-txnmrklroot');
|
||||
delete mblock2._validPartial;
|
||||
delete mblock2._valid;
|
||||
@ -193,7 +193,7 @@ describe('Block', function() {
|
||||
block2.hash();
|
||||
block2.bits = 403014710;
|
||||
var ret = {};
|
||||
assert(!block2.verify(0, ret));
|
||||
assert(!block2.verify(ret));
|
||||
assert.equal(ret.reason, 'high-hash');
|
||||
delete block2._valid;
|
||||
delete block2._hash;
|
||||
@ -205,7 +205,7 @@ describe('Block', function() {
|
||||
var block2 = new bcoin.block(block);
|
||||
block2.txs.push(block2.txs[block2.txs.length - 1]);
|
||||
var ret = {};
|
||||
assert(!block2.verify(0, ret));
|
||||
assert(!block2.verify(ret));
|
||||
assert.equal(ret.reason, 'bad-txns-duplicate');
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user