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