/** * merkleblock.js - merkleblock object for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * https://github.com/indutny/bcoin */ var bcoin = require('../bcoin'); var utils = require('./utils'); /** * MerkleBlock */ function MerkleBlock(data) { if (!(this instanceof MerkleBlock)) return new MerkleBlock(data); bcoin.abstractblock.call(this, data); this.type = 'merkleblock'; this.hashes = (data.hashes || []).map(function(hash) { return utils.toBuffer(hash, 'hex'); }); this.flags = data.flags || []; // List of matched TXs this.txMap = {}; this.tx = []; // TXs that will be pushed on this.txs = []; } utils.inherits(MerkleBlock, bcoin.abstractblock); MerkleBlock.prototype.render = function render() { return this.getRaw(); }; MerkleBlock.prototype.renderNormal = function renderNormal() { return this.getRaw(); }; MerkleBlock.prototype.renderWitness = function renderWitness() { return this.getRaw(); }; MerkleBlock.prototype.getSize = function getSize() { if (this._size == null) this.getRaw(); return this._size; }; MerkleBlock.prototype.getRaw = function getRaw() { if (!this._raw) { this._raw = bcoin.protocol.framer.merkleBlock(this); this._size = this._raw.length; } return this._raw; }; MerkleBlock.prototype.hasTX = function hasTX(hash) { if (hash instanceof bcoin.tx) hash = hash.hash('hex'); return this.txMap[hash] === true; }; MerkleBlock.prototype._verifyPartial = function _verifyPartial() { var height = 0; var tx = []; var txMap = {}; var j = 0; var hashes = this.hashes; var flags = this.flags; var i, root; // Count leaves for (i = this.totalTX; i > 0; i >>= 1) height++; if (this.totalTX > (1 << (height - 1))) height++; function visit(depth) { var flag, left, right; if (i === flags.length * 8 || j === hashes.length) return null; flag = (flags[i >> 3] >>> (i & 7)) & 1; i++; if (flag === 0 || depth === height) { if (depth === height) { tx.push(utils.toHex(hashes[j])); txMap[tx[tx.length - 1]] = true; } return hashes[j++]; } // Go deeper left = visit(depth + 1); if (!left) return null; right = visit(depth + 1); if (right && utils.isEqual(right, left)) return null; if (!right) right = left; return utils.dsha256(Buffer.concat([left, right])); } root = utils.toHex(visit(1)); if (!root || root !== this.merkleRoot) return false; this.tx = tx; this.txMap = txMap; return true; }; MerkleBlock.prototype._verify = function _verify(ret) { if (!ret) ret = {}; if (!this.verifyHeaders(ret)) return false; // Verify the partial merkle tree if we are a merkleblock. if (!this._verifyPartial()) { ret.reason = 'bad-txnmrklroot'; ret.score = 100; return false; } return true; }; MerkleBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { return -1; }; MerkleBlock.prototype.inspect = function inspect() { var copy = bcoin.merkleblock(this); copy.__proto__ = null; delete copy._raw; delete copy._chain; copy.hash = this.hash('hex'); copy.rhash = this.rhash; copy.date = new Date((copy.ts || 0) * 1000).toISOString(); return copy; }; MerkleBlock.prototype.toRaw = function toRaw(enc) { var data; data = this.render(); if (enc === 'hex') data = utils.toHex(data); return data; }; MerkleBlock._fromRaw = function _fromRaw(data, enc) { if (enc === 'hex') data = new Buffer(data, 'hex'); return bcoin.protocol.parser.parseMerkleBlock(data); }; MerkleBlock.fromRaw = function fromRaw(data, enc) { return new MerkleBlock(MerkleBlock._fromRaw(data, enc)); }; MerkleBlock.isMerkleBlock = function isMerkleBlock(obj) { return obj && Array.isArray(obj.flags) && typeof obj._verifyPartial === 'function'; }; MerkleBlock.fromBlock = function fromBlock(block, bloom) { var matches = []; var txs = []; var leaves = []; var bits = []; var hashes = []; var i, tx, totalTX, height, flags, p; for (i = 0; i < block.txs.length; i++) { tx = block.txs[i]; if (tx.isWatched(bloom)) { matches.push(1); txs.push(tx); } else { matches.push(0); } leaves.push(tx.hash()); } totalTX = leaves.length; function width(height) { return (totalTX + (1 << height) - 1) >> height; } function hash(height, pos, leaves) { var left, right; if (height === 0) return leaves[0]; left = hash(height - 1, pos * 2, leaves); if (pos * 2 + 1 < width(height - 1, pos * 2 + 1, leaves)) right = hash(height - 1, pos * 2 + 1, leaves); else right = left; return utils.dsha256(Buffer.concat([left, right])); } function traverse(height, pos, leaves, matches) { var parent = 0; var p; for (p = (pos << height); p < ((pos + 1) << height) && p < totalTX; p++) parent |= matches[p]; bits.push(parent); if (height === 0 || !parent) { hashes.push(hash(height, pos, leaves)); return; } traverse(height - 1, pos * 2, leaves, matches); if (pos * 2 + 1 < width(height - 1)) traverse(height - 1, pos * 2 + 1, leaves, matches); } height = 0; while (width(height) > 1) height++; traverse(height, 0, leaves, matches); flags = new Buffer((bits.length + 7) / 8 | 0); for (p = 0; p < bits.length; p++) flags[p / 8 | 0] |= bits[p] << (p % 8); block = new MerkleBlock({ version: block.version, prevBlock: block.prevBlock, merkleRoot: block.merkleRoot, ts: block.ts, bits: block.bits, nonce: block.nonce, totalTX: totalTX, height: block.height, hashes: hashes, flags: flags }); block.txs = txs; return block; }; /** * Expose */ module.exports = MerkleBlock;