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

Commit

Permalink
Merge branch 'release/6.0.0' of https://github.com/LiskHQ/lisk-sdk in…
Browse files Browse the repository at this point in the history
…to development
  • Loading branch information
shuse2 committed Aug 4, 2023
2 parents f34f970 + b7b4b51 commit e990c67
Show file tree
Hide file tree
Showing 16 changed files with 575 additions and 189 deletions.
2 changes: 1 addition & 1 deletion elements/lisk-cryptography/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"varuint-bitcoin": "1.1.2"
},
"peerDependencies": {
"@chainsafe/blst": "0.2.6",
"@chainsafe/blst": "0.2.9",
"sodium-native": "3.2.1"
},
"peerDependenciesMeta": {
Expand Down
2 changes: 1 addition & 1 deletion framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"test:functional": "jest --config=./test/functional/jest.config.js --runInBand"
},
"dependencies": {
"@chainsafe/blst": "0.2.6",
"@chainsafe/blst": "0.2.9",
"@liskhq/lisk-api-client": "^6.0.0-beta.4",
"@liskhq/lisk-chain": "^0.4.0-beta.4",
"@liskhq/lisk-codec": "^0.3.0-beta.3",
Expand Down
1 change: 1 addition & 0 deletions framework/src/modules/base_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface StoreGetter {
getStore: (moduleID: Buffer, storePrefix: Buffer) => SubStore;
}

// LIP: https://github.com/LiskHQ/lips/blob/main/proposals/lip-0040.md#module-store-prefix-1
export const computeStorePrefix = (name: string): Buffer => {
const prefix = utils.hash(Buffer.from(name, 'utf-8')).slice(0, 4);
// eslint-disable-next-line no-bitwise
Expand Down
106 changes: 55 additions & 51 deletions framework/src/modules/interoperability/base_state_recovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { objects as objectUtils } from '@liskhq/lisk-utils';
import { SparseMerkleTree } from '@liskhq/lisk-db';
import { utils } from '@liskhq/lisk-cryptography';
import { BaseInteroperabilityCommand } from './base_interoperability_command';
import { EMPTY_BYTES, EMPTY_HASH } from './constants';
import { RECOVERED_STORE_VALUE } from './constants';
import { stateRecoveryParamsSchema } from './schemas';
import { RecoverContext, StateRecoveryParams } from './types';
import {
Expand All @@ -29,7 +29,9 @@ import { TerminatedStateStore } from './stores/terminated_state';
import { computeStorePrefix } from '../base_store';
import { BaseCCMethod } from './base_cc_method';
import { BaseInteroperabilityInternalMethod } from './base_interoperability_internal_methods';
import { InvalidSMTVerification } from './events/invalid_smt_verification';

// LIP: https://github.com/LiskHQ/lips/blob/main/proposals/lip-0054.md#state-recovery-command
export class BaseStateRecoveryCommand<
T extends BaseInteroperabilityInternalMethod,
> extends BaseInteroperabilityCommand<T> {
Expand All @@ -39,12 +41,13 @@ export class BaseStateRecoveryCommand<
context: CommandVerifyContext<StateRecoveryParams>,
): Promise<VerificationResult> {
const {
params: { chainID, storeEntries, siblingHashes, module },
params: { chainID, storeEntries, module },
} = context;

const terminatedStateSubstore = this.stores.get(TerminatedStateStore);
const terminatedStateAccountExists = await terminatedStateSubstore.has(context, chainID);

// The terminated account has to exist for this sidechain.
if (!terminatedStateAccountExists) {
return {
status: VerifyStatus.FAIL,
Expand All @@ -70,57 +73,30 @@ export class BaseStateRecoveryCommand<
};
}

// The module indicated in the transaction params must have a recover function.
// For example, this means that modules such as Interoperability or Auth cannot be recovered.
if (!moduleMethod.recover) {
return {
status: VerifyStatus.FAIL,
error: new Error('Module is not recoverable.'),
};
}

const { stateRoot } = terminatedStateAccount;
const queryKeys = [];
const storeQueries = [];

const storePrefix = computeStorePrefix(module);

// For efficiency, only subStorePrefix+storeKey is enough to check for pairwise distinct keys in verification
for (const entry of storeEntries) {
if (entry.storeValue.equals(EMPTY_BYTES)) {
return {
status: VerifyStatus.FAIL,
error: new Error('Recovered store value cannot be empty.'),
};
}
const queryKey = Buffer.concat([
storePrefix,
entry.substorePrefix,
utils.hash(entry.storeKey),
]);
const queryKey = Buffer.concat([entry.substorePrefix, entry.storeKey]);
queryKeys.push(queryKey);
storeQueries.push({
key: queryKey,
value: utils.hash(entry.storeValue),
bitmap: entry.bitmap,
});
}

// Check that all keys are pairwise distinct, meaning that we are not trying to recover the same entry twice.
if (!objectUtils.bufferArrayUniqueItems(queryKeys)) {
return {
status: VerifyStatus.FAIL,
error: new Error('Recovered store keys are not pairwise distinct.'),
};
}

const proofOfInclusionStores = { siblingHashes, queries: storeQueries };
const sparseMerkleTree = new SparseMerkleTree();
const verified = await sparseMerkleTree.verify(stateRoot, queryKeys, proofOfInclusionStores);

if (!verified) {
return {
status: VerifyStatus.FAIL,
error: new Error('State recovery proof of inclusion is not valid.'),
};
}

return {
status: VerifyStatus.OK,
};
Expand All @@ -130,16 +106,50 @@ export class BaseStateRecoveryCommand<
const {
params: { chainID, storeEntries, module, siblingHashes },
} = context;
const storeQueries = [];
const storeQueriesVerify = [];
const queryKeys = [];
// Calculate store prefix from the module name according to LIP 0040.
const storePrefix = computeStorePrefix(module);

for (const entry of storeEntries) {
const queryKey = Buffer.concat([
storePrefix,
entry.substorePrefix,
utils.hash(entry.storeKey),
]);
queryKeys.push(queryKey);
storeQueriesVerify.push({
key: queryKey,
value: utils.hash(entry.storeValue),
bitmap: entry.bitmap,
});
}
const terminatedStateAccount = await this.stores
.get(TerminatedStateStore)
.get(context, chainID);

const proofOfInclusionStores = { siblingHashes, queries: storeQueriesVerify };
// The SMT verification step is computationally expensive. Therefore,
// it is done in the execution step such that the transaction fee must be paid.
const smtVerified = await new SparseMerkleTree().verify(
terminatedStateAccount.stateRoot,
queryKeys,
proofOfInclusionStores,
);

if (!smtVerified) {
this.events.get(InvalidSMTVerification).error(context);
throw new Error('State recovery proof of inclusion is not valid.');
}

// Casting for type issue. `recover` already verified to exist in module for verify
const moduleMethod = this.interoperableCCMethods.get(module) as BaseCCMethod & {
recover: (ctx: RecoverContext) => Promise<void>;
};
const storePrefix = computeStorePrefix(module);

const storeQueriesUpdate = [];
for (const entry of storeEntries) {
try {
// The recover function corresponding to trsParams.module applies the recovery logic.
await moduleMethod.recover({
...context,
module,
Expand All @@ -148,28 +158,22 @@ export class BaseStateRecoveryCommand<
storeKey: entry.storeKey,
storeValue: entry.storeValue,
});
storeQueriesUpdate.push({
key: Buffer.concat([storePrefix, entry.substorePrefix, utils.hash(entry.storeKey)]),
value: RECOVERED_STORE_VALUE,
bitmap: entry.bitmap,
});
} catch (err) {
throw new Error(`Recovery failed for module: ${module}`);
}

storeQueries.push({
key: Buffer.concat([storePrefix, entry.substorePrefix, utils.hash(entry.storeKey)]),
value: EMPTY_HASH,
bitmap: entry.bitmap,
});
}

const sparseMerkleTree = new SparseMerkleTree();
const root = await sparseMerkleTree.calculateRoot({
queries: storeQueries,
const root = await new SparseMerkleTree().calculateRoot({
queries: storeQueriesUpdate,
siblingHashes,
});

const terminatedStateSubstore = this.stores.get(TerminatedStateStore);

const terminatedStateAccount = await terminatedStateSubstore.get(context, chainID);

await terminatedStateSubstore.set(context, chainID, {
await this.stores.get(TerminatedStateStore).set(context, chainID, {
...terminatedStateAccount,
stateRoot: root,
});
Expand Down
3 changes: 2 additions & 1 deletion framework/src/modules/interoperability/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const BLS_PUBLIC_KEY_LENGTH = 48;
export const BLS_SIGNATURE_LENGTH = 96;
export const SMT_KEY_LENGTH = 38;
export const NUMBER_ACTIVE_VALIDATORS_MAINCHAIN = 101;
export const MESSAGE_TAG_CHAIN_REG = 'LSK_CHAIN_REGISTRATION_';
export const MESSAGE_TAG_CHAIN_REG = 'LSK_CRM_';
export const LIVENESS_LIMIT = 2419200; // 28 * 24 * 3600
export const MAX_CCM_SIZE = 10240;
export const EMPTY_FEE_ADDRESS = Buffer.alloc(0);
Expand Down Expand Up @@ -83,6 +83,7 @@ export const COMMAND_NAME_STATE_RECOVERY = 'recoverState';
export const COMMAND_NAME_MESSAGE_RECOVERY = 'recoverMessage';
export const COMMAND_NAME_STATE_RECOVERY_INIT = 'initializeStateRecovery';
export const COMMAND_NAME_LIVENESS_TERMINATION = 'terminateSidechainForLiveness';
export const RECOVERED_STORE_VALUE = Buffer.alloc(32);

// Events
export const EVENT_NAME_CHAIN_ACCOUNT_UPDATED = 'chainAccountUpdated';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright © 2023 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 { BaseEvent, EventQueuer } from '../../base_event';

export class InvalidRMTVerification extends BaseEvent<undefined> {
public error(ctx: EventQueuer): void {
this.add(ctx, undefined);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright © 2023 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 { BaseEvent, EventQueuer } from '../../base_event';

export class InvalidSMTVerification extends BaseEvent<undefined> {
public error(ctx: EventQueuer): void {
this.add(ctx, undefined);
}
}
4 changes: 4 additions & 0 deletions framework/src/modules/interoperability/mainchain/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ import { GenesisBlockExecuteContext } from '../../../state_machine';
import { getMainchainID, isValidName } from '../utils';
import { RegisteredNamesStore } from '../stores/registered_names';
import { InvalidNameError } from '../errors';
import { InvalidSMTVerification } from '../events/invalid_smt_verification';
import { InvalidRMTVerification } from '../events/invalid_rmt_verification';

export class MainchainInteroperabilityModule extends BaseInteroperabilityModule {
public crossChainMethod = new MainchainCCMethod(this.stores, this.events);
Expand Down Expand Up @@ -173,6 +175,8 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule
InvalidCertificateSignatureEvent,
new InvalidCertificateSignatureEvent(this.name),
);
this.events.register(InvalidSMTVerification, new InvalidSMTVerification(this.name));
this.events.register(InvalidRMTVerification, new InvalidRMTVerification(this.name));
}

public addDependencies(tokenMethod: TokenMethod, feeMethod: FeeMethod) {
Expand Down
Loading

0 comments on commit e990c67

Please sign in to comment.