From 40f35f2b26a4ec6623d982927c8477df7cc14034 Mon Sep 17 00:00:00 2001 From: Ishan Date: Mon, 16 Oct 2023 13:49:03 +0200 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=8C=B1=20Add=20hello=20and=20react?= =?UTF-8?q?=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/default/config.json | 4 + .../pos-sidechain-example-one/src/app/app.ts | 6 + .../hello/cc_commands/react_command.ts | 48 ++++++ .../src/app/modules/hello/cc_method.ts | 3 + .../hello/commands/create_hello_command.ts | 89 +++++++++++ .../src/app/modules/hello/constants.ts | 4 + .../src/app/modules/hello/endpoint.ts | 47 ++++++ .../src/app/modules/hello/events/.gitkeep | 0 .../src/app/modules/hello/events/new_hello.ts | 39 +++++ .../src/app/modules/hello/method.ts | 14 ++ .../src/app/modules/hello/module.ts | 141 ++++++++++++++++ .../src/app/modules/hello/schema.ts | 150 ++++++++++++++++++ .../src/app/modules/hello/stores/.gitkeep | 0 .../src/app/modules/hello/stores/counter.ts | 36 +++++ .../src/app/modules/hello/stores/message.ts | 34 ++++ .../src/app/modules/hello/stores/reaction.ts | 33 ++++ .../src/app/modules/hello/types.ts | 9 ++ .../config/default/config.json | 4 + .../pos-sidechain-example-two/src/app/app.ts | 7 +- .../src/app/modules/react/cc_method.ts | 17 ++ .../modules/react/commands/react_command.ts | 108 +++++++++++++ .../src/app/modules/react/constants.ts | 4 + .../src/app/modules/react/endpoint.ts | 3 + .../src/app/modules/react/events/.gitkeep | 0 .../src/app/modules/react/method.ts | 3 + .../src/app/modules/react/module.ts | 85 ++++++++++ .../src/app/modules/react/schemas.ts | 94 +++++++++++ .../src/app/modules/react/stores/.gitkeep | 0 .../src/app/modules/react/types.ts | 28 ++++ examples/interop/run_sidechains.json | 18 ++- 30 files changed, 1025 insertions(+), 3 deletions(-) create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_method.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/commands/create_hello_command.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/constants.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/endpoint.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/.gitkeep create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/new_hello.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/method.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/module.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/.gitkeep create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/counter.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/message.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts create mode 100644 examples/interop/pos-sidechain-example-one/src/app/modules/hello/types.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/cc_method.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/commands/react_command.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/constants.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/endpoint.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/events/.gitkeep create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/method.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/module.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/stores/.gitkeep create mode 100644 examples/interop/pos-sidechain-example-two/src/app/modules/react/types.ts diff --git a/examples/interop/pos-sidechain-example-one/config/default/config.json b/examples/interop/pos-sidechain-example-one/config/default/config.json index b1cb2b0e91d..562a5b59e4b 100644 --- a/examples/interop/pos-sidechain-example-one/config/default/config.json +++ b/examples/interop/pos-sidechain-example-one/config/default/config.json @@ -48,6 +48,10 @@ "ccuFee": "500000000", "receivingChainIPCPath": "~/.lisk/mainchain-node-one", "registrationHeight": 10 + }, + "dashboard": { + "applicationUrl": "ws://127.0.0.1:7885/rpc-ws", + "port": 4006 } } } diff --git a/examples/interop/pos-sidechain-example-one/src/app/app.ts b/examples/interop/pos-sidechain-example-one/src/app/app.ts index d9dc8b2ad28..8b084b532fe 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/app.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/app.ts @@ -1,10 +1,16 @@ import { Application, PartialApplicationConfig } from 'lisk-sdk'; import { registerModules } from './modules'; import { registerPlugins } from './plugins'; +import { HelloModule } from './modules/hello/module'; export const getApplication = (config: PartialApplicationConfig): Application => { const { app } = Application.defaultApplication(config); + const helloModule = new HelloModule(); + app.registerModule(helloModule); + + app.registerInteroperableModule(helloModule); + registerModules(app); registerPlugins(app); diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts new file mode 100644 index 00000000000..73358102213 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts @@ -0,0 +1,48 @@ +/* eslint-disable class-methods-use-this */ + +import { BaseCCCommand, CrossChainMessageContext, codec } from 'lisk-sdk'; +import { crossChainReactParamsSchema, CCReactMessageParams } from '../schema'; +import { + MAX_RESERVED_ERROR_STATUS, + CCM_STATUS_OK, + CROSS_CHAIN_COMMAND_NAME_REACT, +} from '../constants'; +import { ReactionStore } from '../stores/reaction'; + +export class ReactCCCommand extends BaseCCCommand { + public schema = crossChainReactParamsSchema; + + public get name(): string { + return CROSS_CHAIN_COMMAND_NAME_REACT; + } + + // eslint-disable-next-line @typescript-eslint/require-await + public async verify(ctx: CrossChainMessageContext): Promise { + const { ccm } = ctx; + + if (ccm.status > MAX_RESERVED_ERROR_STATUS) { + throw new Error('Invalid CCM status code.'); + } else if (ccm.status === CCM_STATUS_OK) { + throw new Error('Bounced CCM.'); + } + } + + public async execute(ctx: CrossChainMessageContext): Promise { + const { ccm } = ctx; + // const methodContext = ctx.getMethodContext(); + // const { sendingChainID, status, receivingChainID } = ccm; + + const params = codec.decode(crossChainReactParamsSchema, ccm.params); + const { helloMessageID, reactionType, senderAddress } = params; + const reactionSubstore = this.stores.get(ReactionStore); + + const msgReactions = await reactionSubstore.get(ctx, helloMessageID); + + if (reactionType === 0) { + // TODO: Check if the Likes array already contains the sender address. If yes, remove the address to unlike the post. + msgReactions.reactions.like.push(senderAddress); + } + + await reactionSubstore.set(ctx, helloMessageID, msgReactions); + } +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_method.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_method.ts new file mode 100644 index 00000000000..f8535173f9a --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_method.ts @@ -0,0 +1,3 @@ +import { BaseCCMethod } from 'lisk-sdk'; + +export class HelloInteroperableMethod extends BaseCCMethod {} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/commands/create_hello_command.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/commands/create_hello_command.ts new file mode 100644 index 00000000000..86cc22753bd --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/commands/create_hello_command.ts @@ -0,0 +1,89 @@ +/* eslint-disable class-methods-use-this */ + +import { + BaseCommand, + CommandVerifyContext, + CommandExecuteContext, + VerificationResult, + VerifyStatus, +} from 'lisk-sdk'; +import { createHelloSchema } from '../schema'; +import { MessageStore } from '../stores/message'; +import { counterKey, CounterStore, CounterStoreData } from '../stores/counter'; +import { ModuleConfig } from '../types'; +import { NewHelloEvent } from '../events/new_hello'; + +interface Params { + message: string; +} + +export class CreateHelloCommand extends BaseCommand { + public schema = createHelloSchema; + private _blacklist!: string[]; + + // eslint-disable-next-line @typescript-eslint/require-await + public async init(config: ModuleConfig): Promise { + // Set _blacklist to the value of the blacklist defined in the module config + this._blacklist = config.blacklist; + // Set the max message length to the value defined in the module config + this.schema.properties.message.maxLength = config.maxMessageLength; + // Set the min message length to the value defined in the module config + this.schema.properties.message.minLength = config.minMessageLength; + } + + // eslint-disable-next-line @typescript-eslint/require-await + public async verify(context: CommandVerifyContext): Promise { + let validation: VerificationResult; + const wordList = context.params.message.split(' '); + const found = this._blacklist.filter(value => wordList.includes(value)); + if (found.length > 0) { + context.logger.info('==== FOUND: Message contains a blacklisted word ===='); + throw new Error(`Illegal word in hello message: ${found.toString()}`); + } else { + context.logger.info('==== NOT FOUND: Message contains no blacklisted words ===='); + validation = { + status: VerifyStatus.OK, + }; + } + return validation; + } + + public async execute(context: CommandExecuteContext): Promise { + // 1. Get account data of the sender of the Hello transaction. + const { senderAddress } = context.transaction; + // 2. Get message and counter stores. + const messageSubstore = this.stores.get(MessageStore); + const counterSubstore = this.stores.get(CounterStore); + + // 3. Save the Hello message to the message store, using the senderAddress as key, and the message as value. + await messageSubstore.set(context, senderAddress, { + message: context.params.message, + }); + + // 3. Get the Hello counter from the counter store. + let helloCounter: CounterStoreData; + try { + helloCounter = await counterSubstore.get(context, counterKey); + } catch (error) { + helloCounter = { + counter: 0, + }; + } + // 5. Increment the Hello counter +1. + helloCounter.counter += 1; + + // 6. Save the Hello counter to the counter store. + await counterSubstore.set(context, counterKey, helloCounter); + + // 7. Emit a "New Hello" event + const newHelloEvent = this.events.get(NewHelloEvent); + newHelloEvent.add( + context, + { + senderAddress: context.transaction.senderAddress, + message: context.params.message, + }, + [context.transaction.senderAddress], + ); + } +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/constants.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/constants.ts new file mode 100644 index 00000000000..9b427343540 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/constants.ts @@ -0,0 +1,4 @@ +export const CROSS_CHAIN_COMMAND_NAME_REACT = 'reactCrossChain'; + +export const MAX_RESERVED_ERROR_STATUS = 63; +export const CCM_STATUS_OK = 0; diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/endpoint.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/endpoint.ts new file mode 100644 index 00000000000..4db39083be5 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/endpoint.ts @@ -0,0 +1,47 @@ +import { BaseEndpoint, ModuleEndpointContext, cryptography } from 'lisk-sdk'; +import { counterKey, CounterStore, CounterStoreData } from './stores/counter'; +import { MessageStore, MessageStoreData } from './stores/message'; +import { ReactionStore, ReactionStoreData } from './stores/reaction'; + +export class HelloEndpoint extends BaseEndpoint { + public async getHelloCounter(ctx: ModuleEndpointContext): Promise { + const counterSubStore = this.stores.get(CounterStore); + + const helloCounter = await counterSubStore.get(ctx, counterKey); + + return helloCounter; + } + + public async getReactions(ctx: ModuleEndpointContext): Promise { + const reactionSubStore = this.stores.get(ReactionStore); + + const { address } = ctx.params; + if (typeof address !== 'string') { + throw new Error('Parameter address must be a string.'); + } + cryptography.address.validateLisk32Address(address); + + const reactions = await reactionSubStore.get( + ctx, + cryptography.address.getAddressFromLisk32Address(address), + ); + + return reactions; + } + + public async getHello(ctx: ModuleEndpointContext): Promise { + const messageSubStore = this.stores.get(MessageStore); + + const { address } = ctx.params; + if (typeof address !== 'string') { + throw new Error('Parameter address must be a string.'); + } + cryptography.address.validateLisk32Address(address); + const helloMessage = await messageSubStore.get( + ctx, + cryptography.address.getAddressFromLisk32Address(address), + ); + + return helloMessage; + } +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/.gitkeep b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/new_hello.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/new_hello.ts new file mode 100644 index 00000000000..a613a9d18c0 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/events/new_hello.ts @@ -0,0 +1,39 @@ +/* + * Copyright © 2022 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 } from 'lisk-sdk'; + +export const newHelloEventSchema = { + $id: '/hello/events/new_hello', + type: 'object', + required: ['senderAddress', 'message'], + properties: { + senderAddress: { + dataType: 'bytes', + fieldNumber: 1, + }, + message: { + dataType: 'string', + fieldNumber: 2, + }, + }, +}; + +export interface NewHelloEventData { + senderAddress: Buffer; + message: string; +} + +export class NewHelloEvent extends BaseEvent { + public schema = newHelloEventSchema; +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/method.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/method.ts new file mode 100644 index 00000000000..0ce458081f6 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/method.ts @@ -0,0 +1,14 @@ +import { BaseMethod, ImmutableMethodContext } from 'lisk-sdk'; +import { MessageStore, MessageStoreData } from './stores/message'; + +export class HelloMethod extends BaseMethod { + public async getHello( + methodContext: ImmutableMethodContext, + address: Buffer, + ): Promise { + const messageSubStore = this.stores.get(MessageStore); + const helloMessage = await messageSubStore.get(methodContext, address); + + return helloMessage; + } +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/module.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/module.ts new file mode 100644 index 00000000000..0711a02c6d0 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/module.ts @@ -0,0 +1,141 @@ +/* eslint-disable class-methods-use-this */ + +import { + validator, + BaseInteroperableModule, + BlockAfterExecuteContext, + BlockExecuteContext, + BlockVerifyContext, + GenesisBlockExecuteContext, + InsertAssetContext, + ModuleInitArgs, + ModuleMetadata, + TransactionExecuteContext, + TransactionVerifyContext, + utils, + VerificationResult, +} from 'lisk-sdk'; +import { CreateHelloCommand } from './commands/create_hello_command'; +import { ReactCCCommand } from './cc_commands/react_command'; +import { HelloEndpoint } from './endpoint'; +import { NewHelloEvent } from './events/new_hello'; +import { HelloMethod } from './method'; +import { + configSchema, + getHelloCounterResponseSchema, + getHelloRequestSchema, + getHelloResponseSchema, +} from './schema'; +import { CounterStore } from './stores/counter'; +import { MessageStore } from './stores/message'; +import { ReactionStore, reactionStoreSchema } from './stores/reaction'; +import { ModuleConfigJSON } from './types'; +import { HelloInteroperableMethod } from './cc_method'; + +export const defaultConfig = { + maxMessageLength: 256, + minMessageLength: 3, + blacklist: ['illegalWord1'], +}; + +export class HelloModule extends BaseInteroperableModule { + public constructor() { + super(); + // registration of stores and events + this.stores.register(CounterStore, new CounterStore(this.name, 0)); + this.stores.register(MessageStore, new MessageStore(this.name, 1)); + this.stores.register(ReactionStore, new ReactionStore(this.name, 2)); + this.events.register(NewHelloEvent, new NewHelloEvent(this.name)); + } + + public metadata(): ModuleMetadata { + return { + endpoints: [ + { + name: this.endpoint.getHello.name, + request: getHelloRequestSchema, + response: getHelloResponseSchema, + }, + { + name: this.endpoint.getReactions.name, + request: getHelloRequestSchema, + response: reactionStoreSchema, + }, + { + name: this.endpoint.getHelloCounter.name, + response: getHelloCounterResponseSchema, + }, + ], + commands: this.commands.map(command => ({ + name: command.name, + params: command.schema, + })), + events: this.events.values().map(v => ({ + name: v.name, + data: v.schema, + })), + assets: [], + stores: [], + }; + } + + // Lifecycle hooks + // eslint-disable-next-line @typescript-eslint/require-await + public async init(args: ModuleInitArgs): Promise { + // Get the module config defined in the config.json file + const { moduleConfig } = args; + // Overwrite the default module config with values from config.json, if set + const config = utils.objects.mergeDeep({}, defaultConfig, moduleConfig) as ModuleConfigJSON; + // Validate the provided config with the config schema + validator.validator.validate(configSchema, config); + // Call the command init() method with config values as parameters + this.commands[0].init(config).catch(err => { + // eslint-disable-next-line no-console + console.log('Error: ', err); + }); + } + + public async insertAssets(_context: InsertAssetContext) { + // initialize block generation, add asset + } + + public async verifyAssets(_context: BlockVerifyContext): Promise { + // verify block + } + + // Lifecycle hooks + // eslint-disable-next-line @typescript-eslint/require-await + public async verifyTransaction(context: TransactionVerifyContext): Promise { + // verify transaction will be called multiple times in the transaction pool + context.logger.info('TX VERIFICATION'); + const result = { + status: 1, + }; + return result; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async beforeCommandExecute(_context: TransactionExecuteContext): Promise {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async afterCommandExecute(_context: TransactionExecuteContext): Promise {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async initGenesisState(_context: GenesisBlockExecuteContext): Promise {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async finalizeGenesisState(_context: GenesisBlockExecuteContext): Promise {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async beforeTransactionsExecute(_context: BlockExecuteContext): Promise {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public async afterTransactionsExecute(_context: BlockAfterExecuteContext): Promise {} + + public endpoint = new HelloEndpoint(this.stores, this.offchainStores); + public method = new HelloMethod(this.stores, this.events); + public commands = [new CreateHelloCommand(this.stores, this.events)]; + public reactCCCommand = new ReactCCCommand(this.stores, this.events); + public crossChainMethod = new HelloInteroperableMethod(this.stores, this.events); + public crossChainCommand = [this.reactCCCommand]; +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts new file mode 100644 index 00000000000..a59e5ec79d7 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts @@ -0,0 +1,150 @@ +export interface CreateHelloParams { + message: string; +} + +export const createHelloSchema = { + $id: 'hello/createHello-params', + title: 'CreateHelloCommand transaction parameter for the Hello module', + type: 'object', + required: ['message'], + properties: { + message: { + dataType: 'string', + fieldNumber: 1, + minLength: 3, + maxLength: 256, + }, + }, +}; + +export const configSchema = { + $id: '/hello/config', + type: 'object', + properties: { + maxMessageLength: { + type: 'integer', + format: 'uint32', + }, + minMessageLength: { + type: 'integer', + format: 'uint32', + }, + blacklist: { + type: 'array', + items: { + type: 'string', + minLength: 1, + maxLength: 40, + }, + }, + }, + required: ['maxMessageLength', 'minMessageLength', 'blacklist'], +}; + +export const getHelloCounterResponseSchema = { + $id: 'modules/hello/endpoint/getHelloCounter', + type: 'object', + required: ['counter'], + properties: { + counter: { + type: 'number', + format: 'uint32', + }, + }, +}; + +export const getHelloResponseSchema = { + $id: 'modules/hello/endpoint/getHello', + type: 'object', + required: ['message'], + properties: { + message: { + type: 'string', + format: 'utf8', + }, + }, +}; + +export const getHelloRequestSchema = { + $id: 'modules/hello/endpoint/getHelloRequest', + type: 'object', + required: ['address'], + properties: { + address: { + type: 'string', + format: 'lisk32', + }, + }, +}; + +/** + * Parameters of the cross-chain token transfer command + */ +export const crossChainReactParamsSchema = { + /** The unique identifier of the schema. */ + $id: '/lisk/ccReactParams', + type: 'object', + /** The required parameters for the command. */ + required: [ + 'reactionType', + 'helloMessageID', + 'receivingChainID', + 'data', + 'messageFee', + 'messageFeeTokenID', + ], + /** A list describing the available parameters for the command. */ + properties: { + reactionType: { + dataType: 'uint32', + fieldNumber: 1, + }, + data: { + dataType: 'string', + fieldNumber: 2, + }, + /** + * ID of the message. + */ + helloMessageID: { + dataType: 'bytes', + fieldNumber: 3, + }, + /** + * The chain ID of the receiving chain. + * + * `maxLength` and `minLength` are equal to 4. + */ + receivingChainID: { + dataType: 'bytes', + fieldNumber: 4, + minLength: 4, + maxLength: 4, + }, + /** Optional field for data / messages. */ + message: { + dataType: 'string', + fieldNumber: 5, + minLength: 0, + maxLength: 64, + }, + messageFee: { + dataType: 'uint64', + fieldNumber: 6, + }, + messageFeeTokenID: { + dataType: 'bytes', + fieldNumber: 7, + minLength: 8, + maxLength: 8, + }, + }, +}; + +export interface CCReactMessageParams { + reactionType: number; + helloMessageID: Buffer; + receivingChainID: Buffer; + senderAddress: Buffer; + message: string; +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/.gitkeep b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/counter.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/counter.ts new file mode 100644 index 00000000000..254e4976b68 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/counter.ts @@ -0,0 +1,36 @@ +/* + * Copyright © 2022 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 { BaseStore } from 'lisk-sdk'; + +export interface CounterStoreData { + counter: number; +} + +export const counterKey = Buffer.alloc(0); + +export const counterStoreSchema = { + $id: '/hello/counter', + type: 'object', + required: ['counter'], + properties: { + counter: { + dataType: 'uint32', + fieldNumber: 1, + }, + }, +}; + +export class CounterStore extends BaseStore { + public schema = counterStoreSchema; +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/message.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/message.ts new file mode 100644 index 00000000000..55e6af81051 --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/message.ts @@ -0,0 +1,34 @@ +/* + * Copyright © 2022 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 { BaseStore } from 'lisk-sdk'; + +export interface MessageStoreData { + message: string; +} + +export const messageStoreSchema = { + $id: '/hello/message', + type: 'object', + required: ['message'], + properties: { + message: { + dataType: 'string', + fieldNumber: 1, + }, + }, +}; + +export class MessageStore extends BaseStore { + public schema = messageStoreSchema; +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts new file mode 100644 index 00000000000..462f03ff27a --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts @@ -0,0 +1,33 @@ +import { BaseStore } from 'lisk-sdk'; + +export interface ReactionStoreData { + reactions: { + like: Buffer[]; + }; +} + +export const reactionStoreSchema = { + $id: '/hello/message', + type: 'object', + required: ['reactions'], + properties: { + reactions: { + type: 'object', + fieldNumber: 1, + properties: { + like: { + dataType: 'array', + items: { + dataType: 'bytes', + format: 'lisk32', + fieldNumber: 1, + }, + }, + }, + }, + }, +}; + +export class ReactionStore extends BaseStore { + public schema = reactionStoreSchema; +} diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/types.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/types.ts new file mode 100644 index 00000000000..d1c1ddc9f3f --- /dev/null +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/types.ts @@ -0,0 +1,9 @@ +import { JSONObject } from 'lisk-sdk'; + +export interface ModuleConfig { + maxMessageLength: number; + minMessageLength: number; + blacklist: string[]; +} + +export type ModuleConfigJSON = JSONObject; diff --git a/examples/interop/pos-sidechain-example-two/config/default/config.json b/examples/interop/pos-sidechain-example-two/config/default/config.json index fcf83e45fd0..b918eb9de54 100644 --- a/examples/interop/pos-sidechain-example-two/config/default/config.json +++ b/examples/interop/pos-sidechain-example-two/config/default/config.json @@ -48,6 +48,10 @@ "ccuFee": "500000000", "receivingChainIPCPath": "~/.lisk/mainchain-node-two", "registrationHeight": 10 + }, + "dashboard": { + "applicationUrl": "ws://127.0.0.1:7886/rpc-ws", + "port": 4007 } } } diff --git a/examples/interop/pos-sidechain-example-two/src/app/app.ts b/examples/interop/pos-sidechain-example-two/src/app/app.ts index d9dc8b2ad28..62a607b9357 100644 --- a/examples/interop/pos-sidechain-example-two/src/app/app.ts +++ b/examples/interop/pos-sidechain-example-two/src/app/app.ts @@ -1,9 +1,14 @@ import { Application, PartialApplicationConfig } from 'lisk-sdk'; import { registerModules } from './modules'; import { registerPlugins } from './plugins'; +import { ReactModule } from './modules/react/module'; export const getApplication = (config: PartialApplicationConfig): Application => { - const { app } = Application.defaultApplication(config); + const { app, method } = Application.defaultApplication(config); + const reactModule = new ReactModule(); + app.registerModule(reactModule); + app.registerInteroperableModule(reactModule); + reactModule.addDependencies(method.interoperability); registerModules(app); registerPlugins(app); diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/cc_method.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/cc_method.ts new file mode 100644 index 00000000000..b7a881b33db --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/cc_method.ts @@ -0,0 +1,17 @@ +/* + * Copyright © 2022 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 { BaseCCMethod } from 'lisk-sdk'; + +export class ReactInteroperableMethod extends BaseCCMethod {} diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/commands/react_command.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/commands/react_command.ts new file mode 100644 index 00000000000..fc737158a6c --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/commands/react_command.ts @@ -0,0 +1,108 @@ +/* eslint-disable class-methods-use-this */ + +import { + BaseCommand, + CommandVerifyContext, + CommandExecuteContext, + VerificationResult, + VerifyStatus, + codec, +} from 'lisk-sdk'; +import { ReactMethod } from '../method'; +import { CROSS_CHAIN_COMMAND_NAME_REACT } from '../constants'; +import { + crossChainReactParamsSchema, + CCReactMessageParams, + crossChainReactMessageSchema, +} from '../schemas'; +import { InteroperabilityMethod } from '../types'; + +interface Params { + reactionType: number; + helloMessageID: string; + amount: bigint; + receivingChainID: Buffer; + data: string; + messageFee: bigint; + messageFeeTokenID: Buffer; +} + +export class ReactCrossChainCommand extends BaseCommand { + private _interoperabilityMethod!: InteroperabilityMethod; + // private _moduleName!: string; + // private _method!: ReactMethod; + public schema = crossChainReactParamsSchema; + + public get name(): string { + return CROSS_CHAIN_COMMAND_NAME_REACT; + } + + public init(args: { + moduleName: string; + method: ReactMethod; + interoperabilityMethod: InteroperabilityMethod; + }) { + // this._moduleName = args.moduleName; + // this._method = args.method; + this._interoperabilityMethod = args.interoperabilityMethod; + } + + public addDependencies(interoperabilityMethod: InteroperabilityMethod) { + this._interoperabilityMethod = interoperabilityMethod; + } + + // eslint-disable-next-line @typescript-eslint/require-await + public async verify(context: CommandVerifyContext): Promise { + const { params, logger } = context; + + logger.info('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'); + logger.info(params); + logger.info('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'); + + try { + if (params.receivingChainID.equals(context.chainID)) { + throw new Error('Receiving chain cannot be the sending chain.'); + } + + const messageFeeTokenID = await this._interoperabilityMethod.getMessageFeeTokenID( + context.getMethodContext(), + params.receivingChainID, + ); + if (!messageFeeTokenID.equals(params.messageFeeTokenID)) { + throw new Error('Invalid message fee Token ID.'); + } + } catch (err) { + return { + status: VerifyStatus.FAIL, + error: err as Error, + }; + } + return { + status: VerifyStatus.OK, + }; + } + + public async execute(context: CommandExecuteContext): Promise { + const { + params, + transaction: { senderAddress }, + } = context; + + const reactCCM: CCReactMessageParams = { + reactionType: params.reactionType, + data: params.data, + helloMessageID: params.helloMessageID, + }; + + await this._interoperabilityMethod.send( + context.getMethodContext(), + senderAddress, + 'hello', + CROSS_CHAIN_COMMAND_NAME_REACT, + params.receivingChainID, + params.messageFee, + codec.encode(crossChainReactMessageSchema, reactCCM), + context.header.timestamp, + ); + } +} diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/constants.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/constants.ts new file mode 100644 index 00000000000..9b427343540 --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/constants.ts @@ -0,0 +1,4 @@ +export const CROSS_CHAIN_COMMAND_NAME_REACT = 'reactCrossChain'; + +export const MAX_RESERVED_ERROR_STATUS = 63; +export const CCM_STATUS_OK = 0; diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/endpoint.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/endpoint.ts new file mode 100644 index 00000000000..160b0b59d92 --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/endpoint.ts @@ -0,0 +1,3 @@ +import { BaseEndpoint } from 'lisk-sdk'; + +export class ReactEndpoint extends BaseEndpoint {} diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/events/.gitkeep b/examples/interop/pos-sidechain-example-two/src/app/modules/react/events/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/method.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/method.ts new file mode 100644 index 00000000000..69f80f11e97 --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/method.ts @@ -0,0 +1,3 @@ +import { BaseMethod } from 'lisk-sdk'; + +export class ReactMethod extends BaseMethod {} diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/module.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/module.ts new file mode 100644 index 00000000000..5d5aa03d6bc --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/module.ts @@ -0,0 +1,85 @@ +/* eslint-disable class-methods-use-this */ +/* eslint-disable @typescript-eslint/member-ordering */ + +import { BaseInteroperableModule, ModuleMetadata, ModuleInitArgs } from 'lisk-sdk'; +import { ReactCrossChainCommand } from './commands/react_command'; +import { ReactEndpoint } from './endpoint'; +import { ReactMethod } from './method'; +import { ReactInteroperableMethod } from './cc_method'; +import { InteroperabilityMethod } from './types'; + +export class ReactModule extends BaseInteroperableModule { + public endpoint = new ReactEndpoint(this.stores, this.offchainStores); + public method = new ReactMethod(this.stores, this.events); + public commands = [new ReactCrossChainCommand(this.stores, this.events)]; + private _interoperabilityMethod!: InteroperabilityMethod; + + public crossChainMethod = new ReactInteroperableMethod(this.stores, this.events); + + /* public constructor() { + super(); + this.stores.register(ReactionStore, new ReactionStore(this.name, 0)); + } */ + + public metadata(): ModuleMetadata { + return { + ...this.baseMetadata(), + endpoints: [], + commands: this.commands.map(command => ({ + name: command.name, + params: command.schema, + })), + assets: [], + }; + } + + public addDependencies(interoperabilityMethod: InteroperabilityMethod) { + this._interoperabilityMethod = interoperabilityMethod; + } + + // Lifecycle hooks + // eslint-disable-next-line @typescript-eslint/require-await + public async init(_args: ModuleInitArgs) { + this.commands[0].init({ + interoperabilityMethod: this._interoperabilityMethod, + method: this.method, + moduleName: this.name, + }); + } + + // public async insertAssets(_context: InsertAssetContext) { + // // initialize block generation, add asset + // } + + // public async verifyAssets(_context: BlockVerifyContext): Promise { + // // verify block + // } + + // Lifecycle hooks + // public async verifyTransaction(_context: TransactionVerifyContext): Promise { + // verify transaction will be called multiple times in the transaction pool + // return { status: VerifyStatus.OK }; + // } + + // public async beforeCommandExecute(_context: TransactionExecuteContext): Promise { + // } + + // public async afterCommandExecute(_context: TransactionExecuteContext): Promise { + + // } + // public async initGenesisState(_context: GenesisBlockExecuteContext): Promise { + + // } + + // public async finalizeGenesisState(_context: GenesisBlockExecuteContext): Promise { + + // } + + // public async beforeTransactionsExecute(_context: BlockExecuteContext): Promise { + + // } + + // public async afterTransactionsExecute(_context: BlockAfterExecuteContext): Promise { + + // } +} diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts new file mode 100644 index 00000000000..67e7da674ef --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts @@ -0,0 +1,94 @@ +/** + * Parameters of the cross-chain token transfer command + */ +export const crossChainReactParamsSchema = { + /** The unique identifier of the schema. */ + $id: '/lisk/ccReactParams', + type: 'object', + /** The required parameters for the command. */ + required: [ + 'reactionType', + 'helloMessageID', + 'receivingChainID', + 'data', + 'messageFee', + 'messageFeeTokenID', + ], + /** A list describing the available parameters for the command. */ + properties: { + reactionType: { + dataType: 'uint32', + fieldNumber: 1, + }, + /** + * ID of the message. + */ + helloMessageID: { + dataType: 'string', + fieldNumber: 2, + }, + /** + * The chain ID of the receiving chain. + * + * `maxLength` and `minLength` are equal to 4. + */ + receivingChainID: { + dataType: 'bytes', + fieldNumber: 3, + minLength: 4, + maxLength: 4, + }, + /** Optional field for data / messages. */ + data: { + dataType: 'string', + fieldNumber: 4, + minLength: 0, + maxLength: 64, + }, + messageFee: { + dataType: 'uint64', + fieldNumber: 5, + }, + messageFeeTokenID: { + dataType: 'bytes', + fieldNumber: 6, + minLength: 8, + maxLength: 8, + }, + }, +}; + +export const crossChainReactMessageSchema = { + /** The unique identifier of the schema. */ + $id: '/lisk/ccReactMessage', + type: 'object', + /** The required parameters for the command. */ + required: ['reactionType', 'helloMessageID', 'data'], + /** A list describing the available parameters for the command. */ + properties: { + reactionType: { + dataType: 'uint32', + fieldNumber: 1, + }, + /** + * ID of the message. + */ + helloMessageID: { + dataType: 'string', + fieldNumber: 2, + }, + /** Optional field for data / messages. */ + data: { + dataType: 'string', + fieldNumber: 4, + minLength: 0, + maxLength: 64, + }, + }, +}; + +export interface CCReactMessageParams { + reactionType: number; + helloMessageID: string; + data: string; +} diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/stores/.gitkeep b/examples/interop/pos-sidechain-example-two/src/app/modules/react/stores/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/types.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/types.ts new file mode 100644 index 00000000000..279823de2fe --- /dev/null +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/types.ts @@ -0,0 +1,28 @@ +import { + MethodContext, + ImmutableMethodContext, + CCMsg, + ChannelData, + OwnChainAccount, +} from 'lisk-sdk'; + +export type TokenID = Buffer; + +export interface InteroperabilityMethod { + getOwnChainAccount(methodContext: ImmutableMethodContext): Promise; + send( + methodContext: MethodContext, + feeAddress: Buffer, + module: string, + crossChainCommand: string, + receivingChainID: Buffer, + fee: bigint, + parameters: Buffer, + timestamp?: number, + ): Promise; + error(methodContext: MethodContext, ccm: CCMsg, code: number): Promise; + terminateChain(methodContext: MethodContext, chainID: Buffer): Promise; + getChannel(methodContext: MethodContext, chainID: Buffer): Promise; + getMessageFeeTokenID(methodContext: ImmutableMethodContext, chainID: Buffer): Promise; + getMessageFeeTokenIDFromCCM(methodContext: ImmutableMethodContext, ccm: CCMsg): Promise; +} diff --git a/examples/interop/run_sidechains.json b/examples/interop/run_sidechains.json index b96ef8e9360..ea28f89c62e 100644 --- a/examples/interop/run_sidechains.json +++ b/examples/interop/run_sidechains.json @@ -3,12 +3,26 @@ { "name": "pos-sidechain-1", "script": "pos-sidechain-example-one/bin/run", - "args": ["start", "--api-ipc", "--api-http", "--enable-chain-connector-plugin"] + "args": [ + "start", + "--api-ipc", + "--api-http", + "--api-ws", + "--enable-dashboard-plugin", + "--enable-chain-connector-plugin" + ] }, { "name": "pos-sidechain-2", "script": "pos-sidechain-example-two/bin/run", - "args": ["start", "--api-ipc", "--api-http", "--enable-chain-connector-plugin"] + "args": [ + "start", + "--api-ipc", + "--api-http", + "--api-ws", + "--enable-dashboard-plugin", + "--enable-chain-connector-plugin" + ] } ] } From 9f23d9a0c2f25bfd1f4e592a7ceadc277e92c01c Mon Sep 17 00:00:00 2001 From: Ishan Date: Mon, 16 Oct 2023 17:48:57 +0200 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=90=9B=20Fix=20schema=20for=20react?= =?UTF-8?q?=20ccCommand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hello/cc_commands/react_command.ts | 28 ++++++++----- .../src/app/modules/hello/schema.ts | 40 ++----------------- .../src/app/modules/react/schemas.ts | 2 +- 3 files changed, 23 insertions(+), 47 deletions(-) diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts index 73358102213..009dd337e23 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts @@ -1,13 +1,9 @@ /* eslint-disable class-methods-use-this */ -import { BaseCCCommand, CrossChainMessageContext, codec } from 'lisk-sdk'; +import { BaseCCCommand, CrossChainMessageContext, codec, cryptography, db } from 'lisk-sdk'; import { crossChainReactParamsSchema, CCReactMessageParams } from '../schema'; -import { - MAX_RESERVED_ERROR_STATUS, - CCM_STATUS_OK, - CROSS_CHAIN_COMMAND_NAME_REACT, -} from '../constants'; -import { ReactionStore } from '../stores/reaction'; +import { MAX_RESERVED_ERROR_STATUS, CROSS_CHAIN_COMMAND_NAME_REACT } from '../constants'; +import { ReactionStore, ReactionStoreData } from '../stores/reaction'; export class ReactCCCommand extends BaseCCCommand { public schema = crossChainReactParamsSchema; @@ -22,8 +18,6 @@ export class ReactCCCommand extends BaseCCCommand { if (ccm.status > MAX_RESERVED_ERROR_STATUS) { throw new Error('Invalid CCM status code.'); - } else if (ccm.status === CCM_STATUS_OK) { - throw new Error('Bounced CCM.'); } } @@ -36,7 +30,21 @@ export class ReactCCCommand extends BaseCCCommand { const { helloMessageID, reactionType, senderAddress } = params; const reactionSubstore = this.stores.get(ReactionStore); - const msgReactions = await reactionSubstore.get(ctx, helloMessageID); + const messageCreatorAddress = cryptography.address.getAddressFromLisk32Address( + helloMessageID.toString('utf-8'), + ); + let msgReactions: ReactionStoreData; + try { + msgReactions = await reactionSubstore.get(ctx, messageCreatorAddress); + } catch (error) { + if (!(error instanceof db.NotFoundError)) { + throw error; + } + + ctx.logger.info({ helloMessageID, crossChainCommand: this.name }, error.message); + + return; + } if (reactionType === 0) { // TODO: Check if the Likes array already contains the sender address. If yes, remove the address to unlike the post. diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts index a59e5ec79d7..8c8a330a1c3 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts @@ -85,59 +85,27 @@ export const crossChainReactParamsSchema = { $id: '/lisk/ccReactParams', type: 'object', /** The required parameters for the command. */ - required: [ - 'reactionType', - 'helloMessageID', - 'receivingChainID', - 'data', - 'messageFee', - 'messageFeeTokenID', - ], + required: ['reactionType', 'helloMessageID', 'data'], /** A list describing the available parameters for the command. */ properties: { reactionType: { dataType: 'uint32', fieldNumber: 1, }, - data: { - dataType: 'string', - fieldNumber: 2, - }, /** * ID of the message. */ helloMessageID: { dataType: 'bytes', - fieldNumber: 3, - }, - /** - * The chain ID of the receiving chain. - * - * `maxLength` and `minLength` are equal to 4. - */ - receivingChainID: { - dataType: 'bytes', - fieldNumber: 4, - minLength: 4, - maxLength: 4, + fieldNumber: 2, }, /** Optional field for data / messages. */ - message: { + data: { dataType: 'string', - fieldNumber: 5, + fieldNumber: 3, minLength: 0, maxLength: 64, }, - messageFee: { - dataType: 'uint64', - fieldNumber: 6, - }, - messageFeeTokenID: { - dataType: 'bytes', - fieldNumber: 7, - minLength: 8, - maxLength: 8, - }, }, }; diff --git a/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts b/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts index 67e7da674ef..fc0033e9235 100644 --- a/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts +++ b/examples/interop/pos-sidechain-example-two/src/app/modules/react/schemas.ts @@ -80,7 +80,7 @@ export const crossChainReactMessageSchema = { /** Optional field for data / messages. */ data: { dataType: 'string', - fieldNumber: 4, + fieldNumber: 3, minLength: 0, maxLength: 64, }, From b4f0e3f8776da64239a62bfddbc075543bf61285 Mon Sep 17 00:00:00 2001 From: Ishan Date: Mon, 16 Oct 2023 17:49:42 +0200 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=94=A5=20Remove=20ccmID=20from=20Cc?= =?UTF-8?q?mSendSuccessEvent=20topics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../base_cross_chain_update_command.ts | 4 +--- .../base_interoperability_internal_methods.ts | 4 +--- .../interoperability/events/ccm_send_success.ts | 3 +-- .../mainchain/commands/register_sidechain.ts | 11 ++++------- .../sidechain/commands/register_mainchain.ts | 10 +++------- .../base_cross_chain_update_command.spec.ts | 1 - .../mainchain/commands/register_sidechain.spec.ts | 14 +++----------- .../unit/modules/interoperability/method.spec.ts | 4 +--- .../sidechain/commands/register_mainchain.spec.ts | 1 - 9 files changed, 14 insertions(+), 38 deletions(-) diff --git a/framework/src/modules/interoperability/base_cross_chain_update_command.ts b/framework/src/modules/interoperability/base_cross_chain_update_command.ts index 07e4724613d..218c209ea45 100644 --- a/framework/src/modules/interoperability/base_cross_chain_update_command.ts +++ b/framework/src/modules/interoperability/base_cross_chain_update_command.ts @@ -13,7 +13,6 @@ */ import { codec } from '@liskhq/lisk-codec'; -import { utils } from '@liskhq/lisk-cryptography'; import { validator } from '@liskhq/lisk-validator'; import { CommandExecuteContext, CommandVerifyContext } from '../../state_machine'; import { BaseInteroperabilityCommand } from './base_interoperability_command'; @@ -563,10 +562,9 @@ export abstract class BaseCrossChainUpdateCommand< } await this.internalMethod.addToOutbox(context, partnerChainID, bouncedCCM); - const newCcmID = utils.hash(codec.encode(ccmSchema, bouncedCCM)); this.events .get(CcmSendSuccessEvent) - .log(context, bouncedCCM.sendingChainID, bouncedCCM.receivingChainID, newCcmID, { + .log(context, bouncedCCM.sendingChainID, bouncedCCM.receivingChainID, { ccm: bouncedCCM, }); } diff --git a/framework/src/modules/interoperability/base_interoperability_internal_methods.ts b/framework/src/modules/interoperability/base_interoperability_internal_methods.ts index 76224c78dd2..c900d852b7e 100644 --- a/framework/src/modules/interoperability/base_interoperability_internal_methods.ts +++ b/framework/src/modules/interoperability/base_interoperability_internal_methods.ts @@ -32,7 +32,6 @@ import { ccmSchema } from './schemas'; import { CCMsg, CrossChainUpdateTransactionParams, ChainAccount, ChainValidators } from './types'; import { computeValidatorsHash, - getEncodedCCMAndID, getMainchainID, validateFormat, calculateNewActiveValidators, @@ -599,7 +598,6 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet throw new Error('Receiving chain is not active.'); } - const { ccmID } = getEncodedCCMAndID(ccm); await this.addToOutbox(context, partnerChainID, ccm); ownChainAccount.nonce += BigInt(1); await this.stores.get(OwnChainAccountStore).set(context, EMPTY_BYTES, ownChainAccount); @@ -607,7 +605,7 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet // Emit CCM Processed Event. this.events .get(CcmSendSuccessEvent) - .log(context, ccm.sendingChainID, ccm.receivingChainID, ccmID, { ccm }); + .log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm }); } public async getChainValidators( diff --git a/framework/src/modules/interoperability/events/ccm_send_success.ts b/framework/src/modules/interoperability/events/ccm_send_success.ts index e4a3bc348f5..97d4cdd0523 100644 --- a/framework/src/modules/interoperability/events/ccm_send_success.ts +++ b/framework/src/modules/interoperability/events/ccm_send_success.ts @@ -43,10 +43,9 @@ export class CcmSendSuccessEvent extends BaseEvent { ctx: EventQueuer, sendingChainID: Buffer, receivingChainID: Buffer, - sentCCMID: Buffer, data: CcmSendSuccessEventData, noRevert = false, ): void { - this.add(ctx, data, [sendingChainID, receivingChainID, sentCCMID], noRevert); + this.add(ctx, data, [sendingChainID, receivingChainID], noRevert); } } diff --git a/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts b/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts index f8b890a98b4..aef187de988 100644 --- a/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts +++ b/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts @@ -27,7 +27,7 @@ import { } from '../../constants'; import { registrationCCMParamsSchema, sidechainRegParams } from '../../schemas'; import { FeeMethod, SidechainRegistrationParams } from '../../types'; -import { computeValidatorsHash, getEncodedCCMAndID, getTokenIDLSK, isValidName } from '../../utils'; +import { computeValidatorsHash, getTokenIDLSK, isValidName } from '../../utils'; import { CommandVerifyContext, VerificationResult, @@ -256,11 +256,8 @@ export class RegisterSidechainCommand extends BaseInteroperabilityCommand { expect.anything(), context.ccm.receivingChainID, context.ccm.sendingChainID, - expect.any(Buffer), { ccm: { ...defaultCCM, diff --git a/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts b/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts index 79098e423e0..b16727ce3a3 100644 --- a/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts +++ b/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts @@ -47,7 +47,6 @@ import { computeValidatorsHash, getMainchainID, getTokenIDLSK, - getEncodedCCMAndID, } from '../../../../../../src/modules/interoperability/utils'; import { PrefixedStateReadWriter } from '../../../../../../src/state_machine/prefixed_state_read_writer'; import { InMemoryPrefixedStateDB } from '../../../../../../src/testing/in_memory_prefixed_state'; @@ -635,20 +634,13 @@ describe('RegisterSidechainCommand', () => { params: encodedParams, }; - const { ccmID } = getEncodedCCMAndID(ccm); // Act await sidechainRegistrationCommand.execute(context); // Assert - expect(ccmSendSuccessEvent.log).toHaveBeenCalledWith( - expect.anything(), - chainID, - newChainID, - ccmID, - { - ccm, - }, - ); + expect(ccmSendSuccessEvent.log).toHaveBeenCalledWith(expect.anything(), chainID, newChainID, { + ccm, + }); }); }); }); diff --git a/framework/test/unit/modules/interoperability/method.spec.ts b/framework/test/unit/modules/interoperability/method.spec.ts index 993fe117e96..13928773a84 100644 --- a/framework/test/unit/modules/interoperability/method.spec.ts +++ b/framework/test/unit/modules/interoperability/method.spec.ts @@ -40,7 +40,7 @@ import { createTransientMethodContext } from '../../../../src/testing'; import { ChannelDataStore } from '../../../../src/modules/interoperability/stores/channel_data'; import { TerminatedStateStore } from '../../../../src/modules/interoperability/stores/terminated_state'; import { TerminatedOutboxStore } from '../../../../src/modules/interoperability/stores/terminated_outbox'; -import { getMainchainID, getEncodedCCMAndID } from '../../../../src/modules/interoperability/utils'; +import { getMainchainID } from '../../../../src/modules/interoperability/utils'; class SampleInteroperabilityMethod extends BaseInteroperabilityMethod {} @@ -468,7 +468,6 @@ describe('Sample Method', () => { .mockResolvedValue(receivingChainAccount); interopMod.stores.get(OwnChainAccountStore).set = ownChainAccountStoreMock.set; - const { ccmID } = getEncodedCCMAndID(ccmOnMainchain); // Act & Assert await expect( @@ -493,7 +492,6 @@ describe('Sample Method', () => { expect.anything(), ccmOnMainchain.sendingChainID, ccmOnMainchain.receivingChainID, - ccmID, { ccm: ccmOnMainchain }, ); }); diff --git a/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts b/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts index 36d98665d9b..f1dd7c806e8 100644 --- a/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts +++ b/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts @@ -595,7 +595,6 @@ describe('RegisterMainchainCommand', () => { expect.anything(), ownChainAccount.chainID, mainchainID, - expect.anything(), { ccm, }, From daf79604db8bd1a875bf646f07897b522bc55892 Mon Sep 17 00:00:00 2001 From: Ishan Date: Thu, 19 Oct 2023 15:00:37 +0200 Subject: [PATCH 04/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix=20schema=20and?= =?UTF-8?q?=20ccCommand=20execute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hello/cc_commands/react_command.ts | 46 +++++++++++++++---- .../src/app/modules/hello/schema.ts | 4 +- .../src/app/modules/hello/stores/reaction.ts | 7 ++- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts index 009dd337e23..a88e61dfaa5 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts @@ -4,6 +4,7 @@ import { BaseCCCommand, CrossChainMessageContext, codec, cryptography, db } from import { crossChainReactParamsSchema, CCReactMessageParams } from '../schema'; import { MAX_RESERVED_ERROR_STATUS, CROSS_CHAIN_COMMAND_NAME_REACT } from '../constants'; import { ReactionStore, ReactionStoreData } from '../stores/reaction'; +import { MessageStore } from '../stores/message'; export class ReactCCCommand extends BaseCCCommand { public schema = crossChainReactParamsSchema; @@ -19,38 +20,67 @@ export class ReactCCCommand extends BaseCCCommand { if (ccm.status > MAX_RESERVED_ERROR_STATUS) { throw new Error('Invalid CCM status code.'); } + + const params = codec.decode(crossChainReactParamsSchema, ccm.params); + const messageCreatorAddress = cryptography.address.getAddressFromLisk32Address( + params.helloMessageID.toString('utf-8'), + ); + if (!(await this.stores.get(MessageStore).has(ctx, messageCreatorAddress))) { + throw new Error('Message ID does not exists.'); + } } public async execute(ctx: CrossChainMessageContext): Promise { - const { ccm } = ctx; + const { ccm, logger } = ctx; + logger.info('Executing React CCM'); // const methodContext = ctx.getMethodContext(); // const { sendingChainID, status, receivingChainID } = ccm; - const params = codec.decode(crossChainReactParamsSchema, ccm.params); - const { helloMessageID, reactionType, senderAddress } = params; + logger.info(params, 'parameters'); + const { helloMessageID, reactionType } = params; const reactionSubstore = this.stores.get(ReactionStore); + logger.info({ helloMessageID }, 'Contents of helloMessageID'); const messageCreatorAddress = cryptography.address.getAddressFromLisk32Address( helloMessageID.toString('utf-8'), ); + logger.info({ messageCreatorAddress }, 'Contents of messageCreatorAddress'); + let msgReactions: ReactionStoreData; + try { msgReactions = await reactionSubstore.get(ctx, messageCreatorAddress); } catch (error) { if (!(error instanceof db.NotFoundError)) { + logger.info({ helloMessageID, crossChainCommand: this.name }, (error as Error).message); + logger.error({ error }, 'Error when getting the reaction substore'); throw error; } - ctx.logger.info({ helloMessageID, crossChainCommand: this.name }, error.message); - - return; + logger.info( + { helloMessageID, crossChainCommand: this.name }, + `No entry exists for given helloMessageID ${helloMessageID.toString( + 'utf-8', + )}. Creating a default entry.`, + ); + msgReactions = { reactions: { like: [] } }; } + logger.info( + { msgReactions }, + '+++++++++++++++++++++++++++++=============++++++++++++++++++++++++', + ); + logger.info({ msgReactions }, 'Contents of the reaction store PRE'); + logger.info(msgReactions, 'Contents of the reaction store PRE'); if (reactionType === 0) { // TODO: Check if the Likes array already contains the sender address. If yes, remove the address to unlike the post. - msgReactions.reactions.like.push(senderAddress); + msgReactions.reactions.like.push(ctx.transaction.senderAddress); + } else { + logger.error({ reactionType }, 'invalid reaction type'); } - await reactionSubstore.set(ctx, helloMessageID, msgReactions); + logger.info(msgReactions, 'Contents of the reaction store POST'); + logger.info({ msgReactions }, 'Contents of the reaction store POST'); + await reactionSubstore.set(ctx, messageCreatorAddress, msgReactions); } } diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts index 8c8a330a1c3..fa0cf418865 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts @@ -112,7 +112,5 @@ export const crossChainReactParamsSchema = { export interface CCReactMessageParams { reactionType: number; helloMessageID: Buffer; - receivingChainID: Buffer; - senderAddress: Buffer; - message: string; + data: string; } diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts index 462f03ff27a..867c4a97e99 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/stores/reaction.ts @@ -7,7 +7,7 @@ export interface ReactionStoreData { } export const reactionStoreSchema = { - $id: '/hello/message', + $id: '/hello/reaction', type: 'object', required: ['reactions'], properties: { @@ -16,11 +16,10 @@ export const reactionStoreSchema = { fieldNumber: 1, properties: { like: { - dataType: 'array', + type: 'array', + fieldNumber: 1, items: { dataType: 'bytes', - format: 'lisk32', - fieldNumber: 1, }, }, }, From 9ebe1d56bae95c14d79c83e493e9dcfc36006b2e Mon Sep 17 00:00:00 2001 From: Tschakki Date: Thu, 19 Oct 2023 16:23:44 +0200 Subject: [PATCH 05/13] Generalize sidechain_registration.ts --- .../config/scripts/sidechain_registration.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts index 082deab672f..0f2cee0393b 100644 --- a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts +++ b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts @@ -1,30 +1,38 @@ -import { apiClient, codec, cryptography, Transaction } from 'lisk-sdk'; +import { apiClient, codec, sidechainRegParams, cryptography, Transaction } from 'lisk-sdk'; +// Replace this with the a path with a fie storing the public and private key of the mainchain account that will send the sidechain registration transaction import { keys } from '../default/dev-validators.json'; -import { sidechainRegParams } from 'lisk-framework'; (async () => { const { address } = cryptography; - const SIDECHAIN_ARRAY = ['one', 'two']; + // Replace this with alias of the sidechain node(s) + const SIDECHAIN_ARRAY = ['pos-sidechain-example-one', 'pos-sidechain-example-two']; + // Replace this with the alias of the mainchain node(s), e.g. lisk-core + // Note: Number of mainchain nodes should be equal to sidechain nodes, for this script to work properly. + const MAINCHAIN_ARRAY = ['mainchain-node-one', 'mainchain-node-two']; let i = 0; for (const nodeAlias of SIDECHAIN_ARRAY) { - const sidechainClient = await apiClient.createIPCClient( - `~/.lisk/pos-sidechain-example-${nodeAlias}`, - ); - const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-${nodeAlias}`); + // Connect to the sidechain node + const sidechainClient = await apiClient.createIPCClient(`~/.lisk/${nodeAlias}`); + // Connect to the mainchain node + const mainchainClient = await apiClient.createIPCClient(`~/.lisk/${MAINCHAIN_ARRAY[i]}`); + // Get node info data from sidechain and mainchain const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo'); const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo'); + // Get active validators from sidechainchain const { validators: sidehcainActiveValidators, certificateThreshold } = await sidechainClient.invoke('consensus_getBFTParameters', { height: sidechainNodeInfo.height, }); + // Sort validator list lexicographically after their BLS key (sidehcainActiveValidators as { blsKey: string; bftWeight: string }[]).sort((a, b) => Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex')), ); + // Define parameters for the sidechain registration const params = { sidechainCertificateThreshold: certificateThreshold, sidechainValidators: sidehcainActiveValidators, @@ -32,11 +40,13 @@ import { sidechainRegParams } from 'lisk-framework'; name: `sidechain_example_${nodeAlias}`, }; + // Get publickey and nonce of the sender account const relayerkeyInfo = keys[2]; const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', { address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerkeyInfo.publicKey, 'hex')), }); + // Create registerSidechain transaction const tx = new Transaction({ module: 'interoperability', command: 'registerSidechain', @@ -47,11 +57,13 @@ import { sidechainRegParams } from 'lisk-framework'; signatures: [], }); + // Sign the transaction tx.sign( Buffer.from(mainchainNodeInfo.chainID as string, 'hex'), Buffer.from(relayerkeyInfo.privateKey, 'hex'), ); + // Post the transaction to a mainchain node const result = await mainchainClient.invoke<{ transactionId: string; }>('txpool_postTransaction', { @@ -64,6 +76,7 @@ import { sidechainRegParams } from 'lisk-framework'; ); i += 1; + // Wait in case there are more elements in the SIDECHAIN_ARRAY, after performing another loop with the next element. const wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); if (i < SIDECHAIN_ARRAY.length) { const WAIT_PERIOD = 10000; From 7e79c89c157db99f444e8fee93537ee164dbd553 Mon Sep 17 00:00:00 2001 From: Tschakki Date: Thu, 19 Oct 2023 17:36:33 +0200 Subject: [PATCH 06/13] Generalize mainchain_registration.ts --- .../interop/common/mainchain_registration.ts | 63 ++++++++++++++----- .../config/scripts/sidechain_registration.ts | 4 +- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/examples/interop/common/mainchain_registration.ts b/examples/interop/common/mainchain_registration.ts index cdd2bb1ea2a..28804830bb0 100644 --- a/examples/interop/common/mainchain_registration.ts +++ b/examples/interop/common/mainchain_registration.ts @@ -1,22 +1,38 @@ -import { codec, cryptography, apiClient, Transaction } from 'lisk-sdk'; import { + codec, + cryptography, + apiClient, + Transaction, registrationSignatureMessageSchema, mainchainRegParams as mainchainRegParamsSchema, MESSAGE_TAG_CHAIN_REG, MODULE_NAME_INTEROPERABILITY, -} from 'lisk-framework'; -import { COMMAND_NAME_MAINCHAIN_REG } from 'lisk-framework/dist-node/modules/interoperability/constants'; - -export const registerMainchain = async ( - num: string, - sidechainDevValidators: any[], - sidechainValidatorsKeys: any[], -) => { +} from 'lisk-sdk'; + +/** + * Registers the mainchain to a specific sidechain node. + * + * @example + * ```js + * // Update path to point to the dev-validators.json file of the sidechain which shall be registered on the mainchain +import { keys as sidechainDevValidators } from '../default/dev-validators.json'; + + * (async () => { + * await registerMainchain("lisk-core","my-lisk-app",sidechainDevValidators); + *})(); + * ``` + * @param sidechainDevValidators the `key` property of the `dev-validators.json` file. + * Includes all keys of the sidechain validators to create the aggregated signature. + */ +export const registerMainchain = async (mc: string, sc: string, sidechainDevValidators: any[]) => { const { bls, address } = cryptography; - const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-${num}`); - const sidechainClient = await apiClient.createIPCClient(`~/.lisk/pos-sidechain-example-${num}`); + // Connect to the mainchain node + const mainchainClient = await apiClient.createIPCClient(`~/.lisk/${mc}`); + // Connect to the sidechain node + const sidechainClient = await apiClient.createIPCClient(`~/.lisk/${sc}`); + // Get node info data from sidechain and mainchain const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo'); const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo'); @@ -28,15 +44,17 @@ export const registerMainchain = async ( height: mainchainNodeInfo.height, }); + // Sort validator list lexicographically after their BLS key const paramsJSON = { ownChainID: sidechainNodeInfo.chainID, - ownName: `sidechain_example_${num}`, + ownName: sc, mainchainValidators: (mainchainActiveValidators as { blsKey: string; bftWeight: string }[]) .map(v => ({ blsKey: v.blsKey, bftWeight: v.bftWeight })) .sort((a, b) => Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex'))), mainchainCertificateThreshold, }; + // Define parameters for the mainchain registration const params = { ownChainID: Buffer.from(paramsJSON.ownChainID as string, 'hex'), ownName: paramsJSON.ownName, @@ -47,17 +65,19 @@ export const registerMainchain = async ( mainchainCertificateThreshold: paramsJSON.mainchainCertificateThreshold, }; + // Encode parameters const message = codec.encode(registrationSignatureMessageSchema, params); - // Get active validators from sidechainChain + // Get active validators from sidechain const { validators: sidechainActiveValidators } = await sidechainClient.invoke( 'consensus_getBFTParameters', { height: sidechainNodeInfo.height }, ); + // Add validator private keys to the sidechain validator list const activeValidatorsWithPrivateKey: { blsPublicKey: Buffer; blsPrivateKey: Buffer }[] = []; for (const v of sidechainActiveValidators as { blsKey: string; bftWeight: string }[]) { - const validatorInfo = sidechainValidatorsKeys.find( + const validatorInfo = sidechainDevValidators.find( configValidator => configValidator.plain.blsKey === v.blsKey, ); if (validatorInfo) { @@ -68,11 +88,13 @@ export const registerMainchain = async ( } } console.log('Total activeValidatorsWithPrivateKey:', activeValidatorsWithPrivateKey.length); - // Sort active validators from sidechainChain + + // Sort active validators from sidechain lexicographically after their BLS public key activeValidatorsWithPrivateKey.sort((a, b) => a.blsPublicKey.compare(b.blsPublicKey)); const sidechainValidatorsSignatures: { publicKey: Buffer; signature: Buffer }[] = []; - // Sign with each active validator + + // Sign parameters with each active sidechain validator for (const validator of activeValidatorsWithPrivateKey) { const signature = bls.signData( MESSAGE_TAG_CHAIN_REG, @@ -92,18 +114,23 @@ export const registerMainchain = async ( sidechainValidatorsSignatures, ); + // Get public key and nonce of the sender account const relayerKeyInfo = sidechainDevValidators[0]; const { nonce } = await sidechainClient.invoke<{ nonce: string }>('auth_getAuthAccount', { address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')), }); + + // Add aggregated signature to the parameters of the mainchain registration const mainchainRegParams = { ...paramsJSON, signature: signature.toString('hex'), aggregationBits: aggregationBits.toString('hex'), }; + + // Create registerMainchain transaction const tx = new Transaction({ module: MODULE_NAME_INTEROPERABILITY, - command: COMMAND_NAME_MAINCHAIN_REG, + command: 'registerMainchain', fee: BigInt(2000000000), params: codec.encodeJSON(mainchainRegParamsSchema, mainchainRegParams), nonce: BigInt(nonce), @@ -111,11 +138,13 @@ export const registerMainchain = async ( signatures: [], }); + // Sign the transaction tx.sign( Buffer.from(sidechainNodeInfo.chainID as string, 'hex'), Buffer.from(relayerKeyInfo.privateKey, 'hex'), ); + // Post the transaction to a sidechain node const result = await sidechainClient.invoke<{ transactionId: string; }>('txpool_postTransaction', { diff --git a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts index 0f2cee0393b..bc58c5c760e 100644 --- a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts +++ b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts @@ -21,7 +21,7 @@ import { keys } from '../default/dev-validators.json'; const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo'); const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo'); - // Get active validators from sidechainchain + // Get active validators from sidechain const { validators: sidehcainActiveValidators, certificateThreshold } = await sidechainClient.invoke('consensus_getBFTParameters', { height: sidechainNodeInfo.height, @@ -40,7 +40,7 @@ import { keys } from '../default/dev-validators.json'; name: `sidechain_example_${nodeAlias}`, }; - // Get publickey and nonce of the sender account + // Get public key and nonce of the sender account const relayerkeyInfo = keys[2]; const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', { address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerkeyInfo.publicKey, 'hex')), From ecb00696d1f1c6c93fb0d8a6b182601fecb66f7d Mon Sep 17 00:00:00 2001 From: Tschakki Date: Thu, 19 Oct 2023 17:38:49 +0200 Subject: [PATCH 07/13] =?UTF-8?q?Revert=20"=F0=9F=94=A5=20Remove=20ccmID?= =?UTF-8?q?=20from=20CcmSendSuccessEvent=20topics"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit b4f0e3f8776da64239a62bfddbc075543bf61285. --- .../base_cross_chain_update_command.ts | 4 +++- .../base_interoperability_internal_methods.ts | 4 +++- .../interoperability/events/ccm_send_success.ts | 3 ++- .../mainchain/commands/register_sidechain.ts | 11 +++++++---- .../sidechain/commands/register_mainchain.ts | 10 +++++++--- .../base_cross_chain_update_command.spec.ts | 1 + .../mainchain/commands/register_sidechain.spec.ts | 14 +++++++++++--- .../unit/modules/interoperability/method.spec.ts | 4 +++- .../sidechain/commands/register_mainchain.spec.ts | 1 + 9 files changed, 38 insertions(+), 14 deletions(-) diff --git a/framework/src/modules/interoperability/base_cross_chain_update_command.ts b/framework/src/modules/interoperability/base_cross_chain_update_command.ts index 218c209ea45..07e4724613d 100644 --- a/framework/src/modules/interoperability/base_cross_chain_update_command.ts +++ b/framework/src/modules/interoperability/base_cross_chain_update_command.ts @@ -13,6 +13,7 @@ */ import { codec } from '@liskhq/lisk-codec'; +import { utils } from '@liskhq/lisk-cryptography'; import { validator } from '@liskhq/lisk-validator'; import { CommandExecuteContext, CommandVerifyContext } from '../../state_machine'; import { BaseInteroperabilityCommand } from './base_interoperability_command'; @@ -562,9 +563,10 @@ export abstract class BaseCrossChainUpdateCommand< } await this.internalMethod.addToOutbox(context, partnerChainID, bouncedCCM); + const newCcmID = utils.hash(codec.encode(ccmSchema, bouncedCCM)); this.events .get(CcmSendSuccessEvent) - .log(context, bouncedCCM.sendingChainID, bouncedCCM.receivingChainID, { + .log(context, bouncedCCM.sendingChainID, bouncedCCM.receivingChainID, newCcmID, { ccm: bouncedCCM, }); } diff --git a/framework/src/modules/interoperability/base_interoperability_internal_methods.ts b/framework/src/modules/interoperability/base_interoperability_internal_methods.ts index c900d852b7e..76224c78dd2 100644 --- a/framework/src/modules/interoperability/base_interoperability_internal_methods.ts +++ b/framework/src/modules/interoperability/base_interoperability_internal_methods.ts @@ -32,6 +32,7 @@ import { ccmSchema } from './schemas'; import { CCMsg, CrossChainUpdateTransactionParams, ChainAccount, ChainValidators } from './types'; import { computeValidatorsHash, + getEncodedCCMAndID, getMainchainID, validateFormat, calculateNewActiveValidators, @@ -598,6 +599,7 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet throw new Error('Receiving chain is not active.'); } + const { ccmID } = getEncodedCCMAndID(ccm); await this.addToOutbox(context, partnerChainID, ccm); ownChainAccount.nonce += BigInt(1); await this.stores.get(OwnChainAccountStore).set(context, EMPTY_BYTES, ownChainAccount); @@ -605,7 +607,7 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet // Emit CCM Processed Event. this.events .get(CcmSendSuccessEvent) - .log(context, ccm.sendingChainID, ccm.receivingChainID, { ccm }); + .log(context, ccm.sendingChainID, ccm.receivingChainID, ccmID, { ccm }); } public async getChainValidators( diff --git a/framework/src/modules/interoperability/events/ccm_send_success.ts b/framework/src/modules/interoperability/events/ccm_send_success.ts index 97d4cdd0523..e4a3bc348f5 100644 --- a/framework/src/modules/interoperability/events/ccm_send_success.ts +++ b/framework/src/modules/interoperability/events/ccm_send_success.ts @@ -43,9 +43,10 @@ export class CcmSendSuccessEvent extends BaseEvent { ctx: EventQueuer, sendingChainID: Buffer, receivingChainID: Buffer, + sentCCMID: Buffer, data: CcmSendSuccessEventData, noRevert = false, ): void { - this.add(ctx, data, [sendingChainID, receivingChainID], noRevert); + this.add(ctx, data, [sendingChainID, receivingChainID, sentCCMID], noRevert); } } diff --git a/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts b/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts index aef187de988..f8b890a98b4 100644 --- a/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts +++ b/framework/src/modules/interoperability/mainchain/commands/register_sidechain.ts @@ -27,7 +27,7 @@ import { } from '../../constants'; import { registrationCCMParamsSchema, sidechainRegParams } from '../../schemas'; import { FeeMethod, SidechainRegistrationParams } from '../../types'; -import { computeValidatorsHash, getTokenIDLSK, isValidName } from '../../utils'; +import { computeValidatorsHash, getEncodedCCMAndID, getTokenIDLSK, isValidName } from '../../utils'; import { CommandVerifyContext, VerificationResult, @@ -256,8 +256,11 @@ export class RegisterSidechainCommand extends BaseInteroperabilityCommand { expect.anything(), context.ccm.receivingChainID, context.ccm.sendingChainID, + expect.any(Buffer), { ccm: { ...defaultCCM, diff --git a/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts b/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts index b16727ce3a3..79098e423e0 100644 --- a/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts +++ b/framework/test/unit/modules/interoperability/mainchain/commands/register_sidechain.spec.ts @@ -47,6 +47,7 @@ import { computeValidatorsHash, getMainchainID, getTokenIDLSK, + getEncodedCCMAndID, } from '../../../../../../src/modules/interoperability/utils'; import { PrefixedStateReadWriter } from '../../../../../../src/state_machine/prefixed_state_read_writer'; import { InMemoryPrefixedStateDB } from '../../../../../../src/testing/in_memory_prefixed_state'; @@ -634,13 +635,20 @@ describe('RegisterSidechainCommand', () => { params: encodedParams, }; + const { ccmID } = getEncodedCCMAndID(ccm); // Act await sidechainRegistrationCommand.execute(context); // Assert - expect(ccmSendSuccessEvent.log).toHaveBeenCalledWith(expect.anything(), chainID, newChainID, { - ccm, - }); + expect(ccmSendSuccessEvent.log).toHaveBeenCalledWith( + expect.anything(), + chainID, + newChainID, + ccmID, + { + ccm, + }, + ); }); }); }); diff --git a/framework/test/unit/modules/interoperability/method.spec.ts b/framework/test/unit/modules/interoperability/method.spec.ts index 13928773a84..993fe117e96 100644 --- a/framework/test/unit/modules/interoperability/method.spec.ts +++ b/framework/test/unit/modules/interoperability/method.spec.ts @@ -40,7 +40,7 @@ import { createTransientMethodContext } from '../../../../src/testing'; import { ChannelDataStore } from '../../../../src/modules/interoperability/stores/channel_data'; import { TerminatedStateStore } from '../../../../src/modules/interoperability/stores/terminated_state'; import { TerminatedOutboxStore } from '../../../../src/modules/interoperability/stores/terminated_outbox'; -import { getMainchainID } from '../../../../src/modules/interoperability/utils'; +import { getMainchainID, getEncodedCCMAndID } from '../../../../src/modules/interoperability/utils'; class SampleInteroperabilityMethod extends BaseInteroperabilityMethod {} @@ -468,6 +468,7 @@ describe('Sample Method', () => { .mockResolvedValue(receivingChainAccount); interopMod.stores.get(OwnChainAccountStore).set = ownChainAccountStoreMock.set; + const { ccmID } = getEncodedCCMAndID(ccmOnMainchain); // Act & Assert await expect( @@ -492,6 +493,7 @@ describe('Sample Method', () => { expect.anything(), ccmOnMainchain.sendingChainID, ccmOnMainchain.receivingChainID, + ccmID, { ccm: ccmOnMainchain }, ); }); diff --git a/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts b/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts index f1dd7c806e8..36d98665d9b 100644 --- a/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts +++ b/framework/test/unit/modules/interoperability/sidechain/commands/register_mainchain.spec.ts @@ -595,6 +595,7 @@ describe('RegisterMainchainCommand', () => { expect.anything(), ownChainAccount.chainID, mainchainID, + expect.anything(), { ccm, }, From dfd27c4851a88c68f0fd293c52f3cc062f78a171 Mon Sep 17 00:00:00 2001 From: Tschakki Date: Thu, 19 Oct 2023 17:53:09 +0200 Subject: [PATCH 08/13] transfer_lsk_sidechain_one.ts --- .../interop/common/mainchain_registration.ts | 4 ++++ .../config/scripts/sidechain_registration.ts | 3 ++- .../scripts/transfer_lsk_sidechain_one.ts | 17 ++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/interop/common/mainchain_registration.ts b/examples/interop/common/mainchain_registration.ts index 28804830bb0..a3edbdcc85d 100644 --- a/examples/interop/common/mainchain_registration.ts +++ b/examples/interop/common/mainchain_registration.ts @@ -21,9 +21,13 @@ import { keys as sidechainDevValidators } from '../default/dev-validators.json'; * await registerMainchain("lisk-core","my-lisk-app",sidechainDevValidators); *})(); * ``` + * + * @param mc mainchain alias of the mainchain to be registered. + * @param sc sidechain alias of the sidechain, where the mainchain shall be registered. * @param sidechainDevValidators the `key` property of the `dev-validators.json` file. * Includes all keys of the sidechain validators to create the aggregated signature. */ + export const registerMainchain = async (mc: string, sc: string, sidechainDevValidators: any[]) => { const { bls, address } = cryptography; diff --git a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts index bc58c5c760e..27dbc3e4f90 100644 --- a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts +++ b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts @@ -1,5 +1,6 @@ import { apiClient, codec, sidechainRegParams, cryptography, Transaction } from 'lisk-sdk'; -// Replace this with the a path with a fie storing the public and private key of the mainchain account that will send the sidechain registration transaction +// Replace this with the path to a file storing the public and private key of a mainchain account who will send the sidechain registration transaction. +// (Can be any account with enough tokens). import { keys } from '../default/dev-validators.json'; (async () => { diff --git a/examples/interop/pos-mainchain-fast/config/scripts/transfer_lsk_sidechain_one.ts b/examples/interop/pos-mainchain-fast/config/scripts/transfer_lsk_sidechain_one.ts index 8be39bb443c..65ba8d01be1 100644 --- a/examples/interop/pos-mainchain-fast/config/scripts/transfer_lsk_sidechain_one.ts +++ b/examples/interop/pos-mainchain-fast/config/scripts/transfer_lsk_sidechain_one.ts @@ -1,4 +1,6 @@ import { apiClient, codec, cryptography, Schema, Transaction } from 'lisk-sdk'; +// Replace this with the path to a file storing the public and private key of a mainchain account who will send the sidechain registration transaction. +// (Can be any account with enough tokens). import { keys } from '../default/dev-validators.json'; type ModulesMetadata = [ { @@ -12,25 +14,30 @@ type ModulesMetadata = [ const { address } = cryptography; const nodeAlias = 'one'; + // Update this with the Token ID of the token you wish to transfer const tokenID = Buffer.from('0400000000000000', 'hex'); - const sidechainID = Buffer.from('04000001', 'hex'); // Update this to send to another sidechain + // Update this with the chain ID of the receiving chain + const sidechainID = Buffer.from('04000001', 'hex'); + // Update this with the recipient address const recipientLSKAddress = 'lskxz85sur2yo22dmcxybe39uvh2fg7s2ezxq4ny9'; const recipientAddress = address.getAddressFromLisk32Address(recipientLSKAddress); + // Connect to the mainchain node const mainchainClient = await apiClient.createIPCClient(`~/.lisk/mainchain-node-one`); + // Get node info data from mainchain const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo'); + // Get schema for the transferCrossChain command const { modules: modulesMetadata } = await mainchainClient.invoke<{ modules: ModulesMetadata; }>('system_getMetadata'); - const tokenMetadata = modulesMetadata.find(m => m.name === 'token'); - const ccTransferCMDSchema = tokenMetadata?.commands.filter( cmd => cmd.name == 'transferCrossChain', )[0].params as Schema; + // Define parameters for the cc transfer const params = { tokenID, amount: BigInt('10000000000'), @@ -41,11 +48,13 @@ type ModulesMetadata = [ messageFeeTokenID: tokenID, }; + // Get public key and nonce of the sender account const relayerkeyInfo = keys[2]; const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', { address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerkeyInfo.publicKey, 'hex')), }); + // Create transferCrossChain transaction const tx = new Transaction({ module: 'token', command: 'transferCrossChain', @@ -56,11 +65,13 @@ type ModulesMetadata = [ signatures: [], }); + // Sign the transaction tx.sign( Buffer.from(mainchainNodeInfo.chainID as string, 'hex'), Buffer.from(relayerkeyInfo.privateKey, 'hex'), ); + // Post the transaction to a mainchain node const result = await mainchainClient.invoke<{ transactionId: string; }>('txpool_postTransaction', { From 2144b82b440c3b3eda3cf1bb265656850dffca3e Mon Sep 17 00:00:00 2001 From: Tschakki Date: Fri, 20 Oct 2023 13:11:46 +0200 Subject: [PATCH 09/13] Update mainchain_registration.ts for sc1&2 --- .../commands/transaction/sign.ts | 4 - elements/lisk-cryptography/src/utils.ts | 8 +- elements/lisk-cryptography/test/ed.spec.ts | 14 +-- .../config/default/genesis_assets.json | 82 +++++++++++-- .../config/default/genesis_block.blob | Bin 7293 -> 7874 bytes .../config/scripts/mainchain_registration.ts | 7 +- .../config/scripts/mainchain_registration.ts | 7 +- .../base_interoperability_module.ts | 37 ++---- .../interoperability/mainchain/module.ts | 112 +++++++++--------- .../interoperability/sidechain/module.ts | 6 +- .../base_interoperability_module.spec.ts | 2 +- .../interoperability/mainchain/module.spec.ts | 54 +++++++-- 12 files changed, 209 insertions(+), 124 deletions(-) diff --git a/commander/src/bootstrapping/commands/transaction/sign.ts b/commander/src/bootstrapping/commands/transaction/sign.ts index 61fa3557e73..0387c6b0b17 100644 --- a/commander/src/bootstrapping/commands/transaction/sign.ts +++ b/commander/src/bootstrapping/commands/transaction/sign.ts @@ -37,7 +37,6 @@ import { getParamsSchema, } from '../../../utils/transaction'; import { getDefaultPath } from '../../../utils/path'; -import { isApplicationRunning } from '../../../utils/application'; import { PromiseResolvedType } from '../../../types'; import { DEFAULT_KEY_DERIVATION_PATH } from '../../../utils/config'; import { deriveKeypair } from '../../../utils/commons'; @@ -256,9 +255,6 @@ export abstract class SignCommand extends Command { async finally(error?: Error | string): Promise { if (error) { - if (this._dataPath && !isApplicationRunning(this._dataPath)) { - throw new Error(`Application at data path ${this._dataPath} is not running.`); - } this.error(error instanceof Error ? error.message : error); } if (this._client) { diff --git a/elements/lisk-cryptography/src/utils.ts b/elements/lisk-cryptography/src/utils.ts index f90ec861494..086357f8cf3 100644 --- a/elements/lisk-cryptography/src/utils.ts +++ b/elements/lisk-cryptography/src/utils.ts @@ -48,7 +48,7 @@ export const hash = (data: Buffer | string, format?: string): Buffer => { export const parseKeyDerivationPath = (path: string) => { if (!path.startsWith('m') || !path.includes('/')) { - throw new Error('Invalid path format'); + throw new Error('Invalid key derivation path format'); } return ( @@ -58,19 +58,19 @@ export const parseKeyDerivationPath = (path: string) => { .slice(1) .map(segment => { if (!/^[0-9']+$/g.test(segment)) { - throw new Error('Invalid path format'); + throw new Error('Invalid key derivation path format'); } // if segment includes apostrophe add HARDENED_OFFSET if (segment.includes(`'`)) { if (parseInt(segment.slice(0, -1), 10) > MAX_UINT32 / 2) { - throw new Error('Invalid path format'); + throw new Error('Invalid key derivation path format'); } return parseInt(segment, 10) + HARDENED_OFFSET; } if (parseInt(segment, 10) > MAX_UINT32) { - throw new Error('Invalid path format'); + throw new Error('Invalid key derivation path format'); } return parseInt(segment, 10); diff --git a/elements/lisk-cryptography/test/ed.spec.ts b/elements/lisk-cryptography/test/ed.spec.ts index e174e35c718..3155064fc88 100644 --- a/elements/lisk-cryptography/test/ed.spec.ts +++ b/elements/lisk-cryptography/test/ed.spec.ts @@ -168,19 +168,19 @@ describe('getPrivateKeyFromPhraseAndPath', () => { it('should fail for empty string path', async () => { await expect(getPrivateKeyFromPhraseAndPath(passphrase, '')).rejects.toThrow( - 'Invalid path format', + 'Invalid key derivation path format', ); }); it('should fail if path does not start with "m"', async () => { await expect(getPrivateKeyFromPhraseAndPath(passphrase, `/44'/134'/0'`)).rejects.toThrow( - 'Invalid path format', + 'Invalid key derivation path format', ); }); it('should fail if path does not include at least one "/"', async () => { await expect(getPrivateKeyFromPhraseAndPath(passphrase, 'm441340')).rejects.toThrow( - 'Invalid path format', + 'Invalid key derivation path format', ); }); @@ -190,24 +190,24 @@ describe('getPrivateKeyFromPhraseAndPath', () => { passphrase, `m//134'/0'`, // should be number with or without ' between every back slash ), - ).rejects.toThrow('Invalid path format'); + ).rejects.toThrow('Invalid key derivation path format'); }); it('should fail for path with invalid characters', async () => { await expect(getPrivateKeyFromPhraseAndPath(passphrase, `m/a'/134b'/0'`)).rejects.toThrow( - 'Invalid path format', + 'Invalid key derivation path format', ); }); it('should fail for path with non-sanctioned special characters', async () => { await expect(getPrivateKeyFromPhraseAndPath(passphrase, `m/4a'/#134b'/0'`)).rejects.toThrow( - 'Invalid path format', + 'Invalid key derivation path format', ); }); it(`should fail for path with segment greater than ${MAX_UINT32} / 2`, async () => { await expect( getPrivateKeyFromPhraseAndPath(passphrase, `m/44'/134'/${MAX_UINT32}'`), - ).rejects.toThrow('Invalid path format'); + ).rejects.toThrow('Invalid key derivation path format'); }); }); diff --git a/examples/interop/pos-mainchain-fast/config/default/genesis_assets.json b/examples/interop/pos-mainchain-fast/config/default/genesis_assets.json index 343f2580f36..54ed68a6a19 100644 --- a/examples/interop/pos-mainchain-fast/config/default/genesis_assets.json +++ b/examples/interop/pos-mainchain-fast/config/default/genesis_assets.json @@ -4,10 +4,72 @@ "module": "interoperability", "data": { "ownChainName": "lisk_mainchain", - "ownChainNonce": 0, - "chainInfos": [], - "terminatedStateAccounts": [], - "terminatedOutboxAccounts": [] + "ownChainNonce": "123", + "chainInfos": [ + { + "chainID": "04123456", + "chainData": { + "name": "dummy", + "lastCertificate": { + "height": 567467, + "timestamp": 1000, + "stateRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "validatorsHash": "a5a053d50182ea0c33bc03594cf4760f11d67cdba407d16bc0512fabb468253a" + }, + "status": 2 + }, + "channelData": { + "inbox": { + "appendPath": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "size": 18, + "root": "9e37ffe87f08f6b7952d82acdb3376046792533ebf2ba8908dda3ebc813dac9a" + }, + "outbox": { + "appendPath": [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "size": 18, + "root": "221a0c844883022e3a54e78eb5dcdc2ed8faa85f648c40659cf5fd2054f36500" + }, + "partnerChainOutboxRoot": "851faa36d87411d625fb4416c33c6a44795cb84155637f9991fcef06d1de7155", + "messageFeeTokenID": "0400000000000000", + "minReturnFeePerByte": "1000" + }, + "chainValidators": { + "activeValidators": [ + { + "blsKey": "3c1e6f29e3434f816cd6697e56cc54bc8d80927bf65a1361b383aa338cd3f63cbf82ce801b752cb32f8ecb3f8cc16835", + "bftWeight": "10" + } + ], + "certificateThreshold": "10" + } + } + ], + "terminatedStateAccounts": [ + { + "chainID": "04123456", + "terminatedStateAccount": { + "stateRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "mainchainStateRoot": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "initialized": true + } + } + ], + "terminatedOutboxAccounts": [ + { + "chainID": "04123456", + "terminatedOutboxAccount": { + "outboxRoot": "0aed892d544980f5b806dbce4bcb65517acf57a7af012c55c0b2f80b188fa290", + "outboxSize": 1, + "partnerChainInboxSize": 1 + } + } + ] }, "schema": { "$id": "/interoperability/module/genesis", @@ -916,11 +978,17 @@ "totalSupply": "10300000000000000" } ], - "escrowSubstore": [], + "escrowSubstore": [ + { + "escrowChainID": "04123456", + "tokenID": "0400000000000000", + "amount": "0" + } + ], "supportedTokensSubstore": [ { - "chainID": "", - "supportedTokenIDs": [] + "chainID": "04123456", + "supportedTokenIDs": ["0412345600000000"] } ] }, diff --git a/examples/interop/pos-mainchain-fast/config/default/genesis_block.blob b/examples/interop/pos-mainchain-fast/config/default/genesis_block.blob index 6d83ad495db344ab90cc09744a31df45abd374bb..c4b5dfdc624d3bef880bc6f301b008221be29d12 100644 GIT binary patch delta 549 zcmexsambdR>klIbgTRZa`&P0^FeoWZ6qK8&YooAXT6W!!ghdwHp52@t@gwwANCZ=D zso173H3xztA9oo~nAoYJ@J4f|_3`2(_9ct270t7m?CFusHF?(Iwoei@&+cfOH@8eY zYs_?lWilh9u;FGFF20=1;_Ud`#LT?p3?Ly;Ej5psi-kqVBuprbi#4S*H@8yAn`8Aw zC4m?0QXpsOG60>kbV2Y{#->+1#(S6}eZG|O3tp?ay@dT@_JKhC)mt)Dtt6PFnwhvV zxIh|M7#P5`76XHTkd(qa^ZzgEIlgV5s@t^Yws9Ft`lMjH{n{%g^xm@D(`dV9mJq70 zdWbG1DV`ROW+pwWkmr3{@7&S5@oPnVN{>V8oUeZsLO!Q5NGY_+uQIz)B6v;pw~N?e zn=F^gm>rIx$@McQ{&~-K@m^u55(m&BV4(4WUCDyWgv-E2E?@JpvwvgGwamJ(Ga-9= z8zxnMixN)U+`P)T=khn3{Y~c@WTZ=VHtYADw(mKZVJg5Sz@_8|4IVeJub3GageJNv z8c8r}=|LsbxD>eFcIt+BHhkT|cKe+7>D0ig^Wn?aGwOsM*z|*2qJPl@0Y(YN&FYNa pTs*6lxL8Z_vs3eg7AkG7T_3jhN0yWIc) delta 166 zcmV;X09pUSJ^eTi3j6^G01(@q$fpJv03wr+0U?nuIv`{M+wZaKNn<*m)PojJjnVz4 zKhdT*dvZ{J>;Q4h9c@h!|4s2<2YhP_)X>Ma^VQFp<0JBO1O9~8$A_@g` UZ);_468|Byp$+>G1R??o06h0UumAu6 diff --git a/examples/interop/pos-sidechain-example-one/config/scripts/mainchain_registration.ts b/examples/interop/pos-sidechain-example-one/config/scripts/mainchain_registration.ts index f41598533a4..ba3439b6ed5 100644 --- a/examples/interop/pos-sidechain-example-one/config/scripts/mainchain_registration.ts +++ b/examples/interop/pos-sidechain-example-one/config/scripts/mainchain_registration.ts @@ -1,7 +1,10 @@ -import { keys as sidechainValidatorsKeys } from '../../config/default/dev-validators.json'; import { keys as sidechainDevValidators } from '../default/dev-validators.json'; import { registerMainchain } from '../../../common/mainchain_registration'; (async () => { - await registerMainchain('one', sidechainDevValidators, sidechainValidatorsKeys); + await registerMainchain( + 'mainchain-node-one', + 'pos-sidechain-example-one', + sidechainDevValidators, + ); })(); diff --git a/examples/interop/pos-sidechain-example-two/config/scripts/mainchain_registration.ts b/examples/interop/pos-sidechain-example-two/config/scripts/mainchain_registration.ts index 54a30dc39cc..569177d7285 100644 --- a/examples/interop/pos-sidechain-example-two/config/scripts/mainchain_registration.ts +++ b/examples/interop/pos-sidechain-example-two/config/scripts/mainchain_registration.ts @@ -1,7 +1,10 @@ -import { keys as sidechainValidatorsKeys } from '../default/dev-validators.json'; import { keys as sidechainDevValidators } from '../default/dev-validators.json'; import { registerMainchain } from '../../../common/mainchain_registration'; (async () => { - await registerMainchain('two', sidechainDevValidators, sidechainValidatorsKeys); + await registerMainchain( + 'mainchain-node-two', + 'pos-sidechain-example-two', + sidechainDevValidators, + ); })(); diff --git a/framework/src/modules/interoperability/base_interoperability_module.ts b/framework/src/modules/interoperability/base_interoperability_module.ts index 01f784c9740..1986f2f3226 100644 --- a/framework/src/modules/interoperability/base_interoperability_module.ts +++ b/framework/src/modules/interoperability/base_interoperability_module.ts @@ -35,12 +35,7 @@ import { OwnChainAccountStore } from './stores/own_chain_account'; import { RegisteredNamesStore } from './stores/registered_names'; import { TerminatedOutboxStore } from './stores/terminated_outbox'; import { TerminatedStateStore } from './stores/terminated_state'; -import { - ChainInfo, - GenesisInteroperability, - OwnChainAccount, - TerminatedStateAccountWithChainID, -} from './types'; +import { ChainInfo, GenesisInteroperability, OwnChainAccount } from './types'; import { computeValidatorsHash, getTokenIDLSK } from './utils'; import { genesisInteroperabilitySchema } from './schemas'; import { CcmProcessedEvent } from './events/ccm_processed'; @@ -133,21 +128,18 @@ export abstract class BaseInteroperabilityModule extends BaseInteroperableModule } // activeValidators must be ordered lexicographically by blsKey property - const sortedByBlsKeys = [...activeValidators].sort((a, b) => a.blsKey.compare(b.blsKey)); - for (let i = 0; i < activeValidators.length; i += 1) { - if (!activeValidators[i].blsKey.equals(sortedByBlsKeys[i].blsKey)) { - throw new Error('activeValidators must be ordered lexicographically by blsKey property.'); - } + const blsKeys = activeValidators.map(v => v.blsKey); + if (!objectUtils.isBufferArrayOrdered(blsKeys)) { + throw new Error('activeValidators must be ordered lexicographically by blsKey property.'); } // all blsKey properties must be pairwise distinct - const blsKeys = activeValidators.map(v => v.blsKey); if (!objectUtils.bufferArrayUniqueItems(blsKeys)) { throw new Error(`All blsKey properties must be pairwise distinct.`); } // for each validator in activeValidators, validator.bftWeight > 0 must hold - if (activeValidators.filter(v => v.bftWeight <= 0).length > 0) { + if (activeValidators.filter(v => v.bftWeight <= BigInt(0)).length > 0) { throw new Error(`validator.bftWeight must be > 0.`); } @@ -189,28 +181,15 @@ export abstract class BaseInteroperabilityModule extends BaseInteroperableModule } } - protected _verifyTerminatedStateAccountsCommon( - terminatedStateAccounts: TerminatedStateAccountWithChainID[], - mainchainID: Buffer, - ) { + protected _verifyTerminatedStateAccountsIDs(chainIDs: Buffer[]) { // Each entry stateAccount in terminatedStateAccounts has a unique stateAccount.chainID - const chainIDs = terminatedStateAccounts.map(a => a.chainID); if (!objectUtils.bufferArrayUniqueItems(chainIDs)) { throw new Error(`terminatedStateAccounts don't hold unique chainID.`); } // terminatedStateAccounts is ordered lexicographically by stateAccount.chainID - const sortedByChainID = [...terminatedStateAccounts].sort((a, b) => - a.chainID.compare(b.chainID), - ); - - for (let i = 0; i < terminatedStateAccounts.length; i += 1) { - const stateAccountWithChainID = terminatedStateAccounts[i]; - if (!stateAccountWithChainID.chainID.equals(sortedByChainID[i].chainID)) { - throw new Error('terminatedStateAccounts must be ordered lexicographically by chainID.'); - } - - this._verifyChainID(stateAccountWithChainID.chainID, mainchainID, 'stateAccount.'); + if (!objectUtils.isBufferArrayOrdered(chainIDs)) { + throw new Error('terminatedStateAccounts must be ordered lexicographically by chainID.'); } } diff --git a/framework/src/modules/interoperability/mainchain/module.ts b/framework/src/modules/interoperability/mainchain/module.ts index 8dbf6e9f613..9620ed015fd 100644 --- a/framework/src/modules/interoperability/mainchain/module.ts +++ b/framework/src/modules/interoperability/mainchain/module.ts @@ -265,11 +265,11 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule // If chainInfos is non-empty, ownChainNonce > 0 if (chainInfos.length === 0 && ownChainNonce !== BigInt(0)) { throw new Error(`ownChainNonce must be 0 if chainInfos is empty.`); - } else if (chainInfos.length !== 0 && ownChainNonce <= 0) { + } else if (chainInfos.length !== 0 && ownChainNonce <= BigInt(0)) { throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`); } - this._verifyChainInfos(ctx, chainInfos); + this._verifyChainInfos(ctx, chainInfos, terminatedStateAccounts); this._verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts, mainchainID); this._verifyTerminatedOutboxAccounts( chainInfos, @@ -281,7 +281,11 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule } // https://github.com/LiskHQ/lips/blob/main/proposals/lip-0045.md#mainchain - private _verifyChainInfos(ctx: GenesisBlockExecuteContext, chainInfos: ChainInfo[]) { + private _verifyChainInfos( + ctx: GenesisBlockExecuteContext, + chainInfos: ChainInfo[], + terminatedStateAccounts: TerminatedStateAccountWithChainID[], + ) { // Each entry chainInfo in chainInfos has a unique chainInfo.chainID const chainIDs = chainInfos.map(info => info.chainID); if (!objectUtils.bufferArrayUniqueItems(chainIDs)) { @@ -289,11 +293,8 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule } // chainInfos should be ordered lexicographically by chainInfo.chainID - const sortedByChainID = [...chainInfos].sort((a, b) => a.chainID.compare(b.chainID)); - for (let i = 0; i < chainInfos.length; i += 1) { - if (!chainInfos[i].chainID.equals(sortedByChainID[i].chainID)) { - throw new Error('chainInfos is not ordered lexicographically by chainID.'); - } + if (!objectUtils.isBufferArrayOrdered(chainIDs)) { + throw new Error('chainInfos is not ordered lexicographically by chainID.'); } // The entries chainData.name must be pairwise distinct @@ -307,13 +308,17 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule // verify root level properties for (const chainInfo of chainInfos) { this._verifyChainID(chainInfo.chainID, mainchainID, 'chainInfo.'); - this._verifyChainData(ctx, chainInfo); + this._verifyChainData(ctx, chainInfo, terminatedStateAccounts); this._verifyChannelData(ctx, chainInfo); this._verifyChainValidators(chainInfo); } } - private _verifyChainData(ctx: GenesisBlockExecuteContext, chainInfo: ChainInfo) { + private _verifyChainData( + ctx: GenesisBlockExecuteContext, + chainInfo: ChainInfo, + terminatedStateAccounts: TerminatedStateAccountWithChainID[], + ) { const validStatuses = [ChainStatus.REGISTERED, ChainStatus.ACTIVE, ChainStatus.TERMINATED]; const { chainData } = chainInfo; @@ -332,6 +337,17 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule if (!validStatuses.includes(chainData.status)) { throw new Error(`chainData.status must be one of ${validStatuses.join(', ')}`); } + + if (chainData.status === ChainStatus.TERMINATED) { + const accountWithChainID = terminatedStateAccounts.find(accountWithChainIDTemp => + accountWithChainIDTemp.chainID.equals(chainInfo.chainID), + ); + if (!accountWithChainID) { + throw new Error( + 'For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.', + ); + } + } } // https://github.com/LiskHQ/lips/blob/main/proposals/lip-0045.md#mainchain @@ -340,10 +356,14 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule terminatedStateAccounts: TerminatedStateAccountWithChainID[], mainchainID: Buffer, ) { + this._verifyTerminatedStateAccountsIDs(terminatedStateAccounts.map(a => a.chainID)); + // Sanity check to fulfill if-and-only-if situation - for (const account of terminatedStateAccounts) { + for (const terminatedStateAccountWithChainID of terminatedStateAccounts) { + this._verifyChainID(terminatedStateAccountWithChainID.chainID, mainchainID, 'stateAccount.'); + const correspondingChainInfo = chainInfos.find(chainInfo => - chainInfo.chainID.equals(account.chainID), + chainInfo.chainID.equals(terminatedStateAccountWithChainID.chainID), ); if ( !correspondingChainInfo || @@ -353,46 +373,29 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule 'For each terminatedStateAccount there should be a corresponding chainInfo at TERMINATED state.', ); } - } - for (const chainInfo of chainInfos) { - // For each entry chainInfo in chainInfos, chainInfo.chainData.status == CHAIN_STATUS_TERMINATED - // if and only if a corresponding entry (i.e., with chainID == chainInfo.chainID) exists in terminatedStateAccounts. - if (chainInfo.chainData.status === ChainStatus.TERMINATED) { - const terminatedAccount = terminatedStateAccounts.find(tAccount => - tAccount.chainID.equals(chainInfo.chainID), + const stateAccount = terminatedStateAccountWithChainID.terminatedStateAccount; + // For each entry stateAccount in terminatedStateAccounts holds + // stateAccount.stateRoot == chainData.lastCertificate.stateRoot, + // stateAccount.mainchainStateRoot == EMPTY_HASH, and + // stateAccount.initialized == True. + // Here chainData is the corresponding entry (i.e., with chainID == stateAccount.chainID) in chainInfos. + if ( + !stateAccount.stateRoot.equals(correspondingChainInfo.chainData.lastCertificate.stateRoot) + ) { + throw new Error( + "stateAccount.stateRoot doesn't match chainInfo.chainData.lastCertificate.stateRoot.", + ); + } + + if (!stateAccount.mainchainStateRoot.equals(EMPTY_HASH)) { + throw new Error( + `stateAccount.mainchainStateRoot is not equal to ${EMPTY_HASH.toString('hex')}.`, ); - if (!terminatedAccount) { - throw new Error( - 'For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.', - ); - } - - this._verifyTerminatedStateAccountsCommon(terminatedStateAccounts, mainchainID); - - // For each entry stateAccount in terminatedStateAccounts holds - // stateAccount.stateRoot == chainData.lastCertificate.stateRoot, - // stateAccount.mainchainStateRoot == EMPTY_HASH, and - // stateAccount.initialized == True. - // Here chainData is the corresponding entry (i.e., with chainID == stateAccount.chainID) in chainInfos. - const stateAccount = terminatedAccount.terminatedStateAccount; - if (stateAccount) { - if (!stateAccount.stateRoot.equals(chainInfo.chainData.lastCertificate.stateRoot)) { - throw new Error( - "stateAccount.stateRoot doesn't match chainInfo.chainData.lastCertificate.stateRoot.", - ); - } - - if (!stateAccount.mainchainStateRoot.equals(EMPTY_HASH)) { - throw new Error( - `stateAccount.mainchainStateRoot is not equal to ${EMPTY_HASH.toString('hex')}.`, - ); - } - - if (!stateAccount.initialized) { - throw new Error('stateAccount is not initialized.'); - } - } + } + + if (!stateAccount.initialized) { + throw new Error('stateAccount is not initialized.'); } } } @@ -410,13 +413,8 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule } // terminatedOutboxAccounts is ordered lexicographically by outboxAccount.chainID - const sortedByChainID = [...terminatedOutboxAccounts].sort((a, b) => - a.chainID.compare(b.chainID), - ); - for (let i = 0; i < terminatedOutboxAccounts.length; i += 1) { - if (!terminatedOutboxAccounts[i].chainID.equals(sortedByChainID[i].chainID)) { - throw new Error('terminatedOutboxAccounts must be ordered lexicographically by chainID.'); - } + if (!objectUtils.isBufferArrayOrdered(chainIDs)) { + throw new Error('terminatedOutboxAccounts must be ordered lexicographically by chainID.'); } // Furthermore, an entry outboxAccount in terminatedOutboxAccounts must have a corresponding entry diff --git a/framework/src/modules/interoperability/sidechain/module.ts b/framework/src/modules/interoperability/sidechain/module.ts index eb528d789f8..6a3cd0a93d1 100644 --- a/framework/src/modules/interoperability/sidechain/module.ts +++ b/framework/src/modules/interoperability/sidechain/module.ts @@ -290,7 +290,7 @@ export class SidechainInteroperabilityModule extends BaseInteroperabilityModule } // mainchainInfo.chainID == getMainchainID(); const mainchainInfo = chainInfos[0]; - const mainchainID = getMainchainID(mainchainInfo.chainID); + const mainchainID = getMainchainID(ctx.chainID); if (!mainchainInfo.chainID.equals(mainchainID)) { throw new Error(`mainchainInfo.chainID must be equal to ${mainchainID.toString('hex')}.`); } @@ -320,9 +320,11 @@ export class SidechainInteroperabilityModule extends BaseInteroperabilityModule terminatedStateAccounts: TerminatedStateAccountWithChainID[], mainchainID: Buffer, ) { - this._verifyTerminatedStateAccountsCommon(terminatedStateAccounts, mainchainID); + this._verifyTerminatedStateAccountsIDs(terminatedStateAccounts.map(a => a.chainID)); for (const stateAccount of terminatedStateAccounts) { + this._verifyChainID(stateAccount.chainID, mainchainID, 'stateAccount.'); + // and stateAccount.chainID != OWN_CHAIN_ID. if (stateAccount.chainID.equals(ctx.chainID)) { throw new Error(`stateAccount.chainID must not be equal to OWN_CHAIN_ID.`); diff --git a/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts b/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts index 0a92b2ed446..4c15a320356 100644 --- a/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts +++ b/framework/test/unit/modules/interoperability/base_interoperability_module.spec.ts @@ -445,7 +445,7 @@ must NOT have more than ${MAX_NUM_VALIDATORS} items`, }); }); - describe('_verifyTerminatedStateAccountsCommon', () => { + describe('_verifyTerminatedStateAccountsIDs', () => { certificateThreshold = BigInt(10); const validChainInfos = [ { diff --git a/framework/test/unit/modules/interoperability/mainchain/module.spec.ts b/framework/test/unit/modules/interoperability/mainchain/module.spec.ts index b62c2eaef71..a75e2eb4a5c 100644 --- a/framework/test/unit/modules/interoperability/mainchain/module.spec.ts +++ b/framework/test/unit/modules/interoperability/mainchain/module.spec.ts @@ -300,6 +300,38 @@ describe('initGenesisState', () => { ].join(', ')}`, ); }); + + it('should throw if chainInfo.chainData.status === TERMINATED exists but no terminateStateAccount', async () => { + const context = createInitGenesisStateContext( + { + ...genesisInteroperability, + chainInfos: [ + { + ...chainInfo, + chainData: { + ...chainData, + status: ChainStatus.TERMINATED, + lastCertificate: { + ...lastCertificate, + validatorsHash: computeValidatorsHash(activeValidators, certificateThreshold), + }, + }, + chainValidators: { + activeValidators, + certificateThreshold, + }, + }, + ], + // No terminatedStateAccount + terminatedStateAccounts: [], + }, + params, + ); + + await expect(interopMod.initGenesisState(context)).rejects.toThrow( + `For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.`, + ); + }); }); describe('terminatedStateAccounts', () => { @@ -332,7 +364,9 @@ describe('initGenesisState', () => { await expect(interopMod.initGenesisState(context)).resolves.not.toThrow(); }); - it('should throw if chainInfo.chainData.status===TERMINATED exists but no terminateStateAccount', async () => { + it('should call _verifyTerminatedStateAccountsIDs', async () => { + jest.spyOn(interopMod, '_verifyTerminatedStateAccountsIDs' as any); + const context = createInitGenesisStateContext( { ...genesisInteroperability, @@ -353,18 +387,21 @@ describe('initGenesisState', () => { }, }, ], - // No terminatedStateAccount - terminatedStateAccounts: [], + terminatedStateAccounts: [ + { + chainID: chainInfo.chainID, + terminatedStateAccount, + }, + ], }, params, ); - await expect(interopMod.initGenesisState(context)).rejects.toThrow( - `For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.`, - ); + await expect(interopMod.initGenesisState(context)).resolves.toBeUndefined(); + expect(interopMod['_verifyTerminatedStateAccountsIDs']).toHaveBeenCalledTimes(1); }); - it('should throw if there is an entry in terminateStateAccounts for a chainID that is ACTIVE in chainInfos', async () => { + it('should throw error if chainInfo.chainID exists in terminatedStateAccounts & chainInfo.chainData.status is ACTIVE', async () => { const context = createInitGenesisStateContext( { ...genesisInteroperability, @@ -400,7 +437,7 @@ describe('initGenesisState', () => { ); }); - it('should throw error if chainInfo.chainID exists in terminatedStateAccounts & chainInfo.chainData.status !== CHAIN_STATUS_TERMINATED', async () => { + it('should throw error if chainInfo.chainID exists in terminatedStateAccounts & chainInfo.chainData.status is REGISTERED', async () => { const context = createInitGenesisStateContext( { ...genesisInteroperability, @@ -450,7 +487,6 @@ describe('initGenesisState', () => { ...lastCertificate, validatorsHash: computeValidatorsHash(activeValidators, certificateThreshold), }, - status: ChainStatus.TERMINATED, }, chainValidators: { activeValidators, From 38b1eb2a3820040ef4c28046e5e1b9f18a2964b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mona=20B=C3=A4renf=C3=A4nger?= Date: Fri, 20 Oct 2023 13:17:17 +0200 Subject: [PATCH 10/13] Apply suggestions from code review Co-authored-by: !shan --- .../pos-sidechain-example-one/src/app/modules/hello/schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts index fa0cf418865..577f1f9deb4 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/schema.ts @@ -96,7 +96,7 @@ export const crossChainReactParamsSchema = { * ID of the message. */ helloMessageID: { - dataType: 'bytes', + dataType: 'string', fieldNumber: 2, }, /** Optional field for data / messages. */ @@ -111,6 +111,6 @@ export const crossChainReactParamsSchema = { export interface CCReactMessageParams { reactionType: number; - helloMessageID: Buffer; + helloMessageID: string; data: string; } From fc7e136902b8847b4264ef12e38a24ce5bb03864 Mon Sep 17 00:00:00 2001 From: Tschakki Date: Fri, 20 Oct 2023 14:10:04 +0200 Subject: [PATCH 11/13] Fix name bug --- examples/interop/common/mainchain_registration.ts | 2 +- .../config/scripts/sidechain_registration.ts | 12 ++++++------ .../config/scripts/transfer_lsk_mainchain.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/interop/common/mainchain_registration.ts b/examples/interop/common/mainchain_registration.ts index a3edbdcc85d..d1f3216194f 100644 --- a/examples/interop/common/mainchain_registration.ts +++ b/examples/interop/common/mainchain_registration.ts @@ -51,7 +51,7 @@ export const registerMainchain = async (mc: string, sc: string, sidechainDevVali // Sort validator list lexicographically after their BLS key const paramsJSON = { ownChainID: sidechainNodeInfo.chainID, - ownName: sc, + ownName: sc.replace(/-/g, '_'), mainchainValidators: (mainchainActiveValidators as { blsKey: string; bftWeight: string }[]) .map(v => ({ blsKey: v.blsKey, bftWeight: v.bftWeight })) .sort((a, b) => Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex'))), diff --git a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts index 27dbc3e4f90..24d8c2f472d 100644 --- a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts +++ b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts @@ -38,13 +38,13 @@ import { keys } from '../default/dev-validators.json'; sidechainCertificateThreshold: certificateThreshold, sidechainValidators: sidehcainActiveValidators, chainID: sidechainNodeInfo.chainID, - name: `sidechain_example_${nodeAlias}`, + name: nodeAlias.replace(/-/g, '_'), }; // Get public key and nonce of the sender account - const relayerkeyInfo = keys[2]; + const relayerKeyInfo = keys[2]; const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', { - address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerkeyInfo.publicKey, 'hex')), + address: address.getLisk32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')), }); // Create registerSidechain transaction @@ -54,14 +54,14 @@ import { keys } from '../default/dev-validators.json'; fee: BigInt(2000000000), params: codec.encodeJSON(sidechainRegParams, params), nonce: BigInt(nonce), - senderPublicKey: Buffer.from(relayerkeyInfo.publicKey, 'hex'), + senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'), signatures: [], }); // Sign the transaction tx.sign( Buffer.from(mainchainNodeInfo.chainID as string, 'hex'), - Buffer.from(relayerkeyInfo.privateKey, 'hex'), + Buffer.from(relayerKeyInfo.privateKey, 'hex'), ); // Post the transaction to a mainchain node @@ -72,7 +72,7 @@ import { keys } from '../default/dev-validators.json'; }); console.log( - `Sent sidechain registration transaction on mainchain node ${nodeAlias}. Result from transaction pool is: `, + `Sent sidechain registration transaction on mainchain node ${MAINCHAIN_ARRAY[1]}. Result from transaction pool is: `, result, ); i += 1; diff --git a/examples/interop/pos-sidechain-example-two/config/scripts/transfer_lsk_mainchain.ts b/examples/interop/pos-sidechain-example-two/config/scripts/transfer_lsk_mainchain.ts index fd7765b979e..596e8cfda0b 100644 --- a/examples/interop/pos-sidechain-example-two/config/scripts/transfer_lsk_mainchain.ts +++ b/examples/interop/pos-sidechain-example-two/config/scripts/transfer_lsk_mainchain.ts @@ -11,7 +11,7 @@ type ModulesMetadata = [ (async () => { const { address } = cryptography; - const nodeAlias = 'one'; + const nodeAlias = 'two'; const tokenID = Buffer.from('0400000000000000', 'hex'); const mainchainID = Buffer.from('04000000', 'hex'); const recipientLSKAddress = 'lskzjzeam6szx4a65sxgavr98m9h4kctcx85nvy7h'; From 808984a72342491167f7d41b36ea56b1d9421940 Mon Sep 17 00:00:00 2001 From: Tschakki Date: Fri, 20 Oct 2023 14:15:57 +0200 Subject: [PATCH 12/13] Fix error --- .../src/app/modules/hello/cc_commands/react_command.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts index a88e61dfaa5..de26cd3409c 100644 --- a/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts +++ b/examples/interop/pos-sidechain-example-one/src/app/modules/hello/cc_commands/react_command.ts @@ -23,7 +23,7 @@ export class ReactCCCommand extends BaseCCCommand { const params = codec.decode(crossChainReactParamsSchema, ccm.params); const messageCreatorAddress = cryptography.address.getAddressFromLisk32Address( - params.helloMessageID.toString('utf-8'), + params.helloMessageID, ); if (!(await this.stores.get(MessageStore).has(ctx, messageCreatorAddress))) { throw new Error('Message ID does not exists.'); @@ -41,9 +41,7 @@ export class ReactCCCommand extends BaseCCCommand { const reactionSubstore = this.stores.get(ReactionStore); logger.info({ helloMessageID }, 'Contents of helloMessageID'); - const messageCreatorAddress = cryptography.address.getAddressFromLisk32Address( - helloMessageID.toString('utf-8'), - ); + const messageCreatorAddress = cryptography.address.getAddressFromLisk32Address(helloMessageID); logger.info({ messageCreatorAddress }, 'Contents of messageCreatorAddress'); let msgReactions: ReactionStoreData; @@ -59,9 +57,7 @@ export class ReactCCCommand extends BaseCCCommand { logger.info( { helloMessageID, crossChainCommand: this.name }, - `No entry exists for given helloMessageID ${helloMessageID.toString( - 'utf-8', - )}. Creating a default entry.`, + `No entry exists for given helloMessageID ${helloMessageID}. Creating a default entry.`, ); msgReactions = { reactions: { like: [] } }; } From 85070a346d043bc76fd634423afae6cc0b7f5f13 Mon Sep 17 00:00:00 2001 From: Tschakki Date: Fri, 20 Oct 2023 14:49:58 +0200 Subject: [PATCH 13/13] Update sidechain_registration.ts --- .../config/scripts/sidechain_registration.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts index 24d8c2f472d..050845b6336 100644 --- a/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts +++ b/examples/interop/pos-mainchain-fast/config/scripts/sidechain_registration.ts @@ -22,21 +22,21 @@ import { keys } from '../default/dev-validators.json'; const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo'); const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo'); - // Get active validators from sidechain - const { validators: sidehcainActiveValidators, certificateThreshold } = + // Get info about the active sidechain validators and the certificate threshold + const { validators: sidechainActiveValidators, certificateThreshold } = await sidechainClient.invoke('consensus_getBFTParameters', { height: sidechainNodeInfo.height, }); // Sort validator list lexicographically after their BLS key - (sidehcainActiveValidators as { blsKey: string; bftWeight: string }[]).sort((a, b) => + (sidechainActiveValidators as { blsKey: string; bftWeight: string }[]).sort((a, b) => Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex')), ); // Define parameters for the sidechain registration const params = { sidechainCertificateThreshold: certificateThreshold, - sidechainValidators: sidehcainActiveValidators, + sidechainValidators: sidechainActiveValidators, chainID: sidechainNodeInfo.chainID, name: nodeAlias.replace(/-/g, '_'), };