UNPKG

libnexa-ts

Version:

A pure and powerful Nexa SDK library.

285 lines (247 loc) 9.1 kB
import type { IBlockHeader } from "../../common/interfaces"; import BN from "../../crypto/bn.extension"; import BufferReader from "../../encoding/bufferreader"; import BufferUtils from "../../utils/buffer.utils"; import ValidationUtils from "../../utils/validation.utils"; import BufferWriter from "../../encoding/bufferwriter"; import Hash from "../../crypto/hash"; export default class BlockHeader implements IBlockHeader { public static readonly MAX_TIME_OFFSET = 2 * 60 * 60; public prevHash: Uint8Array; public bits: number; public ancestorHash: Uint8Array; public merkleRoot: Uint8Array; public txFilter: Uint8Array; public time: number; public height: number; public chainWork: Uint8Array; public size: number; public txCount: number; public poolFee: number; public utxoCommitment: Uint8Array; public minerData: Uint8Array; public nonce: Uint8Array; constructor(data: Uint8Array | IBlockHeader) { ValidationUtils.validateArgument(data != null, 'data is required'); if (BufferUtils.isBuffer(data)) { data = BlockHeader._fromBufferReader(new BufferReader(data)); } else if (typeof data !== 'object') { throw new TypeError('Unrecognized argument for BlockHeader'); } this.prevHash = typeof data.prevHash === 'string' ? BufferUtils.hexToBuffer(data.prevHash) : data.prevHash; this.bits = data.bits; this.ancestorHash = typeof data.ancestorHash === 'string' ? BufferUtils.hexToBuffer(data.ancestorHash) : data.ancestorHash; this.merkleRoot = typeof data.merkleRoot === 'string' ? BufferUtils.hexToBuffer(data.merkleRoot) : data.merkleRoot; this.txFilter = typeof data.txFilter === 'string' ? BufferUtils.hexToBuffer(data.txFilter) : data.txFilter; this.time = data.time; this.height = data.height; this.chainWork = typeof data.chainWork === 'string' ? BufferUtils.hexToBuffer(data.chainWork) : data.chainWork; this.size = data.size; this.txCount = data.txCount; this.poolFee = data.poolFee; this.utxoCommitment = typeof data.utxoCommitment === 'string' ? BufferUtils.hexToBuffer(data.utxoCommitment) : data.utxoCommitment; this.minerData = typeof data.minerData === 'string' ? BufferUtils.hexToBuffer(data.minerData) : data.minerData; this.nonce = typeof data.nonce === 'string' ? BufferUtils.hexToBuffer(data.nonce) : data.nonce; if (data.hash) { ValidationUtils.validateState(this.hash === data.hash, 'Argument object hash property does not match block hash.' ); } } public get hash(): string { return BufferUtils.bufferToHex(this._getHash().reverse()); } /** * @returns The little endian hash buffer of the header */ private _getHash(): Uint8Array { let miniHeader = new BufferWriter(); miniHeader.writeReverse(this.prevHash); miniHeader.writeInt32LE(this.bits); let miniHash = Hash.sha256(miniHeader.toBuffer()) let extHeader = new BufferWriter(); extHeader.writeReverse(this.ancestorHash); extHeader.writeReverse(this.txFilter); extHeader.writeReverse(this.merkleRoot); extHeader.writeInt32LE(this.time); extHeader.writeUInt64LEBN(BN.fromNumber(this.height)); extHeader.writeReverse(this.chainWork); extHeader.writeUInt64LEBN(BN.fromNumber(this.size)); extHeader.writeUInt64LEBN(BN.fromNumber(this.txCount)); extHeader.writeUInt64LEBN(BN.fromNumber(this.poolFee)); extHeader.writeVarLengthBuf(this.utxoCommitment); extHeader.writeVarLengthBuf(this.minerData); extHeader.writeVarLengthBuf(this.nonce); let extHash = Hash.sha256(extHeader.toBuffer()); let commintment = new BufferWriter(); commintment.write(miniHash); commintment.write(extHash); return Hash.sha256(commintment.toBuffer()); } /** * @param br A BufferReader of the block header * @returns An object representing block header data */ private static _fromBufferReader(br: BufferReader): IBlockHeader { return { prevHash: br.readReverse(32), bits: br.readUInt32LE(), ancestorHash: br.readReverse(32), merkleRoot: br.readReverse(32), txFilter: br.readReverse(32), time: br.readUInt32LE(), height: br.readCoreVarintNum(), chainWork: br.readReverse(32), size: br.readUInt64LEBN().toNumber(), txCount: br.readCoreVarintNum(), poolFee: br.readCoreVarintNum(), utxoCommitment: br.readVarLengthBuffer(), minerData: br.readVarLengthBuffer(), nonce: br.readVarLengthBuffer(), }; } /** * This method is useful for hex that represent concatination of multiple headers * so it able to serve in a loop. * * @param br A BufferReader of the block header * @returns An instance of block header */ public static fromBufferReader(br: BufferReader): BlockHeader { let info = this._fromBufferReader(br); return new BlockHeader(info); } /** * @param header A plain JavaScript block header object * @returns An instance of block header */ public static fromObject(header: IBlockHeader): BlockHeader { return new BlockHeader(header); } /** * @param buf A buffer of the block header * @returns An instance of block header */ public static fromBuffer(buf: Uint8Array): BlockHeader { return this.fromBufferReader(new BufferReader(buf)); } /** * @param hex A hex encoded buffer of the block header * @returns An instance of block header */ public static fromString(hex: string): BlockHeader { let buf = BufferUtils.hexToBuffer(hex); return this.fromBuffer(buf); } public toJSON = this.toObject; /** * @returns A plain object of the BlockHeader */ public toObject(): IBlockHeader { return { hash: this.hash, prevHash: BufferUtils.bufferToHex(this.prevHash), bits: this.bits, ancestorHash: BufferUtils.bufferToHex(this.ancestorHash), merkleRoot: BufferUtils.bufferToHex(this.merkleRoot), txFilter: BufferUtils.bufferToHex(this.txFilter), time: this.time, height: this.height, chainWork: BufferUtils.bufferToHex(this.chainWork), size: this.size, txCount: this.txCount, poolFee: this.poolFee, utxoCommitment: BufferUtils.bufferToHex(this.utxoCommitment), minerData: BufferUtils.bufferToHex(this.minerData), nonce: BufferUtils.bufferToHex(this.nonce), }; } /** * @param bw - An existing instance BufferWriter * @returns An instance of BufferWriter representation of the BlockHeader */ public toBufferWriter(bw?: BufferWriter): BufferWriter { if (!bw) { bw = new BufferWriter(); } bw.writeReverse(this.prevHash); bw.writeInt32LE(this.bits); bw.writeReverse(this.ancestorHash); bw.writeReverse(this.merkleRoot); bw.writeReverse(this.txFilter); bw.writeInt32LE(this.time); bw.writeCoreVarintNum(this.height); bw.writeReverse(this.chainWork); bw.writeUInt64LEBN(BN.fromNumber(this.size)); bw.writeCoreVarintNum(this.txCount); bw.writeCoreVarintNum(this.poolFee); bw.writeVarLengthBuf(this.utxoCommitment); bw.writeVarLengthBuf(this.minerData); bw.writeVarLengthBuf(this.nonce); return bw; } /** * @returns A Uint8Array of the BlockHeader */ public toBuffer(): Uint8Array { return this.toBufferWriter().toBuffer(); } /** * @returns A hex encoded string of the BlockHeader */ public toString(): string { return BufferUtils.bufferToHex(this.toBuffer()); } /** * @returns A string formatted for the console */ public inspect(): string { return `<BlockHeader ${this.hash}>`; } /** * Returns the target difficulty for this block * * @param bits the bits number * @returns An instance of BN with the decoded difficulty bits */ public getTargetDifficulty(bits?: number): BN { let bitsBuf = BN.fromNumber(bits || this.bits).toByteArray({ size: 4, endian: 'big' }); let exponent = BN.fromBuffer(Uint8Array.from([bitsBuf[0]])).toBigInt(); let significandBytes = bitsBuf.subarray(1); significandBytes[0] = significandBytes[0] & 0x7f; let significand = BN.fromBuffer(significandBytes); let target = 0n; if (exponent <= 3n) { target = significand.toBigInt() / (2n ** (8n * (3n - exponent))); } else { target = significand.toBigInt() * (2n ** (8n * (exponent - 3n))); } target = (2n**256n) / (target+1n) return new BN(target.toString()); } /** * @returns the target difficulty for this block */ public getDifficulty(): number { let nShift = (this.bits >> 24) & 0xff; let dDiff = 0x0000ffff / (this.bits & 0x00ffffff); while (nShift < 29) { dDiff *= 256.0; nShift++; } while (nShift > 29) { dDiff /= 256.0; nShift--; } return dDiff; } /** * @returns true If timestamp is not too far in the future */ public validTimestamp(): boolean { let currentTime = Math.round(new Date().getTime() / 1000); return this.time <= currentTime + BlockHeader.MAX_TIME_OFFSET } }