Browse Source

_traverseMerkleTree state cleanup + improve hasTransactions()

patch-2
William Wolf 10 years ago
parent
commit
3eb95b6576
  1. 73
      lib/block/merkleblock.js
  2. 3
      test/merkleblock.js

73
lib/block/merkleblock.js

@ -154,16 +154,11 @@ MerkleBlock.prototype.validMerkleTree = function validMerkleTree() {
return false; return false;
} }
var height = 0; var height = this._calcTreeHeight();
while (this._calcTreeWidth(height) > 1) { var opts = { hashesUsed: 0, flagBitsUsed: 0 };
height++; var root = this._traverseMerkleTree(height, 0, opts);
} if(opts.hashesUsed !== this.hashes.length) {
return false;
this._flagBitsUsed = 0;
this._hashesUsed = 0;
var root = this._traverseMerkleTree(height, 0);
if(this._hashesUsed !== this.hashes.length) {
false;
} }
return BufferUtil.equals(root, this.header.merkleRoot); return BufferUtil.equals(root, this.header.merkleRoot);
} }
@ -172,27 +167,37 @@ MerkleBlock.prototype.validMerkleTree = function validMerkleTree() {
* Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract() * Modeled after Bitcoin Core merkleblock.cpp TraverseAndExtract()
* @param {Number} - Current height * @param {Number} - Current height
* @param {Number} - Current position in the tree * @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 * @private
*/ */
MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos) { MerkleBlock.prototype._traverseMerkleTree = function traverseMerkleTree(depth, pos, opts) {
if(this._flagBitsUsed > this.flags.length * 8) { 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; 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(depth === 0 || !isParentOfMatch) {
if(this._hashesUsed >= this.hashes.length) { if(opts.hashesUsed >= this.hashes.length) {
return null; 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'); return new Buffer(hash, 'hex');
} else { } else {
var left = this._traverseMerkleTree(depth-1, pos*2); var left = this._traverseMerkleTree(depth-1, pos*2, opts);
var right; var right = left;;
if(pos*2+1 < this._calcTreeWidth(depth-1)) { if(pos*2+1 < this._calcTreeWidth(depth-1)) {
right = this._traverseMerkleTree(depth-1, pos*2+1); right = this._traverseMerkleTree(depth-1, pos*2+1, opts);
} else {
right = left;
} }
return Hash.sha256sha256(new Buffer.concat([left, right])); 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; 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 * @param {Transaction|String} - Transaction or Transaction ID Hash
* @returns {Bool} - return true/false if this MerkleBlock has the TX or not * @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', $.checkArgument(tx instanceof Transaction || typeof tx === 'string',
'Invalid tx given, tx must be a "string" or "Transaction"'); 'Invalid tx given, tx must be a "string" or "Transaction"');
var hash = tx.id || tx; var hash = tx;
var revHash = BufferUtil.reverse(new Buffer(hash,'hex')).toString('hex'); 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 var txs = [];
|| this.hashes.indexOf(revHash) !== -1); var height = this._calcTreeHeight();
this._traverseMerkleTree(height, 0, { txs: txs });
return txs.indexOf(hash) !== -1
} }
/** /**

3
test/merkleblock.js

@ -3,7 +3,6 @@ var bitcore = require('..');
var MerkleBlock = bitcore.MerkleBlock; var MerkleBlock = bitcore.MerkleBlock;
var BufferReader = bitcore.encoding.BufferReader; var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter; var BufferWriter = bitcore.encoding.BufferWriter;
var BufferUtil = bitcore.util.buffer;
var Transaction = bitcore.Transaction; var Transaction = bitcore.Transaction;
var data = require('./data/merkleblocks.js'); var data = require('./data/merkleblocks.js');
var transactionVector = require('./data/tx_creation'); var transactionVector = require('./data/tx_creation');
@ -154,7 +153,7 @@ describe('MerkleBlock', function() {
it('should find transactions via hash string', function() { it('should find transactions via hash string', function() {
var json = data.JSON[0]; 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)); var b = MerkleBlock(JSON.stringify(json));
b.hasTransaction(txId).should.equal(true); b.hasTransaction(txId).should.equal(true);
b.hasTransaction(txId + 'abcd').should.equal(false); b.hasTransaction(txId + 'abcd').should.equal(false);

Loading…
Cancel
Save