From 7ca3b3818deddcf0242ad767065ed8975f02f12b Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:30:21 +0200 Subject: [PATCH] Handle signers rotated event and updates. --- .../approvals.processor.service.ts | 2 +- .../approvals.processor.spec.ts | 10 + .../processors/gateway.processor.spec.ts | 30 ++- .../processors/gateway.processor.ts | 65 ++--- .../src/gas-checker/gas-checker.service.ts | 1 - .../message-approved.processor.service.ts | 20 +- .../message-approved.processor.e2e-spec.ts | 6 +- .../src/api/entities/axelar.gmp.api.d.ts | 157 +++++++++++- .../src/assets/axelar-gmp-api.schema.yaml | 237 +++++++++++++++++- .../src/contracts/entities/gateway-events.ts | 4 +- .../src/contracts/gateway.contract.spec.ts | 2 + libs/common/src/contracts/gateway.contract.ts | 6 +- 12 files changed, 478 insertions(+), 62 deletions(-) diff --git a/apps/axelar-event-processor/src/approvals-processor/approvals.processor.service.ts b/apps/axelar-event-processor/src/approvals-processor/approvals.processor.service.ts index fabc74c..94769b4 100644 --- a/apps/axelar-event-processor/src/approvals-processor/approvals.processor.service.ts +++ b/apps/axelar-event-processor/src/approvals-processor/approvals.processor.service.ts @@ -276,7 +276,7 @@ export class ApprovalsProcessorService { response.remainingGasBalance.amount, ); - // TODO: Handle retries in case of transaction failing? + // If transaction generation fails, the task will be retried in parent function const txHash = await this.transactionsHelper.signAndSendTransactionAndGetNonce(transaction, this.walletSigner); this.logger.debug(`Processed refund for ${response.message.messageID}, sent transaction ${txHash}`); diff --git a/apps/axelar-event-processor/src/approvals-processor/approvals.processor.spec.ts b/apps/axelar-event-processor/src/approvals-processor/approvals.processor.spec.ts index 3dc85c5..6d9cee5 100644 --- a/apps/axelar-event-processor/src/approvals-processor/approvals.processor.spec.ts +++ b/apps/axelar-event-processor/src/approvals-processor/approvals.processor.spec.ts @@ -124,6 +124,7 @@ describe('ApprovalsProcessorService', () => { } as RefundTask, id: 'lastUUID1', timestamp: '1234', + chain: 'multiversx', }, ]; } @@ -159,6 +160,7 @@ describe('ApprovalsProcessorService', () => { } as GatewayTransactionTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -235,6 +237,7 @@ describe('ApprovalsProcessorService', () => { } as ExecuteTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -281,6 +284,7 @@ describe('ApprovalsProcessorService', () => { } as ExecuteTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -308,6 +312,7 @@ describe('ApprovalsProcessorService', () => { } as GatewayTransactionTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -360,6 +365,7 @@ describe('ApprovalsProcessorService', () => { } as VerifyTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -616,6 +622,7 @@ describe('ApprovalsProcessorService', () => { } as RefundTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -663,6 +670,7 @@ describe('ApprovalsProcessorService', () => { } as RefundTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -716,6 +724,7 @@ describe('ApprovalsProcessorService', () => { } as RefundTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, @@ -767,6 +776,7 @@ describe('ApprovalsProcessorService', () => { } as RefundTask, id: 'UUID', timestamp: '1234', + chain: 'multiversx', }, ], }, diff --git a/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.spec.ts b/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.spec.ts index 3111db3..9954efe 100644 --- a/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.spec.ts +++ b/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.spec.ts @@ -9,7 +9,7 @@ import { ContractCallEvent, MessageApprovedEvent, MessageExecutedEvent, - WeightedSigners, + SignersRotatedEvent, } from '@mvx-monorepo/common/contracts/entities/gateway-events'; import { TransactionEvent, TransactionOnNetwork } from '@multiversx/sdk-network-providers/out'; import { MessageApprovedRepository } from '@mvx-monorepo/common/database/repository/message-approved.repository'; @@ -19,6 +19,7 @@ import BigNumber from 'bignumber.js'; import CallEvent = Components.Schemas.CallEvent; import MessageApprovedEventApi = Components.Schemas.MessageApprovedEvent; import MessageExecutedEventApi = Components.Schemas.MessageExecutedEvent; +import SignersRotatedEventApi = Components.Schemas.SignersRotatedEvent; const mockGatewayContract = 'erd1qqqqqqqqqqqqqpgqvc7gdl0p4s97guh498wgz75k8sav6sjfjlwqh679jy'; @@ -48,7 +49,9 @@ describe('GatewayProcessor', () => { sourceChain: 'ethereum', messageId: 'messageId', }; - const weightedSigners: WeightedSigners = { + const signersRotatedEvent: SignersRotatedEvent = { + epoch: new BigNumber(1), + signersHash: '0c38359b7a35c755573659d797afec315bb0e51374a056745abd9764715a15da', signers: [ { signer: '', @@ -314,13 +317,30 @@ describe('GatewayProcessor', () => { }); it('Should handle event', async () => { - gatewayContract.decodeSignersRotatedEvent.mockReturnValueOnce(weightedSigners); + gatewayContract.decodeSignersRotatedEvent.mockReturnValueOnce(signersRotatedEvent); - const result = await service.handleGatewayEvent(rawEvent, createMock(), 0, '100', '0'); + const transaction = createMock(); + transaction.hash = 'txHash'; + transaction.sender = Address.newFromBech32('erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7'); + + const result = await service.handleGatewayEvent(rawEvent, transaction, 0, '100', '0'); expect(gatewayContract.decodeSignersRotatedEvent).toHaveBeenCalledTimes(1); - expect(result).toEqual(undefined); + expect(result).not.toBeUndefined(); + expect(result?.type).toBe('SIGNERS_ROTATED'); + + const event = result as SignersRotatedEventApi; + + expect(event.eventID).toBe('0xtxHash-0'); + expect(event.messageID).toBe('0xtxHash-0'); + expect(event.meta).toEqual({ + txID: 'txHash', + fromAddress: 'erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7', + finalized: true, + epoch: 1, + signersHash: BinaryUtils.hexToBase64('0c38359b7a35c755573659d797afec315bb0e51374a056745abd9764715a15da'), + }); }); }); }); diff --git a/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.ts b/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.ts index 5db522b..c6c0c45 100644 --- a/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.ts +++ b/apps/mvx-event-processor/src/cross-chain-transaction-processor/processors/gateway.processor.ts @@ -14,6 +14,7 @@ import MessageApprovedEvent = Components.Schemas.MessageApprovedEvent; import Event = Components.Schemas.Event; import MessageExecutedEvent = Components.Schemas.MessageExecutedEvent; import BigNumber from 'bignumber.js'; +import SignersRotatedEvent = Components.Schemas.SignersRotatedEvent; @Injectable() export class GatewayProcessor { @@ -55,7 +56,7 @@ export class GatewayProcessor { } if (rawEvent.identifier === EventIdentifiers.ROTATE_SIGNERS && eventName === Events.SIGNERS_ROTATED_EVENT) { - return this.handleSignersRotatedEvent(rawEvent, transaction.hash, index); + return this.handleSignersRotatedEvent(rawEvent, transaction.sender.bech32(), transaction.hash, index); } return undefined; @@ -169,7 +170,7 @@ export class GatewayProcessor { fromAddress: sender, finalized: true, }, - status: 'SUCCESSFUL', // TODO: How to handle reverted? + status: 'SUCCESSFUL', }; this.logger.debug( @@ -182,46 +183,30 @@ export class GatewayProcessor { }; } - // TODO: Properly implement this after the Axelar GMP API supports it - private handleSignersRotatedEvent(rawEvent: ITransactionEvent, txHash: string, index: number) { - const weightedSigners = this.gatewayContract.decodeSignersRotatedEvent(rawEvent); + private handleSignersRotatedEvent(rawEvent: ITransactionEvent, sender: string, txHash: string, index: number): Event { + const signersRotatedEvent = this.gatewayContract.decodeSignersRotatedEvent(rawEvent); - this.logger.warn( - `Received Signers Rotated event which is not properly implemented yet. Transaction: ${txHash}, index: ${index}`, - weightedSigners, - ); + const signersRotated: SignersRotatedEvent = { + eventID: DecodingUtils.getEventId(txHash, index), + messageID: DecodingUtils.getEventId(txHash, index), + meta: { + txID: txHash, + fromAddress: sender, + finalized: true, + signersHash: BinaryUtils.hexToBase64(signersRotatedEvent.signersHash), + epoch: signersRotatedEvent.epoch.toNumber(), + }, + }; - return undefined; + this.logger.debug( + `Successfully handled signers rotated event from transaction ${txHash}, log index ${index}`, + signersRotated, + signersRotatedEvent, + ); - // // The id needs to have `0x` in front of the txHash (hex string) - // const id = `0x${txHash}-${index}`; - // - // - // // @ts-ignore - // const response = await this.axelarGmpApi.verifyVerifierSet( - // id, - // weightedSigners.signers, - // weightedSigners.threshold, - // weightedSigners.nonce, - // ); - - // if (response.published) { - // return; - // } - // - // this.logger.warn(`Couldn't dispatch verifyWorkerSet ${id} to Amplifier API. Retrying...`); - // - // setTimeout(async () => { - // const response = await this.axelarGmpApi.verifyVerifierSet( - // id, - // weightedSigners.signers, - // weightedSigners.threshold, - // weightedSigners.nonce, - // ); - // - // if (!response.published) { - // this.logger.error(`Couldn't dispatch verifyWorkerSet ${id} to Amplifier API.`); - // } - // }, 60_000); + return { + type: 'SIGNERS_ROTATED', + ...signersRotated, + }; } } diff --git a/apps/mvx-event-processor/src/gas-checker/gas-checker.service.ts b/apps/mvx-event-processor/src/gas-checker/gas-checker.service.ts index aaee141..5eedd2b 100644 --- a/apps/mvx-event-processor/src/gas-checker/gas-checker.service.ts +++ b/apps/mvx-event-processor/src/gas-checker/gas-checker.service.ts @@ -69,7 +69,6 @@ export class GasCheckerService { } private async checkGasServiceFees() { - // TODO: Add support for other tokens also const tokens = await this.getAccountEgldAndWegld(this.gasServiceContract.getContractAddress()); const tokensToCollect = Object.values(tokens) .filter((token) => token.balance.gte(EGLD_COLLECT_THRESHOLD)) diff --git a/apps/mvx-event-processor/src/message-approved-processor/message-approved.processor.service.ts b/apps/mvx-event-processor/src/message-approved-processor/message-approved.processor.service.ts index cbea771..8df4526 100644 --- a/apps/mvx-event-processor/src/message-approved-processor/message-approved.processor.service.ts +++ b/apps/mvx-event-processor/src/message-approved-processor/message-approved.processor.service.ts @@ -18,8 +18,14 @@ import { ApiConfigService, AxelarGmpApi } from '@mvx-monorepo/common'; import { ItsContract } from '@mvx-monorepo/common/contracts/its.contract'; import { Locker } from '@multiversx/sdk-nestjs-common'; import { GasError } from '@mvx-monorepo/common/contracts/entities/gas.error'; -import { CannotExecuteMessageEvent, Event } from '@mvx-monorepo/common/api/entities/axelar.gmp.api'; +import { + CannotExecuteMessageEvent, + CannotExecuteMessageEventV2, + Event, +} from '@mvx-monorepo/common/api/entities/axelar.gmp.api'; import { AxiosError } from 'axios'; +import { DecodingUtils } from '@mvx-monorepo/common/utils/decoding.utils'; +import { CONSTANTS } from '@mvx-monorepo/common/utils/constants.enum'; // Support a max of 3 retries (mainly because some Interchain Token Service endpoints need to be called 2 times) const MAX_NUMBER_OF_RETRIES: number = 3; @@ -202,11 +208,17 @@ export class MessageApprovedProcessorService { messageApproved.status = MessageApprovedStatus.FAILED; - const cannotExecuteEvent: CannotExecuteMessageEvent = { - eventID: messageApproved.messageId, - taskItemID: messageApproved.taskItemId || '', + const cannotExecuteEvent: CannotExecuteMessageEventV2 = { + eventID: messageApproved.executeTxHash + ? DecodingUtils.getEventId(messageApproved.executeTxHash, 0) + : messageApproved.messageId, + messageID: messageApproved.messageId, + sourceChain: CONSTANTS.SOURCE_CHAIN_NAME, reason: 'ERROR', details: '', + meta: { + taskItemID: messageApproved.taskItemId || '', + }, }; try { diff --git a/apps/mvx-event-processor/test/message-approved.processor.e2e-spec.ts b/apps/mvx-event-processor/test/message-approved.processor.e2e-spec.ts index f68ce6c..edb42ae 100644 --- a/apps/mvx-event-processor/test/message-approved.processor.e2e-spec.ts +++ b/apps/mvx-event-processor/test/message-approved.processor.e2e-spec.ts @@ -246,9 +246,13 @@ describe('MessageApprovedProcessorService', () => { expect(axelarGmpApi.postEvents.mock.lastCall[0][0]).toEqual({ type: 'CANNOT_EXECUTE_MESSAGE', eventID: originalSecondEntry.messageId, - taskItemID: originalSecondEntry.taskItemId, + messageID: originalSecondEntry.messageId, + sourceChain: 'multiversx', reason: 'ERROR', details: '', + meta: { + taskItemID: originalSecondEntry.taskItemId, + }, }); // Was not updated diff --git a/libs/common/src/api/entities/axelar.gmp.api.d.ts b/libs/common/src/api/entities/axelar.gmp.api.d.ts index d3732ea..a03c31b 100644 --- a/libs/common/src/api/entities/axelar.gmp.api.d.ts +++ b/libs/common/src/api/entities/axelar.gmp.api.d.ts @@ -9,11 +9,15 @@ import type { declare namespace Components { namespace Parameters { export type After = string; + export type BroadcastID = Schemas.BroadcastID; export type Chain = string; export type Limit = number; + export type WasmContractAddress = string; // ^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$ } export interface PathParameters { chain: Parameters.Chain; + wasmContractAddress: Parameters.WasmContractAddress /* ^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$ */; + broadcastID: Parameters.BroadcastID; } export interface QueryParameters { after?: Parameters.After; @@ -22,6 +26,30 @@ declare namespace Components { namespace Schemas { export type Address = string; export type BigInt = string; // ^(0|[1-9]\d*)$ + export type BroadcastID = string; + export interface BroadcastRequest { + [name: string]: any; + } + export interface BroadcastResponse { + broadcastID: BroadcastID; + } + export type BroadcastStatus = "RECEIVED" | "SUCCESS" | "ERROR"; + export interface BroadcastStatusResponse { + status: BroadcastStatus; + receivedAt: string; // date-time + completedAt?: string; // date-time + txEvents?: BroadcastTxEvent[]; + txHash?: string | null; + error?: string | null; + } + export interface BroadcastTxEvent { + type: string; + attributes: BroadcastTxEventAttribute[]; + } + export interface BroadcastTxEventAttribute { + key: string; + value: string; + } export interface CallEvent { eventID: string; meta?: { @@ -34,6 +62,7 @@ declare namespace Components { message: GatewayV2Message; destinationChain: string; payload: string; // byte + withToken?: Token; } export interface CallEventMetadata { txID?: string | null; @@ -58,6 +87,25 @@ declare namespace Components { fromAddress?: string | null; timestamp?: string; // date-time } + export interface CannotExecuteMessageEventV2 { + eventID: string; + meta?: { + txID?: string | null; + timestamp?: string; // date-time + fromAddress?: string | null; + finalized?: boolean | null; + taskItemID?: TaskItemID; + } | null; + messageID: string; + sourceChain: string; + reason: CannotExecuteMessageReason; + details: string; + } + export interface CannotExecuteMessageEventV2Metadata { + fromAddress?: string | null; + timestamp?: string; // date-time + taskItemID?: TaskItemID; + } export type CannotExecuteMessageReason = "INSUFFICIENT_GAS" | "ERROR"; export interface ErrorResponse { error: string; @@ -65,7 +113,7 @@ declare namespace Components { } export type Event = { type: EventType; - } & (GasCreditEvent | GasRefundedEvent | CallEvent | MessageApprovedEvent | MessageExecutedEvent | CannotExecuteMessageEvent); + } & (GasCreditEvent | GasRefundedEvent | CallEvent | MessageApprovedEvent | MessageExecutedEvent | CannotExecuteMessageEvent | CannotExecuteMessageEventV2 | SignersRotatedEvent); export interface EventBase { eventID: string; meta?: { @@ -81,7 +129,7 @@ declare namespace Components { fromAddress?: string | null; finalized?: boolean | null; } - export type EventType = "GAS_CREDIT" | "GAS_REFUNDED" | "CALL" | "MESSAGE_APPROVED" | "MESSAGE_EXECUTED" | "CANNOT_EXECUTE_MESSAGE"; + export type EventType = "GAS_CREDIT" | "GAS_REFUNDED" | "CALL" | "MESSAGE_APPROVED" | "MESSAGE_EXECUTED" | "CANNOT_EXECUTE_MESSAGE" | "SIGNERS_ROTATED"; export interface ExecuteTask { message: GatewayV2Message; payload: string; // byte @@ -296,9 +344,30 @@ declare namespace Components { refundRecipientAddress: Address; remainingGasBalance: Token; } + export interface SignersRotatedEvent { + eventID: string; + meta?: { + txID?: string | null; + timestamp?: string; // date-time + fromAddress?: string | null; + finalized?: boolean | null; + signersHash?: string; // byte + epoch?: number; // int64 + } | null; + messageID: string; + } + export interface SignersRotatedEventMetadata { + txID?: string | null; + timestamp?: string; // date-time + fromAddress?: string | null; + finalized?: boolean | null; + signersHash?: string; // byte + epoch?: number; // int64 + } export type Task = VerifyTask | GatewayTransactionTask | ExecuteTask | RefundTask; export interface TaskItem { id: string; + chain: string; timestamp: string; // date-time type: TaskType; task: Task; @@ -316,6 +385,20 @@ declare namespace Components { } } declare namespace Paths { + namespace BroadcastMsgExecuteContract { + namespace Parameters { + export type WasmContractAddress = string; // ^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$ + } + export interface PathParameters { + wasmContractAddress: Parameters.WasmContractAddress /* ^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$ */; + } + export type RequestBody = Components.Schemas.BroadcastRequest; + namespace Responses { + export type $200 = Components.Schemas.BroadcastResponse; + export type $400 = Components.Schemas.ErrorResponse; + export type $500 = Components.Schemas.ErrorResponse; + } + } namespace Chains$ChainEvents { namespace Post { namespace Parameters { @@ -333,6 +416,21 @@ declare namespace Paths { } } } + namespace GetMsgExecuteContractBroadcastStatus { + namespace Parameters { + export type BroadcastID = Components.Schemas.BroadcastID; + export type WasmContractAddress = string; // ^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$ + } + export interface PathParameters { + wasmContractAddress: Parameters.WasmContractAddress /* ^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$ */; + broadcastID: Parameters.BroadcastID; + } + namespace Responses { + export type $200 = Components.Schemas.BroadcastStatusResponse; + export type $404 = Components.Schemas.ErrorResponse; + export type $500 = Components.Schemas.ErrorResponse; + } + } namespace GetTasks { namespace Parameters { export type After = string; @@ -367,15 +465,31 @@ export interface OperationMethods { 'healthCheck'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse + /** + * broadcastMsgExecuteContract - Broadcast arbitrary MsgExecuteContract transaction + */ + 'broadcastMsgExecuteContract'( + parameters?: Parameters | null, + data?: Paths.BroadcastMsgExecuteContract.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getMsgExecuteContractBroadcastStatus - Get broadcast status + */ + 'getMsgExecuteContractBroadcastStatus'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse /** * getTasks - Poll transaction to be executed on chain */ 'getTasks'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } @@ -387,9 +501,29 @@ export interface PathsDictionary { 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } + ['/contracts/{wasmContractAddress}/broadcasts']: { + /** + * broadcastMsgExecuteContract - Broadcast arbitrary MsgExecuteContract transaction + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.BroadcastMsgExecuteContract.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/contracts/{wasmContractAddress}/broadcasts/{broadcastID}']: { + /** + * getMsgExecuteContractBroadcastStatus - Get broadcast status + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } ['/chains/{chain}/events']: { } ['/chains/{chain}/tasks']: { @@ -399,7 +533,7 @@ export interface PathsDictionary { 'get'( parameters?: Parameters | null, data?: any, - config?: AxiosRequestConfig + config?: AxiosRequestConfig ): OperationResponse } } @@ -408,10 +542,19 @@ export type Client = OpenAPIClient export type Address = Components.Schemas.Address; export type BigInt = Components.Schemas.BigInt; +export type BroadcastID = Components.Schemas.BroadcastID; +export type BroadcastRequest = Components.Schemas.BroadcastRequest; +export type BroadcastResponse = Components.Schemas.BroadcastResponse; +export type BroadcastStatus = Components.Schemas.BroadcastStatus; +export type BroadcastStatusResponse = Components.Schemas.BroadcastStatusResponse; +export type BroadcastTxEvent = Components.Schemas.BroadcastTxEvent; +export type BroadcastTxEventAttribute = Components.Schemas.BroadcastTxEventAttribute; export type CallEvent = Components.Schemas.CallEvent; export type CallEventMetadata = Components.Schemas.CallEventMetadata; export type CannotExecuteMessageEvent = Components.Schemas.CannotExecuteMessageEvent; export type CannotExecuteMessageEventMetadata = Components.Schemas.CannotExecuteMessageEventMetadata; +export type CannotExecuteMessageEventV2 = Components.Schemas.CannotExecuteMessageEventV2; +export type CannotExecuteMessageEventV2Metadata = Components.Schemas.CannotExecuteMessageEventV2Metadata; export type CannotExecuteMessageReason = Components.Schemas.CannotExecuteMessageReason; export type ErrorResponse = Components.Schemas.ErrorResponse; export type Event = Components.Schemas.Event; @@ -437,6 +580,8 @@ export type PublishEventStatus = Components.Schemas.PublishEventStatus; export type PublishEventsRequest = Components.Schemas.PublishEventsRequest; export type PublishEventsResult = Components.Schemas.PublishEventsResult; export type RefundTask = Components.Schemas.RefundTask; +export type SignersRotatedEvent = Components.Schemas.SignersRotatedEvent; +export type SignersRotatedEventMetadata = Components.Schemas.SignersRotatedEventMetadata; export type Task = Components.Schemas.Task; export type TaskItem = Components.Schemas.TaskItem; export type TaskItemID = Components.Schemas.TaskItemID; diff --git a/libs/common/src/assets/axelar-gmp-api.schema.yaml b/libs/common/src/assets/axelar-gmp-api.schema.yaml index 1d14367..800ce87 100644 --- a/libs/common/src/assets/axelar-gmp-api.schema.yaml +++ b/libs/common/src/assets/axelar-gmp-api.schema.yaml @@ -11,6 +11,63 @@ paths: responses: '200': description: OK + /contracts/{wasmContractAddress}/broadcasts: + post: + summary: Broadcast arbitrary MsgExecuteContract transaction + operationId: broadcastMsgExecuteContract + parameters: + - $ref: '#/components/parameters/wasmContractAddress' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BroadcastRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/BroadcastResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /contracts/{wasmContractAddress}/broadcasts/{broadcastID}: + get: + summary: Get broadcast status + operationId: getMsgExecuteContractBroadcastStatus + parameters: + - $ref: '#/components/parameters/wasmContractAddress' + - $ref: '#/components/parameters/broadcastID' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/BroadcastStatusResponse' + '404': + description: Broadcast Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' /chains/{chain}/events: post: summary: Publish on-chain events @@ -94,7 +151,7 @@ components: x-go-type-import: path: github.com/google/uuid name: uuid - example: "TBD" + example: "deadbeef-dead-beef-dead-beefdeadbeef" limit: name: limit in: query @@ -104,6 +161,21 @@ components: minimum: 1 default: 20 example: 10 + wasmContractAddress: + name: wasmContractAddress + in: path + required: true + schema: + type: string + pattern: '^axelar1[acdefghjklmnpqrstuvwxyz023456789]{58}$' + example: "axelar16mek8sdcsq78jltfue35zhm5ds0cxpl0dfnrel8kck3jwtecdtnqcejdav" + broadcastID: + name: broadcastID + in: path + required: true + schema: + $ref: '#/components/schemas/BroadcastID' + example: "deadbeef-dead-beef-dead-beefdeadbeef" schemas: PublishEventsRequest: type: object @@ -125,6 +197,7 @@ components: - MESSAGE_APPROVED - MESSAGE_EXECUTED - CANNOT_EXECUTE_MESSAGE + - SIGNERS_ROTATED x-enum-varnames: - EventTypeGasCredit - EventTypeGasRefunded @@ -132,6 +205,7 @@ components: - EventTypeMessageApproved - EventTypeMessageExecuted - EventTypeCannotExecuteMessage + - EventTypeSignersRotated EventMetadata: type: object properties: @@ -170,6 +244,7 @@ components: type: string nullable: true x-omitempty: true + # TODO: use exact length minLength: 1 MessageExecutedEventMetadata: allOf: @@ -179,6 +254,7 @@ components: type: string nullable: true x-omitempty: true + # TODO: use exact length minLength: 1 childMessageIDs: type: array @@ -198,6 +274,31 @@ components: timestamp: type: string format: date-time + CannotExecuteMessageEventV2Metadata: + type: object + properties: + fromAddress: + allOf: + - $ref: '#/components/schemas/Address' + nullable: true + x-omitempty: true + timestamp: + type: string + format: date-time + taskItemID: + $ref: '#/components/schemas/TaskItemID' + SignersRotatedEventMetadata: + allOf: + - $ref: '#/components/schemas/EventMetadata' + - properties: + signersHash: + type: string + format: byte + minLength: 1 + epoch: + type: integer + format: int64 + minimum: 0 Event: oneOf: - $ref: '#/components/schemas/GasCreditEvent' @@ -206,6 +307,8 @@ components: - $ref: '#/components/schemas/MessageApprovedEvent' - $ref: '#/components/schemas/MessageExecutedEvent' - $ref: '#/components/schemas/CannotExecuteMessageEvent' + - $ref: '#/components/schemas/CannotExecuteMessageEventV2' + - $ref: '#/components/schemas/SignersRotatedEvent' discriminator: propertyName: type mapping: @@ -215,6 +318,8 @@ components: MESSAGE_APPROVED: '#/components/schemas/MessageApprovedEvent' MESSAGE_EXECUTED: '#/components/schemas/MessageExecutedEvent' CANNOT_EXECUTE_MESSAGE: '#/components/schemas/CannotExecuteMessageEvent' + CANNOT_EXECUTE_MESSAGE/V2: '#/components/schemas/CannotExecuteMessageEventV2' + SIGNERS_ROTATED: '#/components/schemas/SignersRotatedEvent' properties: type: $ref: '#/components/schemas/EventType' @@ -287,6 +392,8 @@ components: payload: type: string format: byte + withToken: + $ref: '#/components/schemas/Token' required: - message - destinationChain @@ -365,6 +472,33 @@ components: - taskItemID - reason - details + CannotExecuteMessageEventV2: + type: object + allOf: + - $ref: '#/components/schemas/EventBase' + - properties: + meta: + allOf: + - $ref: '#/components/schemas/CannotExecuteMessageEventV2Metadata' + nullable: true + x-omitempty: true + - properties: + messageID: + type: string + minLength: 1 + sourceChain: + type: string + minLength: 1 + reason: + $ref: '#/components/schemas/CannotExecuteMessageReason' + details: + type: string + minLength: 1 + required: + - messageID + - sourceChain + - reason + - details CannotExecuteMessageReason: type: string enum: @@ -373,6 +507,22 @@ components: x-enum-varnames: - CannotExecuteMessageReasonInsufficientGas - CannotExecuteMessageReasonError + SignersRotatedEvent: + type: object + allOf: + - $ref: '#/components/schemas/EventBase' + - properties: + meta: + allOf: + - $ref: '#/components/schemas/SignersRotatedEventMetadata' + nullable: true + x-omitempty: true + - properties: + messageID: + type: string + minLength: 1 + required: + - messageID GatewayV2Message: type: object properties: @@ -487,6 +637,9 @@ components: allOf: - $ref: '#/components/schemas/TaskItemID' x-go-name: ID + chain: + type: string + minLength: 1 timestamp: type: string format: date-time @@ -496,6 +649,7 @@ components: $ref: '#/components/schemas/Task' required: - id + - chain - timestamp - type - task @@ -557,6 +711,80 @@ components: - message - payload - availableGasBalance + BroadcastRequest: + type: object + additionalProperties: {} + BroadcastResponse: + type: object + properties: + broadcastID: + $ref: "#/components/schemas/BroadcastID" + required: + - broadcastID + BroadcastStatusResponse: + type: object + properties: + status: + $ref: '#/components/schemas/BroadcastStatus' + receivedAt: + type: string + format: date-time + completedAt: + type: string + format: date-time + txEvents: + type: array + x-omitempty: true + items: + $ref: '#/components/schemas/BroadcastTxEvent' + txHash: + type: string + nullable: true + x-omitempty: true + minLength: 1 + error: + type: string + nullable: true + x-omitempty: true + minLength: 1 + required: + - receivedAt + - status + BroadcastTxEvent: + type: object + properties: + type: + type: string + minLength: 1 + attributes: + type: array + items: + $ref: '#/components/schemas/BroadcastTxEventAttribute' + required: + - type + - attributes + BroadcastTxEventAttribute: + type: object + properties: + key: + type: string + minLength: 1 + value: + type: string + minLength: 1 + required: + - key + - value + BroadcastStatus: + type: string + enum: + - RECEIVED + - SUCCESS + - ERROR + x-enum-varnames: + - BroadcastStatusReceived + - BroadcastStatusSuccess + - BroadcastStatusError ErrorResponse: type: object properties: @@ -568,6 +796,13 @@ components: minLength: 1 required: - error + BroadcastID: + type: string + minLength: 1 + x-go-type: uuid.UUID + x-go-type-import: + path: github.com/google/uuid + name: uuid Address: type: string minLength: 1 diff --git a/libs/common/src/contracts/entities/gateway-events.ts b/libs/common/src/contracts/entities/gateway-events.ts index 9a39c03..215a51c 100644 --- a/libs/common/src/contracts/entities/gateway-events.ts +++ b/libs/common/src/contracts/entities/gateway-events.ts @@ -24,7 +24,9 @@ export interface MessageExecutedEvent { messageId: string; } -export interface WeightedSigners { +export interface SignersRotatedEvent { + epoch: BigNumber; + signersHash: string; signers: { signer: string, // ed25519 public key weight: BigNumber, diff --git a/libs/common/src/contracts/gateway.contract.spec.ts b/libs/common/src/contracts/gateway.contract.spec.ts index fc24a8c..0d05ce4 100644 --- a/libs/common/src/contracts/gateway.contract.spec.ts +++ b/libs/common/src/contracts/gateway.contract.spec.ts @@ -152,6 +152,8 @@ describe('GatewayContract', () => { it('Should decode event', () => { const result = contract.decodeSignersRotatedEvent(event); + expect(result.epoch).toEqual(new BigNumber('1')); + expect(result.signersHash).toEqual('0c38359b7a35c755573659d797afec315bb0e51374a056745abd9764715a15da'); expect(result.signers).toEqual([ { signer: '0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1', weight: new BigNumber('1') }, { signer: '8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8', weight: new BigNumber('1') }, diff --git a/libs/common/src/contracts/gateway.contract.ts b/libs/common/src/contracts/gateway.contract.ts index 25a4cc3..4929f81 100644 --- a/libs/common/src/contracts/gateway.contract.ts +++ b/libs/common/src/contracts/gateway.contract.ts @@ -11,7 +11,7 @@ import { Events } from '../utils/event.enum'; import { ContractCallEvent, MessageApprovedEvent, MessageExecutedEvent, - WeightedSigners, + SignersRotatedEvent, } from '@mvx-monorepo/common/contracts/entities/gateway-events'; import { DecodingUtils } from '@mvx-monorepo/common/utils/decoding.utils'; @@ -72,13 +72,15 @@ export class GatewayContract { }; } - decodeSignersRotatedEvent(event: ITransactionEvent): WeightedSigners { + decodeSignersRotatedEvent(event: ITransactionEvent): SignersRotatedEvent { const eventDefinition = this.abi.getEvent(Events.SIGNERS_ROTATED_EVENT); const outcome = DecodingUtils.parseTransactionEvent(event, eventDefinition); const signers = outcome.signers; return { + epoch: outcome.epoch, + signersHash: DecodingUtils.decodeByteArrayToHex(outcome.signers_hash), signers: signers.signers.map((signer: any) => ({ signer: DecodingUtils.decodeByteArrayToHex(signer.signer), weight: signer.weight,