UNPKG

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
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 + '>'; } }