UNPKG

libnexa-ts

Version:

A pure and powerful Nexa SDK library.

194 lines (169 loc) 5.47 kB
import type { IBlock } from "../../common/interfaces"; import ValidationUtils from "../../utils/validation.utils"; import Transaction from "../transaction/transaction"; import BlockHeader from "./blockheader"; import BufferUtils from "../../utils/buffer.utils"; import BufferReader from "../../encoding/bufferreader"; import BufferWriter from "../../encoding/bufferwriter"; import Hash from "../../crypto/hash"; import BN from "../../crypto/bn.extension"; export default class Block implements IBlock { public header: BlockHeader; public transactions: Transaction[]; constructor(data: Uint8Array | IBlock) { ValidationUtils.validateArgument(data != null, 'data is required'); if (BufferUtils.isBuffer(data)) { data = Block._fromBufferReader(new BufferReader(data)); } else if (typeof data !== 'object') { throw new TypeError('Unrecognized argument for Block'); } this.header = data.header instanceof BlockHeader ? data.header : BlockHeader.fromObject(data.header); this.transactions = []; data.transactions.forEach(tx => { if (tx instanceof Transaction) { this.transactions.push(tx); } else { this.transactions.push(new Transaction().fromObject(tx)); } }); } public get hash(): string { return this.header.hash; } /** * @param obj A plain JavaScript object * @returns An instance of block */ public static fromObject(obj: IBlock): Block { return new Block(obj); } /** * @param br A BufferReader of the block * @returns An object representing the block data */ private static _fromBufferReader(br: BufferReader): IBlock { ValidationUtils.validateState(!br.finished(), 'No block data received'); let header = BlockHeader.fromBufferReader(br); let txNum = br.readVarintNum(); let transactions: Transaction[] = []; for (let i = 0; i < txNum; i++) { transactions.push(new Transaction().fromBufferReader(br)); } return { header, transactions }; } /** * @param br A buffer reader of the block * @returns An instance of block */ public static fromBufferReader(br: BufferReader): Block { ValidationUtils.validateArgument(br != null, 'br is required'); let info = Block._fromBufferReader(br); return new Block(info); } /** * @param buf A buffer of the block * @returns An instance of block */ public static fromBuffer(buf: Uint8Array): Block { return Block.fromBufferReader(new BufferReader(buf)); } /** * @param str A hex encoded string of the block * @returns A hex encoded string of the block */ public static fromString(str: string): Block { let buf = BufferUtils.hexToBuffer(str); return Block.fromBuffer(buf); } public toJSON = this.toObject; /** * @returns A plain object with the block properties */ public toObject(): IBlock { return { header: this.header.toObject(), transactions: this.transactions.map(tx => tx.toObject()) }; } /** * @param bw An existing instance of BufferWriter (optional) * @returns An instance of BufferWriter representation of the Block */ public toBufferWriter(bw?: BufferWriter): BufferWriter { if (!bw) { bw = new BufferWriter(); } bw.write(this.header.toBuffer()); bw.writeVarintNum(this.transactions.length); for (let tx of this.transactions) { tx.toBufferWriter(bw); } return bw; } /** * @returns A buffer of the block */ public toBuffer(): Uint8Array { return this.toBufferWriter().toBuffer(); } /** * @returns A hex encoded string of the block */ public toString(): string { return BufferUtils.bufferToHex(this.toBuffer()); } /** * @returns A string formatted for the console */ public inspect(): string { return `<Block ${this.hash}>`; } /** * Will iterate through each transaction and return an array of hashes * @returns An array with transaction hashes */ public getTransactionHashes(): Uint8Array[] { return this.transactions.map(tx => BufferUtils.hexToBuffer(tx.id).reverse()); } /** * Will build a merkle tree of all the transactions, ultimately arriving at * a single point, the merkle root. * * @see {@link https://spec.nexa.org/blocks/merkle-tree/} * @returns An array with each level of the tree after the other. */ public getMerkleTree(): Uint8Array[] { let 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) { let i2 = Math.min(i + 1, size - 1); let buf = BufferUtils.concat([tree[j + i], tree[j + i2]]); tree.push(Hash.sha256sha256(buf)); } j += size; } return tree; } /** * Calculates the merkleRoot from the transactions. * * @returns A buffer of the merkle root hash */ public getMerkleRoot(): Uint8Array { let tree = this.getMerkleTree(); return tree[tree.length - 1]; } /** * Verifies that the transactions in the block match the header merkle root * * @returns true If the merkle roots match */ public validMerkleRoot(): boolean { let h = BN.fromString(BufferUtils.bufferToHex(BufferUtils.reverse(this.header.merkleRoot)), 'hex'); let c = BN.fromString(BufferUtils.bufferToHex(this.getMerkleRoot()), 'hex'); return h.eq(c); } }