lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
234 lines (233 loc) • 7.41 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 { Transaction } from '../transaction/index.js';
import { BN } from '../crypto/bn.js';
import { BlockHeader, } from './blockheader.js';
export class Block {
static MAX_BLOCK_SIZE = 32 * 1024 * 1024;
static START_OF_BLOCK = 0;
static NULL_HASH = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');
static Values = {
START_OF_BLOCK: 0,
NULL_HASH: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
};
header;
metadata;
transactions;
_id;
constructor(serialized) {
if (!(this instanceof Block)) {
return new Block(serialized);
}
if (serialized instanceof Block) {
return Block.shallowCopy(serialized);
}
else if (typeof serialized === 'string' && JSUtil.isHexa(serialized)) {
this.fromString(serialized);
}
else if (Buffer.isBuffer(serialized)) {
this.fromBuffer(serialized);
}
else if (serialized && typeof serialized === 'object') {
this.fromObject(serialized);
}
else {
this._newBlock();
}
}
static shallowCopy(block) {
const copy = new Block(block.toBuffer());
return copy;
}
static _from(arg) {
let info = {};
if (Buffer.isBuffer(arg)) {
info = Block._fromBufferReader(new BufferReader(arg));
}
else if (typeof arg === 'object' && arg !== null) {
info = Block._fromObject(arg);
}
else {
throw new TypeError('Unrecognized argument for Block');
}
return info;
}
static fromObject(obj) {
const info = Block._fromObject(obj);
return new Block(info);
}
static fromBufferReader(br) {
Preconditions.checkArgument(br instanceof BufferReader, 'br is required');
const info = Block._fromBufferReader(br);
return new Block(info);
}
static fromBuffer(buf) {
return Block.fromBufferReader(new BufferReader(buf));
}
static fromString(str) {
const buf = Buffer.from(str, 'hex');
return Block.fromBuffer(buf);
}
static fromRawBlock(data) {
if (!BufferUtil.isBuffer(data)) {
data = Buffer.from(data, 'binary');
}
const br = new BufferReader(data);
br.pos = Block.Values.START_OF_BLOCK;
const info = Block._fromBufferReader(br);
return new Block(info);
}
static _fromObject(data) {
const transactions = [];
if (data.transactions) {
data.transactions.forEach(tx => {
if (tx instanceof Transaction) {
transactions.push(tx);
}
else {
transactions.push(new Transaction(tx));
}
});
}
return {
header: data.header
? data.header instanceof BlockHeader
? data.header
: new BlockHeader(data.header)
: new BlockHeader(),
metadata: data.metadata || 0x00,
transactions: transactions,
};
}
static _fromBufferReader(br) {
Preconditions.checkState(!br.finished(), 'No block data received');
const header = BlockHeader.fromBufferReader(br);
const metadata = br.readUInt8();
const transactionCount = br.readVarintNum();
const transactions = [];
for (let i = 0; i < transactionCount; i++) {
const tx = new Transaction();
transactions.push(tx.fromBufferReader(br));
}
return {
header,
metadata,
transactions,
};
}
_newBlock() {
this.header = new BlockHeader();
this.metadata = 0x00;
this.transactions = [];
}
fromBuffer(buf) {
const info = Block._fromBufferReader(new BufferReader(buf));
this.header = info.header;
this.metadata = info.metadata;
this.transactions = info.transactions;
return this;
}
fromString(str) {
const buf = Buffer.from(str, 'hex');
return this.fromBuffer(buf);
}
fromObject(obj) {
const info = Block._fromObject(obj);
this.header = info.header;
this.metadata = info.metadata;
this.transactions = info.transactions;
return this;
}
toObject() {
const transactions = this.transactions.map(tx => tx.toObject());
return {
id: this.id,
hash: this.hash,
header: this.header.toObject(),
metadata: this.metadata,
transactions: transactions,
};
}
toJSON = this.toObject;
toBuffer() {
return this.toBufferWriter().concat();
}
toString() {
return this.toBuffer().toString('hex');
}
toBufferWriter(bw) {
if (!bw) {
bw = new BufferWriter();
}
bw.write(this.header.toBuffer());
bw.writeUInt8(this.metadata);
bw.writeVarintNum(this.transactions.length);
for (let i = 0; i < this.transactions.length; i++) {
this.transactions[i].toBufferWriter(bw);
}
return bw;
}
getTransactionHashes() {
const hashes = [];
if (this.transactions.length === 0) {
return [Block.Values.NULL_HASH];
}
for (let t = 0; t < this.transactions.length; t++) {
const tx = this.transactions[t];
const txid = tx._getTxid();
const hash = tx._getHash();
const buf = Buffer.concat([hash, txid]);
const resultHash = Hash.sha256sha256(buf);
hashes.push(resultHash);
}
return hashes;
}
getMerkleTree() {
const tree = this.getTransactionHashes();
let j = 0;
for (let size = tree.length; size > 1; size = Math.floor(size / 2)) {
if (size % 2 === 1) {
tree.push(Block.Values.NULL_HASH);
size += 1;
}
for (let i = 0; i < size; i += 2) {
const buf = Buffer.concat([tree[j + i], tree[j + i + 1]]);
tree.push(Hash.sha256sha256(buf));
}
j += size;
}
return tree;
}
getMerkleRoot() {
const tree = this.getMerkleTree();
return tree[tree.length - 1];
}
validMerkleRoot() {
const h = new BN(this.header.merkleRoot.toString('hex'), 'hex');
const c = new BN(this.getMerkleRoot().toString('hex'), 'hex');
if (!h.eq(c)) {
return false;
}
return true;
}
_getHash() {
const header = this.header;
return header._getHash();
}
get hash() {
if (!this._id) {
this._id = this.header.id;
}
return this._id;
}
get id() {
return this.hash;
}
inspect() {
return '<Block ' + this.id + '>';
}
}