block/chain: refactor block verification and caching.

This commit is contained in:
Christopher Jeffrey 2017-02-22 06:35:35 -08:00
parent 179caa2bd8
commit 7688d80e74
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
13 changed files with 256 additions and 301 deletions

View File

@ -224,14 +224,15 @@ Chain.prototype._close = function close() {
* @private
* @param {Block} block
* @param {ChainEntry} prev
* @param {Number} flags
* @returns {Promise} - Returns {@link ContextResult}.
*/
Chain.prototype.verifyContext = co(function* verifyContext(block, prev) {
Chain.prototype.verifyContext = co(function* verifyContext(block, prev, flags) {
var state, view;
// Initial non-contextual verification.
state = yield this.verify(block, prev);
state = yield this.verify(block, prev, flags);
// BIP30 - Verify there are no duplicate txids.
yield this.verifyDuplicates(block, prev, state);
@ -269,20 +270,14 @@ Chain.prototype.verifyBlock = co(function* verifyBlock(block) {
*/
Chain.prototype._verifyBlock = co(function* verifyBlock(block) {
var valid = block._validHeaders;
var result;
var flags = common.flags.DEFAULT_FLAGS;
if (block.prevBlock !== this.tip.hash)
throw new VerifyError(block, 'invalid', 'bad-prevblk', 0);
try {
block._validHeaders = true;
result = yield this.verifyContext(block, this.tip);
} finally {
block._validHeaders = valid;
}
flags &= ~common.flags.VERIFY_POW;
return result;
return yield this.verifyContext(block, this.tip, flags);
});
/**
@ -303,30 +298,27 @@ Chain.prototype.isGenesis = function isGenesis(block) {
* @private
* @param {Block} block
* @param {ChainEntry} prev
* @param {Number} flags
* @returns {Promise} - Returns {@link DeploymentState}.
*/
Chain.prototype.verify = co(function* verify(block, prev) {
Chain.prototype.verify = co(function* verify(block, prev, flags) {
var ret = new VerifyResult();
var now = this.network.now();
var i, err, height, ts, tx, mtp;
var i, height, ts, tx, mtp;
var commit, state, bits;
assert(typeof flags === 'number');
// Non-contextual checks.
if (!block.verify(ret)) {
err = new VerifyError(block,
'invalid',
ret.reason,
ret.score);
// High hash is the only thing an
// adversary couldn't mutate in
// otherwise valid non-contextual
// checks.
if (ret.reason !== 'high-hash')
err.malleated = true;
throw err;
if (flags & common.flags.VERIFY_BODY) {
if (!block.verifyBody(ret)) {
throw new VerifyError(block,
'invalid',
ret.reason,
ret.score,
true);
}
}
// Skip all blocks in spv mode.
@ -357,12 +349,11 @@ Chain.prototype.verify = co(function* verify(block, prev) {
// If this fails we may be able to accept
// the block later.
if (block.ts > now + 2 * 60 * 60) {
err = new VerifyError(block,
throw new VerifyError(block,
'invalid',
'time-too-new',
0);
err.malleated = true;
throw err;
0,
true);
}
// Get the new deployment state.
@ -407,21 +398,19 @@ Chain.prototype.verify = co(function* verify(block, prev) {
// "invalid" if either of these checks
// fail.
if (!block.getWitnessNonce()) {
err = new VerifyError(block,
throw new VerifyError(block,
'invalid',
'bad-witness-nonce-size',
100);
err.malleated = true;
throw err;
100,
true);
}
if (!util.equal(commit, block.createCommitmentHash())) {
err = new VerifyError(block,
throw new VerifyError(block,
'invalid',
'bad-witness-merkle-match',
100);
err.malleated = true;
throw err;
100,
true);
}
}
}
@ -430,12 +419,11 @@ Chain.prototype.verify = co(function* verify(block, prev) {
// witness data cannot contain it.
if (!commit) {
if (block.hasWitness()) {
err = new VerifyError(block,
throw new VerifyError(block,
'invalid',
'unexpected-witness',
100);
err.malleated = true;
throw err;
100,
true);
}
}
@ -881,7 +869,7 @@ Chain.prototype.disconnect = co(function* disconnect(entry) {
if (!block) {
if (!this.options.spv)
throw new Error('Block not found.');
block = entry.toHeader();
block = entry.toHeaders();
}
prev = yield entry.getPrevious();
@ -903,31 +891,33 @@ Chain.prototype.disconnect = co(function* disconnect(entry) {
* in alternate chains when they come in).
* @method
* @param {ChainEntry} entry
* @param {Number} flags
* @returns {Promise}
*/
Chain.prototype.reconnect = co(function* reconnect(entry) {
var block = yield this.db.getBlock(entry.hash);
var flags = common.flags.VERIFY_NONE;
var prev, result;
if (!block) {
if (!this.options.spv)
throw new Error('Block not found.');
block = entry.toHeader();
block = entry.toHeaders();
}
prev = yield entry.getPrevious();
assert(prev);
try {
result = yield this.verifyContext(block, prev);
} catch (e) {
if (e.type === 'VerifyError') {
if (!e.malleated)
result = yield this.verifyContext(block, prev, flags);
} catch (err) {
if (err.type === 'VerifyError') {
if (!err.malleated)
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
throw e;
throw err;
}
yield this.db.reconnect(entry, block, result.view);
@ -951,10 +941,11 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
* @param {ChainEntry} entry
* @param {Block} block
* @param {ChainEntry} prev
* @param {Number} flags
* @returns {Promise}
*/
Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev, flags) {
var result;
// A higher fork has arrived.
@ -982,14 +973,14 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
// now that we're certain its previous
// block is in the chain.
try {
result = yield this.verifyContext(block, prev);
result = yield this.verifyContext(block, prev, flags);
} catch (e) {
if (e.type === 'VerifyError') {
if (!e.malleated)
if (err.type === 'VerifyError') {
if (!err.malleated)
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
throw e;
throw err;
}
// Save block and connect inputs.
@ -1012,21 +1003,22 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
* @param {ChainEntry} entry
* @param {Block} block
* @param {ChainEntry} prev
* @param {Number} flags
* @returns {Promise}
*/
Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev, flags) {
try {
// Do as much verification
// as we can before saving.
yield this.verify(block, prev);
} catch (e) {
if (e.type === 'VerifyError') {
if (!e.malleated)
yield this.verify(block, prev, flags);
} catch (err) {
if (err.type === 'VerifyError') {
if (!err.malleated)
this.setInvalid(entry.hash);
this.emit('invalid', block, entry.height);
}
throw e;
throw err;
}
// Warn of unknown versionbits.
@ -1191,14 +1183,15 @@ Chain.prototype._resetTime = co(function* resetTime(ts) {
* Add a block to the chain, perform all necessary verification.
* @method
* @param {Block} block
* @param {Number} flags
* @returns {Promise}
*/
Chain.prototype.add = co(function* add(block) {
Chain.prototype.add = co(function* add(block, flags) {
var hash = block.hash('hex');
var unlock = yield this.locker.lock(hash);
try {
return yield this._add(block);
return yield this._add(block, flags);
} finally {
unlock();
}
@ -1209,14 +1202,17 @@ Chain.prototype.add = co(function* add(block) {
* @method
* @private
* @param {Block} block
* @param {Number} flags
* @returns {Promise}
*/
Chain.prototype._add = co(function* add(block) {
var ret = new VerifyResult();
Chain.prototype._add = co(function* add(block, flags) {
var initial = true;
var hash, entry, prev, result;
if (flags == null)
flags = common.flags.DEFAULT_FLAGS;
assert(block);
while (block) {
@ -1250,12 +1246,12 @@ Chain.prototype._add = co(function* add(block) {
throw new VerifyError(block, 'duplicate', 'duplicate', 100);
}
// Non-contextual verification.
// If this is a memblock, it will
// only be a POW validation.
if (!block.verify(ret)) {
this.emit('invalid', block, block.getCoinbaseHeight());
throw new VerifyError(block, 'invalid', ret.reason, ret.score);
// Check the POW before doing anything.
if (flags & common.flags.VERIFY_POW) {
if (!block.verifyPOW()) {
this.emit('invalid', block, block.getCoinbaseHeight());
throw new VerifyError(block, 'invalid', 'high-hash', 50);
}
}
// Do we already have this block?
@ -1312,13 +1308,13 @@ Chain.prototype._add = co(function* add(block) {
// connect the inputs.
if (entry.chainwork.cmp(this.tip.chainwork) <= 0) {
// Save block to an alternate chain.
yield this.saveAlternate(entry, block, prev);
yield this.saveAlternate(entry, block, prev, flags);
if (!initial)
this.emit('competitor resolved', block, entry);
} else {
// Attempt to add block to the chain index.
yield this.setBestChain(entry, block, prev);
yield this.setBestChain(entry, block, prev, flags);
if (!initial)
this.emit('resolved', block, entry);

View File

@ -52,3 +52,25 @@ exports.thresholdStates = {
ACTIVE: 3,
FAILED: 4
};
/**
* Verify flags for blocks.
* @enum {Number}
* @default
*/
exports.flags = {
VERIFY_NONE: 0,
VERIFY_POW: 1 << 0,
VERIFY_BODY: 1 << 1,
};
/**
* Default block verify flags.
* @const {Number}
* @default
*/
exports.flags.DEFAULT_FLAGS = 0
| exports.flags.VERIFY_POW
| exports.flags.VERIFY_BODY;

View File

@ -4116,7 +4116,7 @@ RPC.prototype.importprunedfunds = co(function* importprunedfunds(args, help) {
if (!block.verify())
throw new RPCError('Invalid proof.');
if (!block.hasTX(tx))
if (!block.hasTX(tx.hash('hex')))
throw new RPCError('Invalid proof.');
height = yield this.chain.db.getHeight(hash);

View File

@ -804,12 +804,11 @@ Mempool.prototype.verify = co(function* verify(entry, view) {
}
if (this.chain.state.hasWitness()) {
if (!tx.hasStandardWitness(view, ret)) {
ret = new VerifyError(tx,
throw new VerifyError(tx,
'nonstandard',
ret.reason,
ret.score);
ret.malleated = ret.score > 0;
throw ret;
ret.score,
ret.score > 0);
}
}
}

View File

@ -53,6 +53,7 @@ function CompactBlock(options) {
this.idMap = {};
this.count = 0;
this.sipKey = null;
this.totalTX = 0;
if (options)
this.fromOptions(options);
@ -86,6 +87,9 @@ CompactBlock.prototype.fromOptions = function fromOptions(options) {
if (options.count)
this.count = options.count;
if (options.totalTX != null)
this.totalTX = options.totalTX;
this.sipKey = options.sipKey;
this.initKey();
@ -105,15 +109,14 @@ CompactBlock.fromOptions = function fromOptions(options) {
};
/**
* Verify the block headers.
* @alias CompactBlock#verify
* Verify the block.
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
CompactBlock.prototype._verify = function _verify(ret) {
return this.verifyHeaders(ret);
CompactBlock.prototype.verifyBody = function verifyBody(ret) {
return true;
};
/**
@ -484,7 +487,6 @@ CompactBlock.prototype.toBlock = function toBlock() {
block.totalTX = this.totalTX;
block._hash = this._hash;
block._hhash = this._hhash;
block._validHeaders = this._validHeaders;
for (i = 0; i < this.available.length; i++) {
tx = this.available[i];
@ -516,8 +518,6 @@ CompactBlock.prototype.fromBlock = function fromBlock(block, witness, nonce) {
this.totalTX = block.totalTX;
this._hash = block._hash;
this._hhash = block._hhash;
this._validHeaders = true;
this._valid = true;
if (!nonce)
nonce = util.nonce();

View File

@ -14,6 +14,7 @@ var util = require('../utils/util');
var IP = require('../utils/ip');
var co = require('../utils/co');
var common = require('./common');
var chainCommon = require('../blockchain/common');
var NetAddress = require('../primitives/netaddress');
var Address = require('../primitives/address');
var BIP150 = require('./bip150');
@ -1921,6 +1922,8 @@ Pool.prototype.handleSendHeaders = co(function* handleSendHeaders(peer, packet)
*/
Pool.prototype.handleBlock = co(function* handleBlock(peer, packet) {
var flags = chainCommon.flags.DEFAULT_FLAGS;
if (this.options.spv) {
this.logger.warning(
'Peer sent unsolicited block (%s).',
@ -1928,7 +1931,7 @@ Pool.prototype.handleBlock = co(function* handleBlock(peer, packet) {
return;
}
return yield this.addBlock(peer, packet.block);
return yield this.addBlock(peer, packet.block, flags);
});
/**
@ -1940,11 +1943,11 @@ Pool.prototype.handleBlock = co(function* handleBlock(peer, packet) {
* @returns {Promise}
*/
Pool.prototype.addBlock = co(function* addBlock(peer, block) {
Pool.prototype.addBlock = co(function* addBlock(peer, block, flags) {
var hash = block.hash('hex');
var unlock = yield this.locker.lock(hash);
try {
return yield this._addBlock(peer, block);
return yield this._addBlock(peer, block, flags);
} finally {
unlock();
}
@ -1959,7 +1962,7 @@ Pool.prototype.addBlock = co(function* addBlock(peer, block) {
* @returns {Promise}
*/
Pool.prototype._addBlock = co(function* addBlock(peer, block) {
Pool.prototype._addBlock = co(function* addBlock(peer, block, flags) {
var hash = block.hash('hex');
if (!this.syncing)
@ -1976,7 +1979,7 @@ Pool.prototype._addBlock = co(function* addBlock(peer, block) {
peer.blockTime = util.ms();
try {
yield this.chain.add(block);
yield this.chain.add(block, flags);
} catch (err) {
if (err.type === 'VerifyError') {
if (err.reason === 'bad-prevblk') {
@ -2145,6 +2148,7 @@ Pool.prototype.handleTX = co(function* handleTX(peer, packet) {
Pool.prototype._handleTX = co(function* handleTX(peer, packet) {
var tx = packet.tx;
var hash = tx.hash('hex');
var flags = chainCommon.flags.VERIFY_NONE;
var missing;
if (peer.merkleBlock) {
@ -2152,7 +2156,7 @@ Pool.prototype._handleTX = co(function* handleTX(peer, packet) {
if (peer.merkleBlock.hasTX(tx)) {
peer.merkleBlock.addTX(tx);
if (--peer.merkleMatches === 0) {
yield this._addBlock(peer, peer.merkleBlock);
yield this._addBlock(peer, peer.merkleBlock, flags);
peer.merkleTime = -1;
peer.merkleBlock = null;
peer.merkleMatches = 0;
@ -2332,6 +2336,7 @@ Pool.prototype.handleMerkleBlock = co(function* handleMerkleBlock(peer, packet)
Pool.prototype._handleMerkleBlock = co(function* handleMerkleBlock(peer, packet) {
var block = packet.block;
var hash = block.hash('hex');
var flags = chainCommon.flags.VERIFY_NONE;
if (!this.syncing)
return;
@ -2369,14 +2374,14 @@ Pool.prototype._handleMerkleBlock = co(function* handleMerkleBlock(peer, packet)
return;
}
if (block.matches.length === 0) {
yield this._addBlock(peer, block);
if (block.tree.matches.length === 0) {
yield this._addBlock(peer, block, flags);
return;
}
peer.merkleTime = util.ms();
peer.merkleBlock = block;
peer.merkleMatches = block.matches.length;
peer.merkleMatches = block.tree.matches.length;
});
/**
@ -2415,6 +2420,7 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
var block = packet.block;
var hash = block.hash('hex');
var witness = peer.hasWitness();
var flags = chainCommon.flags.VERIFY_BODY;
var result;
if (!this.syncing)
@ -2473,7 +2479,7 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
this.logger.debug(
'Received full compact block %s (%s).',
block.rhash(), peer.hostname());
yield this.addBlock(peer, block.toBlock());
yield this.addBlock(peer, block.toBlock(), flags);
return;
}
@ -2555,6 +2561,7 @@ Pool.prototype.handleGetBlockTxn = co(function* handleGetBlockTxn(peer, packet)
Pool.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) {
var res = packet.response;
var block = peer.compactBlocks.get(res.hash);
var flags = chainCommon.flags.VERIFY_BODY;
if (!block) {
this.logger.debug(
@ -2580,7 +2587,7 @@ Pool.prototype.handleBlockTxn = co(function* handleBlockTxn(peer, packet) {
'Filled compact block %s (%s).',
block.rhash(), peer.hostname());
yield this.addBlock(peer, block.toBlock());
yield this.addBlock(peer, block.toBlock(), flags);
});
/**

View File

@ -10,7 +10,6 @@
var assert = require('assert');
var util = require('../utils/util');
var crypto = require('../crypto/crypto');
var VerifyResult = require('../protocol/errors').VerifyResult;
var StaticWriter = require('../utils/staticwriter');
var InvItem = require('./invitem');
var encoding = require('../utils/encoding');
@ -30,7 +29,6 @@ var consensus = require('../protocol/consensus');
* @property {Number} ts - Timestamp.
* @property {Number} bits
* @property {Number} nonce
* @property {Number} totalTX - Transaction count.
* @property {TX[]} txs - Transaction vector.
* @property {ReversedHash} rhash - Reversed block hash (uint256le).
*/
@ -45,14 +43,10 @@ function AbstractBlock() {
this.ts = 0;
this.bits = 0;
this.nonce = 0;
this.totalTX = 0;
this.txs = null;
this.mutable = false;
this._valid = null;
this._validHeaders = null;
this._hash = null;
this._hhash = null;
this._size = -1;
@ -90,11 +84,6 @@ AbstractBlock.prototype.parseOptions = function parseOptions(options) {
this.bits = options.bits;
this.nonce = options.nonce;
if (options.totalTX != null) {
assert(util.isNumber(options.totalTX));
this.totalTX = options.totalTX;
}
if (options.mutable != null)
this.mutable = !!options.mutable;
@ -115,7 +104,6 @@ AbstractBlock.prototype.parseJSON = function parseJSON(json) {
assert(util.isNumber(json.ts));
assert(util.isNumber(json.bits));
assert(util.isNumber(json.nonce));
assert(util.isNumber(json.totalTX));
this.version = json.version;
this.prevBlock = util.revHex(json.prevBlock);
@ -123,7 +111,6 @@ AbstractBlock.prototype.parseJSON = function parseJSON(json) {
this.ts = json.ts;
this.bits = json.bits;
this.nonce = json.nonce;
this.totalTX = json.totalTX;
return this;
};
@ -136,8 +123,6 @@ AbstractBlock.prototype.parseJSON = function parseJSON(json) {
AbstractBlock.prototype._refresh = function refresh(all) {
var i, tx;
this._valid = null;
this._validHeaders = null;
this._hash = null;
this._hhash = null;
this._size = -1;
@ -234,74 +219,15 @@ AbstractBlock.prototype.parseAbbr = function parseAbbr(br) {
/**
* Verify the block.
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
AbstractBlock.prototype.verify = function verify(ret) {
var valid = this._valid;
if (valid == null) {
valid = this._verify(ret);
if (!this.mutable)
this._valid = valid;
}
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).
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
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();
// Check proof of work.
if (!this.verifyPOW()) {
ret.reason = 'high-hash';
ret.score = 50;
AbstractBlock.prototype.verify = function verify() {
if (!this.verifyPOW())
return false;
if (!this.verifyBody())
return false;
}
return true;
};
@ -315,6 +241,17 @@ AbstractBlock.prototype.verifyPOW = function verifyPOW() {
return consensus.verifyPOW(this.hash(), this.bits);
};
/**
* Verify the block.
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
AbstractBlock.prototype.verifyBody = function verifyBody(ret) {
throw new Error('Abstract method.');
};
/**
* Get little-endian block hash.
* @returns {Hash}

View File

@ -270,9 +270,6 @@ Block.prototype.hasTX = function hasTX(hash) {
Block.prototype.indexOf = function indexOf(hash) {
var i, tx;
if (hash instanceof TX)
hash = hash.hash('hex');
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
if (tx.hash('hex') === hash)
@ -430,7 +427,7 @@ Block.prototype.getCommitmentHash = function getCommitmentHash(enc) {
* @returns {Boolean}
*/
Block.prototype._verify = function _verify(ret) {
Block.prototype.verifyBody = function verifyBody(ret) {
var sigops = 0;
var scale = consensus.WITNESS_SCALE_FACTOR;
var i, tx, merkle;
@ -438,9 +435,6 @@ Block.prototype._verify = function _verify(ret) {
if (!ret)
ret = new VerifyResult();
if (!this.verifyHeaders(ret))
return false;
// Check merkle root.
merkle = this.createMerkleRoot('hex');
@ -634,7 +628,6 @@ Block.prototype.getJSON = function getJSON(network, view, height) {
ts: this.ts,
bits: this.bits,
nonce: this.nonce,
totalTX: this.totalTX,
txs: this.txs.map(function(tx, i) {
return tx.getJSON(network, view, null, i);
}, this)
@ -679,15 +672,15 @@ Block.fromJSON = function fromJSON(json) {
Block.prototype.fromReader = function fromReader(br) {
var witness = 0;
var i, tx;
var i, count, tx;
br.start();
this.parseAbbr(br);
this.totalTX = br.readVarint();
count = br.readVarint();
for (i = 0; i < this.totalTX; i++) {
for (i = 0; i < count; i++) {
tx = TX.fromReader(br);
witness += tx._witness;
this.addTX(tx);

View File

@ -9,7 +9,6 @@
var util = require('../utils/util');
var AbstractBlock = require('./abstractblock');
var encoding = require('../utils/encoding');
var StaticWriter = require('../utils/staticwriter');
var BufferReader = require('../utils/reader');
@ -35,14 +34,13 @@ util.inherits(Headers, AbstractBlock);
/**
* Do non-contextual verification on the headers.
* @alias Headers#verify
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
Headers.prototype._verify = function _verify(ret) {
return this.verifyHeaders(ret);
Headers.prototype.verifyBody = function verifyBody(ret) {
return true;
};
/**
@ -51,7 +49,7 @@ Headers.prototype._verify = function _verify(ret) {
*/
Headers.prototype.getSize = function getSize() {
return 80 + encoding.sizeVarint(this.totalTX);
return 81;
};
/**
@ -61,7 +59,7 @@ Headers.prototype.getSize = function getSize() {
Headers.prototype.toWriter = function toWriter(bw) {
this.writeAbbr(bw);
bw.writeVarint(this.totalTX);
bw.writeVarint(0);
return bw;
};
@ -83,7 +81,7 @@ Headers.prototype.toRaw = function toRaw() {
Headers.prototype.fromReader = function fromReader(br) {
this.parseAbbr(br);
this.totalTX = br.readVarint();
br.readVarint();
return this;
};
@ -172,7 +170,6 @@ Headers.fromAbbr = function fromAbbr(data, enc) {
Headers.fromEntry = function fromEntry(entry) {
var headers = new Headers(entry);
headers._hash = new Buffer(entry.hash, 'hex');
headers._valid = true;
return headers;
};
@ -195,7 +192,6 @@ Headers.fromBlock = function fromBlock(block) {
var headers = new Headers(block);
headers._hash = block._hash;
headers._hhash = block._hhash;
headers._valid = true;
return headers;
};
@ -229,8 +225,7 @@ Headers.prototype.getJSON = function getJSON(network, view, height) {
merkleRoot: util.revHex(this.merkleRoot),
ts: this.ts,
bits: this.bits,
nonce: this.nonce,
totalTX: this.totalTX
nonce: this.nonce
};
};
@ -283,8 +278,7 @@ Headers.prototype.format = function format(view, height) {
merkleRoot: util.revHex(this.merkleRoot),
ts: this.ts,
bits: this.bits,
nonce: this.nonce,
totalTX: this.totalTX
nonce: this.nonce
};
};

View File

@ -73,15 +73,14 @@ MemBlock.prototype.getSize = function getSize() {
};
/**
* Verify the block headers.
* @alias MemBlock#verify
* Verify the block.
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
MemBlock.prototype._verify = function _verify(ret) {
return this.verifyHeaders(ret);
MemBlock.prototype.verifyBody = function verifyBody(ret) {
return true;
};
/**
@ -103,25 +102,25 @@ MemBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
MemBlock.prototype.fromRaw = function fromRaw(data) {
var br = new BufferReader(data, true);
var height = -1;
var inCount, input;
var count, script;
this.parseAbbr(br);
this.totalTX = br.readVarint();
count = br.readVarint();
if (this.version > 1 && this.totalTX > 0) {
if (this.version > 1 && count > 0) {
br.seek(4);
inCount = br.readVarint();
count = br.readVarint();
if (inCount === 0) {
if (count === 0) {
if (br.readU8() !== 0)
inCount = br.readVarint();
count = br.readVarint();
}
if (inCount > 0) {
if (count > 0) {
br.seek(36);
input = br.readVarBytes();
height = Script.getCoinbaseHeight(input);
script = br.readVarBytes();
height = Script.getCoinbaseHeight(script);
}
}
@ -171,7 +170,6 @@ MemBlock.prototype.toBlock = function toBlock() {
block._hash = this._hash;
block._hhash = this._hhash;
block._cbHeight = this._cbHeight;
block._validHeaders = this._validHeaders;
this._raw = null;
return block;
};

View File

@ -17,7 +17,6 @@ var StaticWriter = require('../utils/staticwriter');
var encoding = require('../utils/encoding');
var consensus = require('../protocol/consensus');
var Headers = require('./headers');
var TX = require('./tx');
var DUMMY = new Buffer([0]);
/**
@ -37,12 +36,8 @@ function MerkleBlock(options) {
this.hashes = [];
this.flags = DUMMY;
// List of matched TXs
this.map = {};
this.matches = [];
this._validPartial = null;
// TXs that will be pushed on
this.totalTX = 0;
this.tree = null;
this.txs = [];
if (options)
@ -65,6 +60,7 @@ MerkleBlock.prototype.fromOptions = function fromOptions(options) {
assert(options, 'MerkleBlock data is required.');
assert(Array.isArray(options.hashes));
assert(Buffer.isBuffer(options.flags));
assert(util.isUInt32(options.totalTX));
if (options.hashes) {
for (i = 0; i < options.hashes.length; i++) {
@ -78,6 +74,9 @@ MerkleBlock.prototype.fromOptions = function fromOptions(options) {
if (options.flags)
this.flags = options.flags;
if (options.totalTX != null)
this.totalTX = options.totalTX;
return this;
};
@ -97,9 +96,7 @@ MerkleBlock.fromOptions = function fromOptions(data) {
*/
MerkleBlock.prototype.refresh = function refresh(all) {
this.map = {};
this.matches.length = 0;
this._validPartial = null;
this.tree = null;
this._refresh(all);
};
@ -110,8 +107,9 @@ MerkleBlock.prototype.refresh = function refresh(all) {
*/
MerkleBlock.prototype.addTX = function addTX(tx) {
var tree = this.getTree();
var hash = tx.hash('hex');
var index = this.map[hash];
var index = tree.map[hash];
this.txs.push(tx);
@ -120,7 +118,7 @@ MerkleBlock.prototype.addTX = function addTX(tx) {
/**
* Test the block's _matched_ transaction vector against a hash.
* @param {Hash|TX} hash
* @param {Hash} hash
* @returns {Boolean}
*/
@ -130,19 +128,13 @@ MerkleBlock.prototype.hasTX = function hasTX(hash) {
/**
* Test the block's _matched_ transaction vector against a hash.
* @param {Hash|TX} hash
* @param {Hash} hash
* @returns {Number} Index.
*/
MerkleBlock.prototype.indexOf = function indexOf(hash) {
var index;
if (hash instanceof TX)
hash = hash.hash('hex');
this.verifyPartial();
index = this.map[hash];
var tree = this.getTree();
var index = tree.map[hash];
if (index == null)
return -1;
@ -150,6 +142,25 @@ MerkleBlock.prototype.indexOf = function indexOf(hash) {
return index;
};
/**
* Do non-contextual verification on the block.
* Verify the headers and the partial merkle tree.
* @alias MerkleBlock#verify
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
MerkleBlock.prototype.verify = function verify(ret) {
if (!this.verifyPOW())
return false;
if (!this.verifyBody())
return false;
return true;
};
/**
* Verify the partial merkletree. Push leaves onto
* {@link MerkleBlock#tx} and into {@link MerkleBlock#map}.
@ -157,31 +168,38 @@ MerkleBlock.prototype.indexOf = function indexOf(hash) {
* @returns {Boolean}
*/
MerkleBlock.prototype.verifyPartial = function verifyPartial() {
var tree;
MerkleBlock.prototype.verifyBody = function verifyBody(ret) {
var tree = this.getTree();
if (this._validPartial != null)
return this._validPartial;
try {
tree = this.extractTree();
} catch (e) {
this._validPartial = false;
return false;
}
if (!ret)
ret = new VerifyResult();
if (tree.root !== this.merkleRoot) {
this._validPartial = false;
ret.reason = 'bad-txnmrklroot';
ret.score = 100;
return false;
}
this.matches = tree.matches;
this.map = tree.map;
this._validPartial = true;
return true;
};
/**
* Extract the matches from partial merkle
* tree and calculate merkle root.
* @returns {Object}
*/
MerkleBlock.prototype.getTree = function getTree() {
if (!this.tree) {
try {
this.tree = this.extractTree();
} catch (e) {
this.tree = new PartialTree();
}
}
return this.tree;
};
/**
* Extract the matches from partial merkle
* tree and calculate merkle root.
@ -279,31 +297,6 @@ MerkleBlock.prototype.extractTree = function extractTree() {
return new PartialTree(root, matches, indexes, map);
};
/**
* Do non-contextual verification on the block.
* Verify the headers and the partial merkle tree.
* @alias MerkleBlock#verify
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
MerkleBlock.prototype._verify = function _verify(ret) {
if (!ret)
ret = new VerifyResult();
if (!this.verifyHeaders(ret))
return false;
if (!this.verifyPartial()) {
ret.reason = 'bad-txnmrklroot';
ret.score = 100;
return false;
}
return true;
};
/**
* Extract the coinbase height (always -1).
* @returns {Number}
@ -347,7 +340,7 @@ MerkleBlock.prototype.format = function format(view, height) {
return hash.toString('hex');
}),
flags: this.flags,
map: this.map,
map: this.getTree().map,
txs: this.txs.length
};
};
@ -508,6 +501,7 @@ MerkleBlock.prototype.fromJSON = function fromJSON(json) {
assert(json, 'MerkleBlock data is required.');
assert(Array.isArray(json.hashes));
assert(typeof json.flags === 'string');
assert(util.isUInt32(json.totalTX));
this.parseJSON(json);
@ -518,6 +512,8 @@ MerkleBlock.prototype.fromJSON = function fromJSON(json) {
this.flags = new Buffer(json.flags, 'hex');
this.totalTX = json.totalTX;
return this;
};
@ -687,8 +683,8 @@ MerkleBlock.fromMatches = function fromMatches(block, matches) {
MerkleBlock.isMerkleBlock = function isMerkleBlock(obj) {
return obj
&& obj.flags !== undefined
&& typeof obj.verifyPartial === 'function';
&& Buffer.isBuffer(obj.flags)
&& typeof obj.verifyBody === 'function';
};
/**
@ -705,10 +701,10 @@ MerkleBlock.prototype.toHeaders = function toHeaders() {
*/
function PartialTree(root, matches, indexes, map) {
this.root = root.toString('hex');
this.matches = matches;
this.indexes = indexes;
this.map = map;
this.root = root ? root.toString('hex') : encoding.NULL_HASH;
this.matches = matches || [];
this.indexes = indexes || [];
this.map = map || {};
}
/*

View File

@ -26,14 +26,16 @@ var util = require('../utils/util');
* @param {String} reason - Reject packet reason.
* @param {Number} score - Ban score increase
* (can be -1 for no reject packet).
* @param {Boolean} malleated
* @property {String} code
* @property {Buffer} hash
* @property {Number} height (will be the coinbase height if not present).
* @property {Number} score
* @property {String} message
* @property {Boolean} malleated
*/
function VerifyError(msg, code, reason, score) {
function VerifyError(msg, code, reason, score, malleated) {
Error.call(this);
if (Error.captureStackTrace)
@ -48,13 +50,11 @@ function VerifyError(msg, code, reason, score) {
this.code = code;
this.reason = reason;
this.score = score;
this.hash = msg.hash();
this.malleated = false;
this.malleated = malleated || false;
this.message = 'Verification failure: ' + reason
+ ' (code=' + code + ', score=' + score
+ ', hash=' + util.revHex(this.hash.toString('hex'))
+ ', hash=' + msg.rhash()
+ ')';
}

View File

@ -100,17 +100,24 @@ describe('Block', function() {
this.timeout(10000);
it('should parse partial merkle tree', function() {
var tree;
assert(mblock.verifyPOW());
assert(mblock.verifyBody());
assert(mblock.verify());
assert.equal(mblock.matches.length, 2);
tree = mblock.getTree();
assert.equal(tree.matches.length, 2);
assert.equal(mblock.hash('hex'),
'8cc72c02a958de5a8b35a23bb7e3bced8bf840cc0a4e1c820000000000000000');
assert.equal(mblock.rhash(),
'0000000000000000821c4e0acc40f88bedbce3b73ba2358b5ade58a9022cc78c');
assert.equal(
mblock.matches[0].toString('hex'),
tree.matches[0].toString('hex'),
'7393f84cd04ca8931975c66282ebf1847c78d8de6c2578d4f9bae23bc6f30857');
assert.equal(
mblock.matches[1].toString('hex'),
tree.matches[1].toString('hex'),
'ec8c51de3170301430ec56f6703533d9ea5b05c6fa7068954bcb90eed8c2ee5c');
});
@ -181,7 +188,7 @@ describe('Block', function() {
mblock2 = MerkleBlock.fromBlock(block, filter);
assert(mblock2.verifyPartial());
assert(mblock2.verifyBody());
assert.deepEqual(mblock2.toRaw(), mblock.toRaw());
});
@ -230,10 +237,11 @@ describe('Block', function() {
it('should fail with a bad merkle root', function() {
var block2 = new Block(block);
var ret = {};
block2.hash();
block2.merkleRoot = encoding.NULL_HASH;
block2._validHeaders = null;
assert(!block2.verify(ret));
block2.refresh();
assert(!block2.verifyPOW());
assert(!block2.verifyBody(ret));
assert(!block2.verify());
assert.equal(ret.reason, 'bad-txnmrklroot');
block2.merkleRoot = block.merkleRoot;
block2.refresh();
@ -243,9 +251,11 @@ describe('Block', function() {
it('should fail on merkle block with a bad merkle root', function() {
var mblock2 = new MerkleBlock(mblock);
var ret = {};
mblock2.hash();
mblock2.merkleRoot = encoding.NULL_HASH;
assert(!mblock2.verify(ret));
mblock2.refresh();
assert(!mblock2.verifyPOW());
assert(!mblock2.verifyBody(ret));
assert(!mblock2.verify());
assert.equal(ret.reason, 'bad-txnmrklroot');
mblock2.merkleRoot = mblock.merkleRoot;
mblock2.refresh();
@ -254,11 +264,11 @@ describe('Block', function() {
it('should fail with a low target', function() {
var block2 = new Block(block);
var ret = {};
block2.hash();
block2.bits = 403014710;
assert(!block2.verify(ret));
assert.equal(ret.reason, 'high-hash');
block2.refresh();
assert(!block2.verifyPOW());
assert(block2.verifyBody());
assert(!block2.verify());
block2.bits = block.bits;
block2.refresh();
assert(block2.verify());
@ -268,12 +278,15 @@ describe('Block', function() {
var block2 = new Block(block);
var ret = {};
block2.txs.push(block2.txs[block2.txs.length - 1]);
assert(!block2.verify(ret));
block2.refresh();
assert(!block2.verifyBody(ret));
assert.equal(ret.reason, 'bad-txns-duplicate');
});
it('should verify with headers', function() {
var headers = new Headers(block);
assert(headers.verifyPOW());
assert(headers.verifyBody());
assert(headers.verify());
});