UNPKG

@dashevo/dashcore-lib

Version:

A pure and powerful JavaScript Dash library.

292 lines (264 loc) 7.92 kB
const _ = require('lodash'); const BlockHeader = require('./blockheader'); const BN = require('../crypto/bn'); const BufferUtil = require('../util/buffer'); const BufferReader = require('../encoding/bufferreader'); const BufferWriter = require('../encoding/bufferwriter'); const Hash = require('../crypto/hash'); const Transaction = require('../transaction'); const $ = require('../util/preconditions'); /** * Instantiate a Block from a Buffer, JSON object, or Object with * the properties of the Block * * @param {Buffer|Block.fromObjectParams} arg - A Buffer, JSON string, or Object * @returns {Block} * @constructor */ function Block(arg) { if (!(this instanceof Block)) { return new Block(arg); } _.extend(this, Block._from(arg)); return this; } // https://github.com/bitcoin/bitcoin/blob/b5fa132329f0377d787a4a21c1686609c2bfaece/src/primitives/block.h#L14 Block.MAX_BLOCK_SIZE = 1000000; /** * @param {Buffer|Block.fromObjectParams} arg - A Buffer, JSON string or Object * @returns {Object} - An object representing block data * @throws {TypeError} - If the argument was not recognized * @private */ Block._from = function _from(arg) { let info = {}; if (BufferUtil.isBuffer(arg)) { info = Block._fromBufferReader(BufferReader(arg)); } else if (_.isObject(arg)) { info = Block._fromObject(arg); } else { throw new TypeError('Unrecognized argument for Block'); } return info; }; /** * @param {Object} data - A plain JavaScript object * @returns {Object} - An object representing block data * @private */ Block._fromObject = function _fromObject(data) { const transactions = []; data.transactions.forEach((tx) => { if (tx instanceof Transaction) { transactions.push(tx); } else { transactions.push(Transaction().fromObject(tx)); } }); const info = { /** @type {BlockHeader} */ header: BlockHeader.fromObject(data.header), /** @type {Transaction[]} */ transactions, }; return info; }; /** * @property {Block.fromObjectParams} obj - A plain JavaScript object * @returns {Block} - An instance of block */ Block.fromObject = function fromObject(obj) { const info = Block._fromObject(obj); return new Block(info); }; /** * @param {BufferReader} br - Block data * @returns {Object} - An object representing the block data * @private */ Block._fromBufferReader = function _fromBufferReader(br) { const info = {}; $.checkState(!br.finished(), 'No block data received'); info.header = BlockHeader.fromBufferReader(br); const transactions = br.readVarintNum(); info.transactions = []; for (let i = 0; i < transactions; i += 1) { info.transactions.push(Transaction().fromBufferReader(br)); } return info; }; /** * @param {BufferReader} br A buffer reader of the block * @returns {Block} - An instance of block */ Block.fromBufferReader = function fromBufferReader(br) { $.checkArgument(br, 'br is required'); const info = Block._fromBufferReader(br); return new Block(info); }; /** * @param {Buffer} buf A buffer of the block * @returns {Block} - An instance of block */ Block.fromBuffer = function fromBuffer(buf) { return Block.fromBufferReader(new BufferReader(buf)); }; /** * @param {string} str - A hex encoded string of the block * @returns {Block} - A hex encoded string of the block */ Block.fromString = function fromString(str) { const buf = Buffer.from(str, 'hex'); return Block.fromBuffer(buf); }; /** * @param {Buffer} rawData - Raw block binary data or buffer * @returns {Block} - An instance of block */ Block.fromRawBlock = function fromRawBlock(rawData) { let data = rawData; if (!BufferUtil.isBuffer(rawData)) { data = Buffer.from(rawData, 'binary'); } const br = BufferReader(data); br.pos = Block.Values.START_OF_BLOCK; const info = Block._fromBufferReader(br); return new Block(info); }; /** * @function * @returns {BlockHeader.toObjectParams} - A plain object with the block properties */ Block.prototype.toJSON = function toObject() { const transactions = []; this.transactions.forEach((tx) => { transactions.push(tx.toObject()); }); return { header: this.header.toObject(), transactions, }; }; /** * @function * @returns {BlockHeader.toObjectParams} - A plain object with the block properties */ Block.prototype.toObject = Block.prototype.toJSON; /** * @returns {Buffer} - A buffer of the block */ Block.prototype.toBuffer = function toBuffer() { return this.toBufferWriter().toBuffer(); }; /** * @returns {string} - A hex encoded string of the block */ Block.prototype.toString = function toString() { return this.toBuffer().toString('hex'); }; /** * @param {BufferWriter} bw - An existing instance of BufferWriter * @returns {BufferWriter} - An instance of BufferWriter representation of the Block */ Block.prototype.toBufferWriter = function toBufferWriter(bw) { const bufferWriter = bw || new BufferWriter(); bufferWriter.write(this.header.toBuffer()); bufferWriter.writeVarintNum(this.transactions.length); for (let i = 0; i < this.transactions.length; i += 1) { this.transactions[i].toBufferWriter(bufferWriter); } return bufferWriter; }; /** * Will iterate through each transaction and return an array of hashes * @returns {Buffer[]} - An array with transaction hashes */ Block.prototype.getTransactionHashes = function getTransactionHashes() { const hashes = []; if (this.transactions.length === 0) { return [Block.Values.NULL_HASH]; } for (let t = 0; t < this.transactions.length; t += 1) { hashes.push(this.transactions[t]._getHash()); } return hashes; }; /** * Will build a merkle tree of all the transactions, ultimately arriving at * a single point, the merkle root. * @link https://en.bitcoin.it/wiki/Protocol_specification#Merkle_Trees * @returns {Buffer[]} - An array with each level of the tree after the other. */ Block.prototype.getMerkleTree = function getMerkleTree() { const tree = this.getTransactionHashes(); let j = 0; for ( let size = this.transactions.length; size > 1; size = Math.floor((size + 1) / 2) ) { for (let i = 0; i < size; i += 2) { const i2 = Math.min(i + 1, size - 1); const buf = Buffer.concat([tree[j + i], tree[j + i2]]); tree.push(Hash.sha256sha256(buf)); } j += size; } return tree; }; /** * Calculates the merkleRoot from the transactions. * @returns {Buffer} - A buffer of the merkle root hash */ Block.prototype.getMerkleRoot = function getMerkleRoot() { const tree = this.getMerkleTree(); return tree[tree.length - 1]; }; /** * Verifies that the transactions in the block match the header merkle root * @returns {Boolean} - If the merkle roots match */ Block.prototype.validMerkleRoot = function validMerkleRoot() { const h = new BN(this.header.merkleRoot.toString('hex'), 'hex'); const c = new BN(this.getMerkleRoot().toString('hex'), 'hex'); if (h.cmp(c) !== 0) { return false; } return true; }; /** * @returns {Buffer} - The little endian hash buffer of the header */ Block.prototype._getHash = function () { return this.header._getHash(); }; const idProperty = { configurable: false, enumerable: true, /** * @returns {string} - The big endian hash buffer of the header */ get() { if (!this._id) { this._id = this.header.id; } return this._id; }, set: _.noop, }; Object.defineProperty(Block.prototype, 'id', idProperty); Object.defineProperty(Block.prototype, 'hash', idProperty); /** * @returns {string} - A string formatted for the console */ Block.prototype.inspect = function inspect() { return `<Block ${this.id}>`; }; Block.Values = { START_OF_BLOCK: 8, // Start of block in raw block data NULL_HASH: Buffer.from( '0000000000000000000000000000000000000000000000000000000000000000', 'hex' ), }; module.exports = Block;