libnexa-ts
Version:
A pure and powerful Nexa SDK library.
194 lines (169 loc) • 5.47 kB
text/typescript
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);
}
}