lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
324 lines (323 loc) • 11 kB
JavaScript
import { Preconditions } from '../util/preconditions.js';
import { BufferUtil } from '../util/buffer.js';
import { BufferReader } from '../encoding/bufferreader.js';
import { BufferWriter } from '../encoding/bufferwriter.js';
import { Hash } from '../crypto/hash.js';
import { JSUtil } from '../util/js.js';
import { BN } from '../crypto/bn.js';
const GENESIS_BITS = 0x1d00ffff;
export class BlockHeader {
static Constants = {
START_OF_HEADER: 0,
MAX_TIME_OFFSET: 2 * 60 * 60,
LARGEST_HASH: new BN('10000000000000000000000000000000000000000000000000000000000000000', 'hex'),
};
static START_OF_HEADER = 0;
static MAX_TIME_OFFSET = 2 * 60 * 60;
static LARGEST_HASH = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000');
prevHash;
bits;
time;
timestamp;
reserved;
nonce;
version;
size;
height;
epochBlock;
merkleRoot;
extendedMetadata;
_id;
constructor(arg) {
if (arg instanceof BlockHeader) {
return arg;
}
this.prevHash = Buffer.alloc(32);
this.bits = 0;
this.time = 0;
this.timestamp = 0;
this.reserved = 0;
this.nonce = new BN(0);
this.version = 0;
this.size = new BN(0);
this.height = 0;
this.epochBlock = Buffer.alloc(32);
this.merkleRoot = Buffer.alloc(32);
this.extendedMetadata = Buffer.alloc(32);
if (arg && typeof arg === 'object') {
const info = BlockHeader._from(arg);
this.prevHash = info.prevHash;
this.bits = info.bits;
this.time = info.time;
this.timestamp = info.time;
this.reserved = info.reserved || 0;
this.nonce = info.nonce;
this.version = info.version;
this.size = info.size;
this.height = info.height;
this.epochBlock = info.epochBlock;
this.merkleRoot = info.merkleRoot;
this.extendedMetadata = info.extendedMetadata;
if (info.hash) {
Preconditions.checkState(this.hash === info.hash, 'Argument object hash property does not match block hash.');
}
}
}
static _from(arg) {
let info = {};
if (Buffer.isBuffer(arg)) {
info = BlockHeader._fromBufferReader(new BufferReader(arg));
}
else if (typeof arg === 'string' && JSUtil.isHexa(arg)) {
const buf = Buffer.from(arg, 'hex');
info = BlockHeader._fromBufferReader(new BufferReader(buf));
}
else if (typeof arg === 'object' && arg !== null) {
info = BlockHeader._fromObject(arg);
}
else {
throw new TypeError('Unrecognized argument for BlockHeader');
}
return info;
}
static _fromObject(data) {
Preconditions.checkArgument(typeof data === 'object' && data !== null, 'data is required');
let prevHash = data.prevHash;
let merkleRoot = data.merkleRoot;
let epochBlock = data.epochBlock;
let extendedMetadata = data.extendedMetadata;
let nonce = data.nonce;
let size = data.size;
if (typeof data.prevHash === 'string') {
prevHash = BufferUtil.reverse(Buffer.from(data.prevHash, 'hex'));
}
else if (!Buffer.isBuffer(data.prevHash)) {
prevHash = Buffer.alloc(32);
}
if (typeof data.merkleRoot === 'string') {
merkleRoot = BufferUtil.reverse(Buffer.from(data.merkleRoot, 'hex'));
}
else if (!Buffer.isBuffer(data.merkleRoot)) {
merkleRoot = Buffer.alloc(32);
}
if (typeof data.epochBlock === 'string') {
epochBlock = BufferUtil.reverse(Buffer.from(data.epochBlock, 'hex'));
}
else if (!Buffer.isBuffer(data.epochBlock)) {
epochBlock = Buffer.alloc(32);
}
if (typeof data.extendedMetadata === 'string') {
extendedMetadata = BufferUtil.reverse(Buffer.from(data.extendedMetadata, 'hex'));
}
else if (!Buffer.isBuffer(data.extendedMetadata)) {
extendedMetadata = Buffer.alloc(32);
}
if (typeof data.nonce === 'string') {
nonce = new BN(data.nonce, 10);
}
else if (typeof data.nonce === 'number') {
nonce = new BN(data.nonce);
}
else if (data.nonce instanceof BN) {
nonce = data.nonce;
}
else {
nonce = new BN(0);
}
if (typeof data.size === 'string') {
size = new BN(data.size, 10);
}
else if (typeof data.size === 'number') {
size = new BN(data.size);
}
else if (data.size instanceof BN) {
size = data.size;
}
else {
size = new BN(0);
}
return {
hash: data.hash,
prevHash: prevHash,
bits: data.bits || 0,
timestamp: data.time || data.timestamp || 0,
reserved: data.reserved || 0,
nonce: nonce,
version: data.version || 0,
size: size,
height: data.height || 0,
epochBlock: epochBlock,
merkleRoot: merkleRoot,
extendedMetadata: extendedMetadata,
time: data.time || data.timestamp || 0,
};
}
static fromObject(obj) {
const info = BlockHeader._fromObject(obj);
return new BlockHeader(info);
}
static fromRawBlock(data) {
if (!Buffer.isBuffer(data)) {
data = Buffer.from(data, 'binary');
}
const br = new BufferReader(data);
br.pos = BlockHeader.Constants.START_OF_HEADER;
const info = BlockHeader._fromBufferReader(br);
return new BlockHeader(info);
}
static fromBuffer(buf) {
const info = BlockHeader._fromBufferReader(new BufferReader(buf));
return new BlockHeader(info);
}
static fromString(str) {
const buf = Buffer.from(str, 'hex');
return BlockHeader.fromBuffer(buf);
}
static _fromBufferReader(br) {
return {
prevHash: br.read(32),
bits: br.readUInt32LE(),
time: br.readUInt48LE(),
reserved: br.readUInt16LE(),
nonce: br.readUInt64LEBN(),
version: br.readUInt8(),
size: br.readUInt56LEBN(),
height: br.readUInt32LE(),
epochBlock: br.read(32),
merkleRoot: br.read(32),
extendedMetadata: br.read(32),
};
}
static fromBufferReader(br) {
const info = BlockHeader._fromBufferReader(br);
return new BlockHeader(info);
}
toObject() {
return {
hash: this.hash,
prevHash: BufferUtil.reverse(this.prevHash).toString('hex'),
bits: this.bits,
time: this.time,
reserved: this.reserved,
nonce: this.nonce.toString(10),
version: this.version,
size: this.size.toNumber(),
height: this.height,
epochBlock: BufferUtil.reverse(this.epochBlock).toString('hex'),
merkleRoot: BufferUtil.reverse(this.merkleRoot).toString('hex'),
extendedMetadata: BufferUtil.reverse(this.extendedMetadata).toString('hex'),
};
}
toJSON = this.toObject;
toBuffer() {
return this.toBufferWriter().toBuffer();
}
toString() {
return this.toBuffer().toString('hex');
}
toBufferWriter(bw) {
if (!bw) {
bw = new BufferWriter();
}
bw.write(this.prevHash);
bw.writeUInt32LE(this.bits);
bw.writeUInt48LE(this.time);
bw.writeUInt16LE(this.reserved);
bw.writeUInt64LEBN(this.nonce);
bw.writeUInt8(this.version);
bw.writeUInt56LEBN(this.size);
bw.writeUInt32LE(this.height);
bw.write(this.epochBlock);
bw.write(this.merkleRoot);
bw.write(this.extendedMetadata);
return bw;
}
getTargetDifficulty(bits) {
bits = bits || this.bits;
let target = new BN(bits & 0xffffff);
let mov = 8 * ((bits >>> 24) - 3);
while (mov-- > 0) {
target = target.mul(new BN(2));
}
return target;
}
getDifficulty() {
const difficulty1TargetBN = this.getTargetDifficulty(GENESIS_BITS).mul(new BN(Math.pow(10, 8)));
const currentTargetBN = this.getTargetDifficulty();
const difficultyString = difficulty1TargetBN
.div(currentTargetBN)
.toString(10);
const decimalPos = difficultyString.length - 8;
const formattedDifficulty = difficultyString.slice(0, decimalPos) +
'.' +
difficultyString.slice(decimalPos);
return parseFloat(formattedDifficulty);
}
_getHash() {
const layer3Hash = this._getLayer3Hash();
const layer2Hash = this._getLayer2Hash(layer3Hash);
const bw = new BufferWriter();
bw.write(this.prevHash);
bw.write(layer2Hash);
return Hash.sha256(bw.toBuffer());
}
_timeToBytes() {
const buf = Buffer.alloc(6);
buf.writeUIntLE(this.time, 0, 6);
return buf;
}
_sizeToBytes() {
const buf = Buffer.alloc(7);
const sizeBuffer = this.size.toArrayLike(Buffer, 'le', 7);
sizeBuffer.copy(buf);
return buf;
}
_getLayer3Hash() {
const bw = new BufferWriter();
bw.writeUInt8(this.version);
bw.writeUInt56LEBN(this.size);
bw.writeUInt32LE(this.height);
bw.write(this.epochBlock);
bw.write(this.merkleRoot);
bw.write(this.extendedMetadata);
return Hash.sha256(bw.toBuffer());
}
_getLayer2Hash(layer3Hash) {
const bw = new BufferWriter();
bw.writeUInt32LE(this.bits);
bw.writeUInt48LE(this.time);
bw.writeUInt16LE(this.reserved);
bw.writeUInt64LEBN(this.nonce);
bw.write(layer3Hash);
return Hash.sha256(bw.toBuffer());
}
get hash() {
if (!this._id) {
const hashBuffer = this._getHash();
const reader = new BufferReader(hashBuffer);
this._id = reader.readReverse(32).toString('hex');
}
return this._id;
}
get id() {
return this.hash;
}
validTimestamp() {
const currentTime = Math.round(new Date().getTime() / 1000);
if (this.time > currentTime + BlockHeader.Constants.MAX_TIME_OFFSET) {
return false;
}
return true;
}
validProofOfWork() {
const pow = new BN(this.id, 'hex');
const target = this.getTargetDifficulty();
if (pow.gt(target)) {
return false;
}
return true;
}
inspect() {
return '<BlockHeader ' + this.id + '>';
}
}