diff --git a/lib/block/merkleblock.js b/lib/block/merkleblock.js index d2012664a..a95d64ec1 100644 --- a/lib/block/merkleblock.js +++ b/lib/block/merkleblock.js @@ -154,16 +154,11 @@ MerkleBlock.prototype.validMerkleTree = function validMerkleTree() { return false; } - var height = 0; - while (this._calcTreeWidth(height) > 1) { - height++; - } - - this._flagBitsUsed = 0; - this._hashesUsed = 0; - var root = this._traverseMerkleTree(height, 0); - if(this._hashesUsed !== this.hashes.length) { - false; + var height = this._calcTreeHeight(); + var opts = { hashesUsed: 0, flagBitsUsed: 0 }; + var root = this._traverseMerkleTree(height, 0, opts); + if(opts.hashesUsed !== this.hashes.length) { + return false; } return BufferUtil.equals(root, this.header.merkleRoot); } @@ -172,27 +167,37 @@ MerkleBlock.prototype.validMerkleTree = function validMerkleTree() { * Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract() * @param {Number} - Current height * @param {Number} - Current position in the tree - * @returns {Buffer|null} - Buffer containing the Merkle Hash far that height + * @param {Object} - Object with values that need to be mutated throughout the traversal + * @param {flagBitsUsed} - Number of flag bits used, should start at 0 + * @param {hashesUsed} - Number of hashes used, should start at 0 + * @param {tx} - Will finish populated by transactions found during traversal + * @returns {Buffer|null} - Buffer containing the Merkle Hash for that height * @private */ -MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos) { - if(this._flagBitsUsed > this.flags.length * 8) { +MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos, opts) { + opts = opts || {}; + opts.txs = opts.txs || []; + opts.flagBitsUsed = opts.flagBitsUsed || 0; + opts.hashesUsed = opts.hashesUsed || 0; + + if(opts.flagBitsUsed > this.flags.length * 8) { return null; } - var isParentOfMatch = (this.flags[this._flagBitsUsed >> 3] >>> (this._flagBitsUsed++ & 7)) & 1; + var isParentOfMatch = (this.flags[opts.flagBitsUsed >> 3] >>> (opts.flagBitsUsed++ & 7)) & 1; if(depth === 0 || !isParentOfMatch) { - if(this._hashesUsed >= this.hashes.length) { + if(opts.hashesUsed >= this.hashes.length) { return null; } - var hash = this.hashes[this._hashesUsed++]; + var hash = this.hashes[opts.hashesUsed++]; + if(depth === 0 && isParentOfMatch) { + opts.txs.push(hash); + } return new Buffer(hash, 'hex'); } else { - var left = this._traverseMerkleTree(depth-1, pos*2); - var right; + var left = this._traverseMerkleTree(depth-1, pos*2, opts); + var right = left;; if(pos*2+1 < this._calcTreeWidth(depth-1)) { - right = this._traverseMerkleTree(depth-1, pos*2+1); - } else { - right = left; + right = this._traverseMerkleTree(depth-1, pos*2+1, opts); } return Hash.sha256sha256(new Buffer.concat([left, right])); } @@ -208,6 +213,19 @@ MerkleBlock.prototype._calcTreeWidth = function calcTreeWidth(height) { return (this.numTransactions + (1 << height) - 1) >> height; } +/** Calculates the height of the merkle tree in this MerkleBlock + * @param {Number} - Height at which we want the tree width + * @returns {Number} - Height of the merkle tree in this MerkleBlock + * @private + */ +MerkleBlock.prototype._calcTreeHeight = function calcTreeHeight() { + var height = 0; + while (this._calcTreeWidth(height) > 1) { + height++; + } + return height; +} + /** * @param {Transaction|String} - Transaction or Transaction ID Hash * @returns {Bool} - return true/false if this MerkleBlock has the TX or not @@ -218,11 +236,16 @@ MerkleBlock.prototype.hasTransaction = function hasTransaction(tx) { $.checkArgument(tx instanceof Transaction || typeof tx === 'string', 'Invalid tx given, tx must be a "string" or "Transaction"'); - var hash = tx.id || tx; - var revHash = BufferUtil.reverse(new Buffer(hash,'hex')).toString('hex'); + var hash = tx; + if(tx instanceof Transaction) { + // We need to reverse the id hash for the lookup + hash = BufferUtil.reverse(new Buffer(tx.id, 'hex')).toString('hex'); + } - return (this.hashes.indexOf(hash) !== -1 - || this.hashes.indexOf(revHash) !== -1); + var txs = []; + var height = this._calcTreeHeight(); + this._traverseMerkleTree(height, 0, { txs: txs }); + return txs.indexOf(hash) !== -1 } /** diff --git a/test/merkleblock.js b/test/merkleblock.js index 236f863fa..ed020f36f 100644 --- a/test/merkleblock.js +++ b/test/merkleblock.js @@ -3,7 +3,6 @@ var bitcore = require('..'); var MerkleBlock = bitcore.MerkleBlock; var BufferReader = bitcore.encoding.BufferReader; var BufferWriter = bitcore.encoding.BufferWriter; -var BufferUtil = bitcore.util.buffer; var Transaction = bitcore.Transaction; var data = require('./data/merkleblocks.js'); var transactionVector = require('./data/tx_creation'); @@ -154,7 +153,7 @@ describe('MerkleBlock', function() { it('should find transactions via hash string', function() { var json = data.JSON[0]; - var txId = BufferUtil.reverse(new Buffer(json.hashes[1],'hex')).toString('hex'); + var txId = new Buffer(json.hashes[1],'hex').toString('hex'); var b = MerkleBlock(JSON.stringify(json)); b.hasTransaction(txId).should.equal(true); b.hasTransaction(txId + 'abcd').should.equal(false);