|
|
@ -57,10 +57,12 @@ function MerkleBlock(arg) { |
|
|
|
flags: arg.flags |
|
|
|
}; |
|
|
|
} else { |
|
|
|
throw new TypeError('Unrecognized argument for Block'); |
|
|
|
throw new TypeError('Unrecognized argument for MerkleBlock'); |
|
|
|
} |
|
|
|
_.extend(this,info); |
|
|
|
this._validMerkleTree = null; |
|
|
|
this._flagBitsUsed = 0; |
|
|
|
this._hashesUsed = 0; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
@ -140,13 +142,10 @@ MerkleBlock.prototype.toJSON = function toJSON() { |
|
|
|
* @returns {Bool} - True/False whether this MerkleBlock is Valid |
|
|
|
*/ |
|
|
|
MerkleBlock.prototype.validMerkleTree = function validMerkleTree() { |
|
|
|
$.checkState(this.flags instanceof Array, 'MerkleBlock flags is not an array'); |
|
|
|
$.checkState(this.hashes instanceof Array, 'MerkleBlock flags is not an array'); |
|
|
|
var self = this; |
|
|
|
if(this._validMerkleTree === true) { |
|
|
|
return true; |
|
|
|
} else if (this._validMerkleTree === false) { |
|
|
|
return false; |
|
|
|
$.checkState(_.isArray(this.flags), 'MerkleBlock flags is not an array'); |
|
|
|
$.checkState(_.isArray(this.hashes), 'MerkleBlock hashes is not an array'); |
|
|
|
if(_.isBoolean(this._validMerkleTree)) { |
|
|
|
return this._validMerkleTree; |
|
|
|
} |
|
|
|
|
|
|
|
// Can't have more hashes than numTransactions
|
|
|
@ -159,50 +158,58 @@ MerkleBlock.prototype.validMerkleTree = function validMerkleTree() { |
|
|
|
return this._setValidMerkleTree(false); |
|
|
|
} |
|
|
|
|
|
|
|
// Calculate height of tree
|
|
|
|
// From Bitcoin Core merkleblock.h CalcTreeWidth() + CPartialMerkleTree
|
|
|
|
var height = 0; |
|
|
|
while (calcTreeWidth(height) > 1) { |
|
|
|
while (this._calcTreeWidth(height) > 1) { |
|
|
|
height++; |
|
|
|
} |
|
|
|
|
|
|
|
var flagBitsUsed = 0; |
|
|
|
var hashesUsed = 0; |
|
|
|
|
|
|
|
// Modeled after Bitcoin Core merkleblock.h CalcTreeWidth()
|
|
|
|
function calcTreeWidth(height) { |
|
|
|
return (self.numTransactions + (1 << height) - 1) >> height; |
|
|
|
this._flagBitsUsed = 0; |
|
|
|
this._hashesUsed = 0; |
|
|
|
var root = this._traverseMerkleTree(height, 0); |
|
|
|
if(this._hashesUsed !== this.hashes.length) { |
|
|
|
return this._setValidMerkleTree(false); |
|
|
|
} |
|
|
|
return this._setValidMerkleTree(BufferUtil.equals(root, this.header.merkleRoot)); |
|
|
|
} |
|
|
|
|
|
|
|
// Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract()
|
|
|
|
function traverse(depth, pos) { |
|
|
|
if(flagBitsUsed > self.flags.length * 8) { |
|
|
|
/** Traverse a the tree in this MerkleBlock, validating it along the way |
|
|
|
* 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 |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos) { |
|
|
|
if(this._flagBitsUsed > this.flags.length * 8) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
var isParentOfMatch = (this.flags[this._flagBitsUsed >> 3] >>> (this._flagBitsUsed++ & 7)) & 1; |
|
|
|
if(depth === 0 || !isParentOfMatch) { |
|
|
|
if(this._hashesUsed >= this.hashes.length) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
var isParentOfMatch = (self.flags[flagBitsUsed >> 3] >>> (flagBitsUsed++ & 7)) & 1; |
|
|
|
if(depth === 0 || !isParentOfMatch) { |
|
|
|
if(hashesUsed >= self.hashes.length) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
var hash = self.hashes[hashesUsed++]; |
|
|
|
return new Buffer(hash, 'hex'); |
|
|
|
var hash = this.hashes[this._hashesUsed++]; |
|
|
|
return new Buffer(hash, 'hex'); |
|
|
|
} else { |
|
|
|
var left = this._traverseMerkleTree(depth-1, pos*2); |
|
|
|
var right; |
|
|
|
if(pos*2+1 < this._calcTreeWidth(depth-1)) { |
|
|
|
right = this._traverseMerkleTree(depth-1, pos*2+1); |
|
|
|
} else { |
|
|
|
var left = traverse(depth-1, pos*2); |
|
|
|
var right; |
|
|
|
if(pos*2+1 < calcTreeWidth(depth-1)) { |
|
|
|
right = traverse(depth-1, pos*2+1); |
|
|
|
} else { |
|
|
|
right = left; |
|
|
|
} |
|
|
|
return Hash.sha256sha256(new Buffer.concat([left, right])); |
|
|
|
right = left; |
|
|
|
} |
|
|
|
return Hash.sha256sha256(new Buffer.concat([left, right])); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var root = traverse(height, 0); |
|
|
|
if(hashesUsed !== this.hashes.length) { |
|
|
|
return this._setValidMerkleTree(false); |
|
|
|
} |
|
|
|
return this._setValidMerkleTree(BufferUtil.equals(root, this.header.merkleRoot)); |
|
|
|
/** Calculates the width of a merkle tree at a given height. |
|
|
|
* Modeled after Bitcoin Core merkleblock.h CalcTreeWidth() |
|
|
|
* @param {Number} - Height at which we want the tree width |
|
|
|
* @returns {Number} - Width of the tree at a given height |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
MerkleBlock.prototype._calcTreeWidth = function calcTreeWidth(height) { |
|
|
|
return (this.numTransactions + (1 << height) - 1) >> height; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -211,14 +218,11 @@ MerkleBlock.prototype.validMerkleTree = function validMerkleTree() { |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
MerkleBlock.prototype.hasTransaction = function hasTransaction(tx) { |
|
|
|
$.checkArgument(!_.isUndefined(tx), 'No transaction given'); |
|
|
|
$.checkArgument(tx instanceof Transaction |
|
|
|
|| typeof tx === 'string', 'No transaction given'); |
|
|
|
$.checkArgument(!_.isUndefined(tx), 'tx cannot be undefined'); |
|
|
|
$.checkArgument(tx instanceof Transaction || typeof tx === 'string', |
|
|
|
'Invalid tx given, tx must be a "string" or "Transaction"'); |
|
|
|
|
|
|
|
var hash = tx; |
|
|
|
if(tx instanceof Transaction) { |
|
|
|
hash = tx.id; |
|
|
|
} |
|
|
|
var hash = tx.id || tx; |
|
|
|
var revHash = BufferUtil.reverse(new Buffer(hash,'hex')).toString('hex'); |
|
|
|
|
|
|
|
return (this.hashes.indexOf(hash) !== -1 |
|
|
|