-
Notifications
You must be signed in to change notification settings - Fork 458
/
block_assets.ts
141 lines (118 loc) · 3.86 KB
/
block_assets.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* 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 { validator } from '@liskhq/lisk-validator';
import { codec } from '@liskhq/lisk-codec';
import { MerkleTree } from '@liskhq/lisk-tree';
import { blockAssetSchema } from './schema';
import { MAX_ASSET_DATA_SIZE_BYTES, NAME_REGEX } from './constants';
import { JSONObject } from './types';
export interface BlockAsset {
module: string;
data: Buffer;
}
export type BlockAssetJSON = JSONObject<BlockAsset>;
export class BlockAssets {
private readonly _assets: BlockAsset[] = [];
private _assetRoot!: Buffer;
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 async getRoot(): Promise<Buffer> {
this._assetRoot = await this._calculateRoot();
return this._assetRoot;
}
public getBytes(): Buffer[] {
return this._assets.map(asset => codec.encode(blockAssetSchema, asset));
}
public getAsset(module: string): Buffer | undefined {
return this._assets.find(a => a.module === module)?.data;
}
public getAll(): BlockAsset[] {
return [...this._assets];
}
public toJSON(): BlockAssetJSON[] {
return this._assets.map(asset => ({
module: asset.module,
data: asset.data.toString('hex'),
}));
}
public setAsset(module: string, value: Buffer): void {
const asset = this.getAsset(module);
if (asset) {
throw new Error(`Module asset for "${module}" is already set.`);
}
this._assets.push({ module, data: value });
}
public sort(): void {
this._assets.sort((a1, a2) => a1.module.localeCompare(a2.module, 'en'));
}
public validate(): void {
let last = this._assets[0];
let i = 0;
for (const asset of this._assets) {
validator.validate(blockAssetSchema, asset);
if (!NAME_REGEX.test(asset.module)) {
throw new Error(`Invalid module name ${asset.module}`);
}
// Data size of each module should not be greater than max asset data size
if (asset.data.byteLength > MAX_ASSET_DATA_SIZE_BYTES) {
throw new Error(
`Module with ID ${asset.module} has data size more than ${MAX_ASSET_DATA_SIZE_BYTES} bytes.`,
);
}
if (last.module > asset.module) {
throw new Error(
'Assets are not sorted by the module property value in lexicographical order.',
);
}
// Check for duplicates
if (i > 0 && asset.module === last.module) {
throw new Error(`Module with ID ${this._assets[i].module} has duplicate entries.`);
}
i += 1;
last = asset;
}
}
public validateGenesis(): void {
let last = this._assets[0];
let i = 0;
for (const asset of this._assets) {
validator.validate(blockAssetSchema, asset);
if (last.module > asset.module) {
throw new Error(
'Assets are not sorted by the module property value in lexicographical order.',
);
}
if (i > 0 && asset.module === last.module) {
throw new Error(`Module with ID ${this._assets[i].module} has duplicate entries.`);
}
i += 1;
last = asset;
}
}
private async _calculateRoot(): Promise<Buffer> {
const merkleTree = new MerkleTree();
await merkleTree.init(this.getBytes());
return merkleTree.root;
}
}