Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Update block schema - Closes #6739 #6765

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions elements/lisk-chain/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,61 @@
*/

import { codec } from '@liskhq/lisk-codec';
import { BlockAssets } from './block_assets';
import { BlockHeader } from './block_header';
import { blockSchema } from './schema';
import { Transaction } from './transaction';

interface BlockAttrs {
header: Buffer;
payload: Buffer[];
assets: Buffer[];
}

export class Block {
// eslint-disable-next-line no-useless-constructor
public constructor(public readonly header: BlockHeader, public readonly payload: Transaction[]) {
public constructor(
public readonly header: BlockHeader,
public readonly payload: Transaction[],
public readonly assets: BlockAssets,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit unsure here if it can be redefined to be something like BlockAsset[]. In the LIP it says assets is an array of objects and also since payload is an array of objects too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other properties are also class instance, and we could create BlockAsset class, but we want to have blockAssets.get and blockAssets.set. Therefore it's better to be BlockAssets class

) {
// No body necessary
}

public static fromBytes(value: Buffer): Block {
const { header, payload } = codec.decode<BlockAttrs>(blockSchema, value);
const { header, payload, assets } = codec.decode<BlockAttrs>(blockSchema, value);

return new Block(
BlockHeader.fromBytes(header),
payload.map(v => Transaction.fromBytes(v)),
BlockAssets.fromBytes(assets),
);
}

public static fromJSON(value: Record<string, unknown>): Block {
const { header, payload } = value;
const { header, payload, assets } = value;
if (typeof header !== 'object') {
throw new Error('Invalid block format. header must be an object.');
}
if (!Array.isArray(payload)) {
throw new Error('Invalid block format. payload must be an array.');
}
if (!Array.isArray(assets)) {
throw new Error('Invalid block format. assets must be an array.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error('Invalid block format. assets must be an array.');
throw new Error('Invalid block format. assets must be an array.');
Suggested change
throw new Error('Invalid block format. assets must be an array.');
throw new Error('Invalid block format. "assets" must be an array.');

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is following the style of other error message?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw further below the property name was in double quotes but I'm ok with either way.

}

return new Block(
BlockHeader.fromJSON(value.header as Record<string, unknown>),
payload.map(v => Transaction.fromBytes(v)),
BlockAssets.fromJSON(assets),
);
}

public getBytes(): Buffer {
return codec.encode(blockSchema, {
header: this.header.getBytes(),
payload: this.payload.map(p => p.getBytes()),
assets: this.assets.getBytes(),
});
}

Expand Down
61 changes: 61 additions & 0 deletions elements/lisk-chain/src/block_assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright © 2021 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/

import { codec } from '@liskhq/lisk-codec';
import { blockAssetSchema } from './schema';

export interface BlockAsset {
moduleID: number;
data: Buffer;
}

export class BlockAssets {
private readonly _assets: BlockAsset[] = [];

public constructor(assets: BlockAsset[] = []) {
this._assets = assets;
}

public static fromBytes(values: ReadonlyArray<Buffer>): BlockAssets {
const assets = values.map(val => codec.decode<BlockAsset>(blockAssetSchema, val));
const blockAssets = new BlockAssets(assets);
return blockAssets;
}

public static fromJSON(values: Record<string, unknown>[]): BlockAssets {
const assets = values.map(val => codec.fromJSON<BlockAsset>(blockAssetSchema, val));
return new BlockAssets(assets);
}

public getBytes(): Buffer[] {
return this._assets.map(asset => codec.encode(blockAssetSchema, asset));
}

public getAsset(moduleID: number): Buffer | undefined {
return this._assets.find(a => a.moduleID === moduleID)?.data;
}

public setAsset(moduleID: number, value: Buffer): void {
const asset = this.getAsset(moduleID);
if (asset) {
throw new Error(`Module asset for "${moduleID}" is already set.`);
}

this._assets.push({ moduleID, data: value });
}

public sort(): void {
this._assets.sort((a1, a2) => a1.moduleID - a2.moduleID);
}
}
100 changes: 73 additions & 27 deletions elements/lisk-chain/src/block_header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,29 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { objects } from '@liskhq/lisk-utils';
import { signDataWithPrivateKey, hash, verifyData } from '@liskhq/lisk-cryptography';
import { codec } from '@liskhq/lisk-codec';
import { validator, LiskValidationError } from '@liskhq/lisk-validator';
import { TAG_BLOCK_HEADER } from './constants';
import { blockHeaderSchema, blockHeaderSchemaWithId, signingBlockHeaderSchema } from './schema';

export interface BlockHeaderAsset {
moduleID: number;
data: Buffer;
}

export interface BlockHeaderAttrs {
readonly version: number;
readonly height: number;
readonly generatorAddress: Buffer;
readonly previousBlockID: Buffer;
readonly timestamp: number;
readonly maxHeightPrevoted: number;
readonly maxHeightGenerated: number;
readonly aggregateCommit: {
readonly height: number;
readonly aggregationBits: Buffer;
readonly certificateSignature: Buffer;
};
readonly validatorsHash?: Buffer;
readonly stateRoot?: Buffer;
readonly transactionRoot?: Buffer;
readonly assets: ReadonlyArray<BlockHeaderAsset>;
readonly assetsRoot?: Buffer;
signature?: Buffer;
id?: Buffer;
}
Expand All @@ -43,9 +45,17 @@ export interface BlockHeaderJSON {
readonly height: number;
readonly generatorAddress: string;
readonly previousBlockID: string;
readonly maxHeightPrevoted: number;
readonly maxHeightGenerated: number;
readonly aggregateCommit: {
readonly height: number;
readonly aggregationBits: string;
readonly certificateSignature: string;
};
readonly validatorsHash: string;
readonly stateRoot: string;
readonly transactionRoot: string;
readonly assets: ReadonlyArray<BlockHeaderAsset>;
readonly assetsRoot: string;
readonly signature: string;
readonly id: string;
}
Expand All @@ -56,9 +66,17 @@ export class BlockHeader {
public readonly generatorAddress: Buffer;
public readonly previousBlockID: Buffer;
public readonly timestamp: number;
public readonly maxHeightPrevoted: number;
public readonly maxHeightGenerated: number;
public readonly aggregateCommit: {
readonly height: number;
readonly aggregationBits: Buffer;
readonly certificateSignature: Buffer;
};
private _validatorsHash?: Buffer;
private _stateRoot?: Buffer;
private _transactionRoot?: Buffer;
private readonly _assets: BlockHeaderAsset[];
private _assetsRoot?: Buffer;
private _signature?: Buffer;
private _id?: Buffer;

Expand All @@ -68,20 +86,28 @@ export class BlockHeader {
height,
generatorAddress,
previousBlockID,
maxHeightPrevoted,
maxHeightGenerated,
aggregateCommit,
validatorsHash,
stateRoot,
assetsRoot,
transactionRoot,
signature,
id,
assets,
}: BlockHeaderAttrs) {
this.version = version;
this.height = height;
this.generatorAddress = generatorAddress;
this.previousBlockID = previousBlockID;
this.timestamp = timestamp;
this.maxHeightPrevoted = maxHeightPrevoted;
this.maxHeightGenerated = maxHeightGenerated;
this.aggregateCommit = aggregateCommit;
this._validatorsHash = validatorsHash;
this._stateRoot = stateRoot;
this._transactionRoot = transactionRoot;
this._assets = objects.cloneDeep<BlockHeaderAsset[]>([...assets]);
this._assetsRoot = assetsRoot;

this._signature = signature;
this._id = id;
Expand All @@ -104,6 +130,15 @@ export class BlockHeader {
this._resetComputedValues();
}

public get assetsRoot() {
return this._assetsRoot;
}

public set assetsRoot(val) {
this._assetsRoot = val;
this._resetComputedValues();
}

public get transactionRoot() {
return this._transactionRoot;
}
Expand All @@ -113,6 +148,15 @@ export class BlockHeader {
this._resetComputedValues();
}

public get validatorsHash() {
return this._validatorsHash;
}

public set validatorsHash(val) {
this._validatorsHash = val;
this._resetComputedValues();
}

public getBytes(): Buffer {
return codec.encode(blockHeaderSchema, this._getBlockHeaderProps());
}
Expand All @@ -121,7 +165,7 @@ export class BlockHeader {
return codec.toJSON(blockHeaderSchemaWithId, this._getAllProps());
}

public toObject(): BlockHeaderAttrs {
public toObject(): Required<BlockHeaderAttrs> {
return this._getAllProps();
}

Expand Down Expand Up @@ -165,20 +209,6 @@ export class BlockHeader {
);
}

public getAsset(moduleID: number): Buffer | undefined {
return this._assets.find(a => a.moduleID === moduleID)?.data;
}

public setAsset(moduleID: number, value: Buffer): void {
const asset = this.getAsset(moduleID);
if (asset) {
throw new Error(`Module asset for "${moduleID}" is already set.`);
}

this._assets.push({ moduleID, data: value });
this._resetComputedValues();
}

public get signature(): Buffer {
if (!this._signature) {
throw new Error('Block header is not signed.');
Expand Down Expand Up @@ -210,15 +240,31 @@ export class BlockHeader {
}

private _getSigningProps() {
if (!this.assetsRoot) {
throw new Error('Asset root is empty.');
}
if (!this.stateRoot) {
throw new Error('State root is empty.');
}
if (!this.transactionRoot) {
throw new Error('State root is empty.');
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
}
if (!this.validatorsHash) {
throw new Error('Validators hash is empty.');
}
return {
version: this.version,
timestamp: this.timestamp,
height: this.height,
previousBlockID: this.previousBlockID,
stateRoot: this.stateRoot,
assetsRoot: this.assetsRoot,
transactionRoot: this.transactionRoot,
validatorsHash: this.validatorsHash,
aggregateCommit: this.aggregateCommit,
generatorAddress: this.generatorAddress,
assets: this._assets,
maxHeightPrevoted: this.maxHeightPrevoted,
maxHeightGenerated: this.maxHeightGenerated,
};
}

Expand Down
15 changes: 13 additions & 2 deletions elements/lisk-chain/src/data_access/data_access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Block } from '../block';
import { BlockCache } from './cache';
import { Storage as StorageAccess } from './storage';
import { StateStore } from '../state_store';
import { BlockAssets } from '../block_assets';

interface DAConstructor {
readonly db: KVStore;
Expand Down Expand Up @@ -299,6 +300,7 @@ export class DataAccess {
finalizedHeight,
encodedHeader,
encodedPayload,
block.assets.getBytes(),
stateStore,
removeFromTemp,
);
Expand All @@ -312,12 +314,21 @@ export class DataAccess {
const { id: blockID, height } = block.header;
const txIDs = block.payload.map(tx => tx.id);
const encodedBlock = block.getBytes();
await this._storage.deleteBlock(blockID, height, txIDs, encodedBlock, stateStore, saveToTemp);
await this._storage.deleteBlock(
blockID,
height,
txIDs,
block.assets.getBytes(),
encodedBlock,
stateStore,
saveToTemp,
);
}

private _decodeRawBlock(block: RawBlock): Block {
const header = BlockHeader.fromBytes(block.header);
const transactions = block.payload.map(txBytes => Transaction.fromBytes(txBytes));
return new Block(header, transactions);
const assets = BlockAssets.fromBytes(block.assets);
return new Block(header, transactions, assets);
}
}
Loading