src/main/generic/consensus/base/block/BlockBody.js
class BlockBody {
/**
* @param {Uint8Array} extraData
* @returns {number}
*/
static getMetadataSize(extraData) {
return Address.SERIALIZED_SIZE
+ /*extraDataLength*/ 1
+ extraData.byteLength
+ /*transactionsLength*/ 2
+ /*prunedAccountsLength*/ 2;
}
/**
* @param {Address} minerAddr
* @param {Array.<Transaction>} transactions
* @param {Uint8Array} [extraData]
* @param {Array.<PrunedAccount>} prunedAccounts
*/
constructor(minerAddr, transactions, extraData = new Uint8Array(0), prunedAccounts = []) {
if (!(minerAddr instanceof Address)) throw new Error('Malformed minerAddr');
if (!Array.isArray(transactions) || transactions.some(it => !(it instanceof Transaction))) throw new Error('Malformed transactions');
if (!(extraData instanceof Uint8Array) || !NumberUtils.isUint8(extraData.byteLength)) throw new Error('Malformed extraData');
/** @type {Address} */
this._minerAddr = minerAddr;
/** @type {Uint8Array} */
this._extraData = extraData;
/** @type {Array.<Transaction>} */
this._transactions = transactions;
/** @type {Array.<PrunedAccount>} */
this._prunedAccounts = prunedAccounts;
/** @type {Hash} */
this._hash = null;
}
/**
* @param {SerialBuffer} buf
* @return {BlockBody}
*/
static unserialize(buf) {
const minerAddr = Address.unserialize(buf);
const extraDataLength = buf.readUint8();
const extraData = buf.read(extraDataLength);
const numTransactions = buf.readUint16();
const transactions = new Array(numTransactions);
for (let i = 0; i < numTransactions; i++) {
transactions[i] = Transaction.unserialize(buf);
}
const numPrunedAccounts = buf.readUint16();
const prunedAccounts = [];
for (let i = 0; i < numPrunedAccounts; i++) {
prunedAccounts.push(PrunedAccount.unserialize(buf));
}
return new BlockBody(minerAddr, transactions, extraData, prunedAccounts);
}
/**
* @param {SerialBuffer} [buf]
* @returns {SerialBuffer}
*/
serialize(buf) {
buf = buf || new SerialBuffer(this.serializedSize);
this._minerAddr.serialize(buf);
buf.writeUint8(this._extraData.byteLength);
buf.write(this._extraData);
buf.writeUint16(this._transactions.length);
for (const tx of this._transactions) {
tx.serialize(buf);
}
buf.writeUint16(this._prunedAccounts.length);
for (const acc of this._prunedAccounts) {
acc.serialize(buf);
}
return buf;
}
/**
* @type {number}
*/
get serializedSize() {
let size = this._minerAddr.serializedSize
+ /*extraDataLength*/ 1
+ this._extraData.byteLength
+ /*transactionsLength*/ 2
+ /*prunedAccountsLength*/ 2;
for (const tx of this._transactions) {
size += tx.serializedSize;
}
size += this._prunedAccounts.reduce((sum, acc) => sum + acc.serializedSize, 0);
return size;
}
/**
* @returns {boolean}
*/
verify() {
/** @type {Transaction} */
let previousTx = null;
for (const tx of this._transactions) {
// Ensure transactions are ordered and unique.
if (previousTx && previousTx.compareBlockOrder(tx) >= 0) {
Log.w(BlockBody, 'Invalid block - transactions not ordered.');
return false;
}
previousTx = tx;
// Check that all transactions are valid.
if (!tx.verify()) {
Log.w(BlockBody, 'Invalid block - invalid transaction');
return false;
}
}
let previousAcc = null;
for (const acc of this._prunedAccounts) {
// Ensure pruned accounts are ordered and unique.
if (previousAcc && previousAcc.compare(acc) >= 0) {
Log.w(BlockBody, 'Invalid block - pruned accounts not ordered.');
return false;
}
previousAcc = acc;
// Check that pruned accounts are actually supposed to be pruned
if (!acc.account.isToBePruned()) {
Log.w(BlockBody, 'Invalid block - invalid pruned account');
return false;
}
}
// Everything checks out.
return true;
}
/**
* @returns {Array}
*/
getMerkleLeafs() {
return [this._minerAddr, this._extraData, ...this._transactions, ...this.prunedAccounts];
}
/**
* @return {Hash}
*/
hash() {
if (!this._hash) {
this._hash = MerkleTree.computeRoot(this.getMerkleLeafs());
}
return this._hash;
}
/**
* @param {BlockBody} o
* @returns {boolean}
*/
equals(o) {
return o instanceof BlockBody
&& this._minerAddr.equals(o.minerAddr)
&& BufferUtils.equals(this._extraData, o.extraData)
&& this._transactions.length === o.transactions.length
&& this._transactions.every((tx, i) => tx.equals(o.transactions[i]));
}
/**
* @return {Array.<Address>}
*/
getAddresses() {
const addresses = [this._minerAddr];
for (const tx of this._transactions) {
addresses.push(tx.sender, tx.recipient);
}
return addresses;
}
/** @type {Uint8Array} */
get extraData() {
return this._extraData;
}
/** @type {Address} */
get minerAddr() {
return this._minerAddr;
}
/** @type {Array.<Transaction>} */
get transactions() {
return this._transactions;
}
/** @type {number} */
get transactionCount() {
return this._transactions.length;
}
/** @type {Array.<PrunedAccount>} */
get prunedAccounts() {
return this._prunedAccounts;
}
}
Class.register(BlockBody);