diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 9e538f6e..2d5be03c 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -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); diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 1c245a95..ca5f1eff 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -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; diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 1f0fa4d4..844f5f89 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -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); diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 2978edb7..618d2362 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -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); } } } diff --git a/lib/net/bip152.js b/lib/net/bip152.js index 781fd392..c72f576d 100644 --- a/lib/net/bip152.js +++ b/lib/net/bip152.js @@ -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(); diff --git a/lib/net/pool.js b/lib/net/pool.js index 16c697ed..a4f207c0 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -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); }); /** diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index 1ae10678..97781794 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -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} diff --git a/lib/primitives/block.js b/lib/primitives/block.js index 663d2756..c4370bf9 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -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); diff --git a/lib/primitives/headers.js b/lib/primitives/headers.js index f24b0e34..930d33ea 100644 --- a/lib/primitives/headers.js +++ b/lib/primitives/headers.js @@ -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 }; }; diff --git a/lib/primitives/memblock.js b/lib/primitives/memblock.js index e59b867a..6c685f2e 100644 --- a/lib/primitives/memblock.js +++ b/lib/primitives/memblock.js @@ -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; }; diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js index 1aa423d7..1ca37ac6 100644 --- a/lib/primitives/merkleblock.js +++ b/lib/primitives/merkleblock.js @@ -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 || {}; } /* diff --git a/lib/protocol/errors.js b/lib/protocol/errors.js index f8e92f06..fb9dd987 100644 --- a/lib/protocol/errors.js +++ b/lib/protocol/errors.js @@ -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() + ')'; } diff --git a/test/block-test.js b/test/block-test.js index e656a499..41a8a0d2 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -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()); });