From f5ec1c4aad532591fcb43d0685e4b1105a288416 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:52:52 +0200 Subject: [PATCH 1/5] Fix gas limit and its execute. --- .../approvals.processor.service.ts | 2 +- .../message-approved.processor.service.ts | 12 +++-- .../message-approved.processor.e2e-spec.ts | 51 ++++++++++++++++++- libs/common/src/contracts/fee.helper.spec.ts | 10 ++++ libs/common/src/contracts/fee.helper.ts | 11 +++- libs/common/src/contracts/its.contract.ts | 16 +++++- 6 files changed, 93 insertions(+), 9 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 19d0723..b9cda99 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 @@ -193,7 +193,7 @@ export class ApprovalsProcessorService { // In case the gas estimation fails, the transaction will fail on chain, but we will still send it // for transparency if (e instanceof GasError) { - this.logger.warn('Could not estimate gas for Gateway transaction...'); + this.logger.warn('Could not estimate gas for Gateway transaction...', e); transaction.setGasLimit(GasInfo.GatewayDefault.value); } else { 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 68ad7e2..6e1b378 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 @@ -172,12 +172,18 @@ export class MessageApprovedProcessorService { // In case the gas estimation fails, the transaction will fail on chain, but we will still send it // for transparency with the full gas available, but don't try to retry it if (e instanceof GasError) { - this.logger.warn('Could not estimate gas for execute transaction...'); + const gasLimit = this.feeHelper.getGasLimitFromEgldFee( + BigInt(messageApproved.availableGasBalance), + transaction.getData(), + ); - transaction.setGasLimit( - this.feeHelper.getGasLimitFromEgldFee(BigInt(messageApproved.availableGasBalance), transaction.getData()), + this.logger.warn( + `Could not estimate gas for execute transaction... Sending transaction with max gas limit ${gasLimit}`, + e, ); + transaction.setGasLimit(gasLimit); + messageApproved.retry = MAX_NUMBER_OF_RETRIES - 1; } else { throw e; 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 c632a4f..47f67d6 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 @@ -261,7 +261,6 @@ describe('MessageApprovedProcessorService', () => { updatedAt: expect.any(Date), }); - expect(axelarGmpApi.postEvents).toHaveBeenCalledTimes(1); // @ts-ignore expect(axelarGmpApi.postEvents.mock.lastCall[0][0]).toEqual({ @@ -426,7 +425,7 @@ describe('MessageApprovedProcessorService', () => { details: 'retried 3 times', meta: { txID: null, - taskItemID: "", + taskItemID: '', }, }); }); @@ -683,6 +682,54 @@ describe('MessageApprovedProcessorService', () => { }); }); + it('Should send execute transaction deploy interchain token ITS Hub payload', async () => { + const originalItsExecute = await createMessageApproved({ + contractAddress, + sourceChain: 'polygon', + sourceAddress: 'otherSourceAddress', + payload: Buffer.from( + AbiCoder.defaultAbiCoder() + .encode( + ['uint256', 'string', 'bytes'], + [4, 'ethereum', AbiCoder.defaultAbiCoder().encode(['uint256'], [1])], + ) + .substring(2), + 'hex', + ), + availableGasBalance: '51200000000000000', // also contains 0.05 EGLD for ESDT issue + executeTxHash: '67b2b814e2ec9bdd08f57073f575ec95d160c76ec9ccd4d14395e7824b6b77cc', + successTimes: 1, + }); + + // Mock 1st transaction executed successfully + transactionWatcher.awaitCompleted.mockReturnValueOnce( + // @ts-ignore + Promise.resolve({ + status: new TransactionStatus('success'), + }), + ); + + mockProxySendTransactionsSuccess(); + + await service.processPendingMessageApproved(); + + const transactions = proxy.sendTransactions.mock.lastCall?.[0] as Transaction[]; + expect(transactions).toHaveLength(1); + expect(transactions[0].getValue()).toBe(50000000000000000n); // assert sent with value + + const itsExecute = (await messageApprovedRepository.findBySourceChainAndMessageId( + originalItsExecute.sourceChain, + originalItsExecute.messageId, + )) as MessageApproved; + expect(itsExecute).toEqual({ + ...originalItsExecute, + retry: 1, + executeTxHash: '73887b17192ededfda3318bc824e0ea0594dd3b7b7e7251dadde36ca8dbaea17', + updatedAt: expect.any(Date), + successTimes: 1, + }); + }); + it('Should send execute transaction deploy interchain token only deploy esdt not enough fee', async () => { const originalItsExecute = await createMessageApproved({ contractAddress, diff --git a/libs/common/src/contracts/fee.helper.spec.ts b/libs/common/src/contracts/fee.helper.spec.ts index b61757d..bae7af2 100644 --- a/libs/common/src/contracts/fee.helper.spec.ts +++ b/libs/common/src/contracts/fee.helper.spec.ts @@ -155,6 +155,16 @@ describe('FeeHelper', () => { ), ), ).toBe(BigInt(71000)); + + // Max gas limit reached + expect( + feeHelper.getGasLimitFromEgldFee( + BigInt(6086216100000000), + TransactionPayload.fromEncoded( + 'c2V0UmVtb3RlVmFsdWU=', + ), + ), + ).toBe(BigInt(600_000_000n)); }); it('getEgldFeeFromGasLimit', () => { diff --git a/libs/common/src/contracts/fee.helper.ts b/libs/common/src/contracts/fee.helper.ts index 4b54864..25ddb40 100644 --- a/libs/common/src/contracts/fee.helper.ts +++ b/libs/common/src/contracts/fee.helper.ts @@ -5,6 +5,8 @@ import { ITransactionPayload, ITransactionValue } from '@multiversx/sdk-core/out import { MessageApproved } from '@prisma/client'; import { NotEnoughGasError } from '@mvx-monorepo/common/contracts/entities/gas.error'; +const MAX_GAS_LIMIT = 600_000_000n; + @Injectable() export class FeeHelper implements OnModuleInit { private readonly logger: Logger; @@ -71,7 +73,14 @@ export class FeeHelper implements OnModuleInit { return gasLimit1; } - return ((availableGasBalance - gasLimit1 * this.minGasPrice) * this.gasPriceModifierInverted) / this.minGasPrice; + const gasLimit = + ((availableGasBalance - gasLimit1 * this.minGasPrice) * this.gasPriceModifierInverted) / this.minGasPrice; + + if (gasLimit > MAX_GAS_LIMIT) { + return MAX_GAS_LIMIT; + } + + return gasLimit; } public getEgldFeeFromGasLimit(gasLimit2: bigint, dataLength: bigint): bigint { diff --git a/libs/common/src/contracts/its.contract.ts b/libs/common/src/contracts/its.contract.ts index 46f52b1..84594df 100644 --- a/libs/common/src/contracts/its.contract.ts +++ b/libs/common/src/contracts/its.contract.ts @@ -9,6 +9,7 @@ import { } from '@mvx-monorepo/common/contracts/entities/its-events'; const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1; +const MESSAGE_TYPE_RECEIVE_FROM_HUB = 4; const DEFAULT_ESDT_ISSUE_COST = '50000000000000000'; // 0.05 EGLD @@ -39,9 +40,20 @@ export class ItsContract { } private decodeExecutePayloadMessageType(payload: Buffer): number { - const result = AbiCoder.defaultAbiCoder().decode(['uint256'], payload); + let result = AbiCoder.defaultAbiCoder().decode(['uint256'], payload); - return Number(result[0]); + const originalMessageType = Number(result[0]); + + if (originalMessageType !== MESSAGE_TYPE_RECEIVE_FROM_HUB) { + return originalMessageType; + } + + result = AbiCoder.defaultAbiCoder().decode(['uint256', 'string', 'bytes'], payload); + + const originalPayload = result[2]; + const newResult = AbiCoder.defaultAbiCoder().decode(['uint256'], originalPayload); + + return Number(newResult[0]); } decodeInterchainTokenDeploymentStartedEvent(event: ITransactionEvent): InterchainTokenDeploymentStartedEvent { From 19a1f26ec31bfeda9db6d2773c03d7fb500a9174 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:53:43 +0200 Subject: [PATCH 2/5] Small env update. --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index b157118..ae3c5fb 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ EVENTS_NOTIFIER_QUEUE=queue CONTRACT_GATEWAY=erd1qqqqqqqqqqqqqpgqvaj9w3g006wgj7avhen3au27datwgk63kklszcamgw CONTRACT_GAS_SERVICE=erd1qqqqqqqqqqqqqpgqvr340nazahl2r0q7dnqvyakuk909yjvukklsuzx4ar -CONTRACT_ITS=erd1qqqqqqqqqqqqqpgqcv3rhjjrqpl88es4q25lw03hfhpw6s36kklsn6t9a6 +CONTRACT_ITS=erd1qqqqqqqqqqqqqpgqc5ypvy2d6z52fwscsfnrwcdkdh2fnthfkkls7kcn9j CONTRACT_WEGLD_SWAP=erd1qqqqqqqqqqqqqpgqpv09kfzry5y4sj05udcngesat07umyj70n4sa2c0rp From 6d5c9f6340236ad623f912fabc5d20d8253ebb36 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:30:08 +0200 Subject: [PATCH 3/5] Fix gas error because of nonce management. --- .../message-approved.processor.service.ts | 19 +++++++++++++++++-- .../src/contracts/transactions.helper.ts | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) 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 6e1b378..741b635 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 @@ -68,6 +68,9 @@ export class MessageApprovedProcessorService { const transactionsToSend: Transaction[] = []; const entriesToUpdate: MessageApproved[] = []; const entriesWithTransactions: MessageApproved[] = []; + + const firstTransactionAccountNonce = accountNonce; + for (const messageApproved of entries) { if (messageApproved.retry >= MAX_NUMBER_OF_RETRIES) { await this.handleMessageApprovedFailed(messageApproved); @@ -94,7 +97,11 @@ export class MessageApprovedProcessorService { } try { - const transaction = await this.buildAndSignExecuteTransaction(messageApproved, accountNonce); + const transaction = await this.buildAndSignExecuteTransaction( + messageApproved, + accountNonce, + firstTransactionAccountNonce, + ); accountNonce++; @@ -135,6 +142,9 @@ export class MessageApprovedProcessorService { if (!sent) { entry.executeTxHash = null; entry.retry = entry.retry === 1 ? 1 : entry.retry - 1; // retry should be 1 or more to not be processed immediately + + // re-retrieve account nonce in case not all transactions were succesfully sent + accountNonce = null; } entriesToUpdate.push(entry); @@ -154,17 +164,19 @@ export class MessageApprovedProcessorService { private async buildAndSignExecuteTransaction( messageApproved: MessageApproved, accountNonce: number, + firstTransactionAccountNonce: number, ): Promise { const interaction = await this.buildExecuteInteraction(messageApproved); const transaction = interaction .withSender(this.walletSigner.getAddress()) - .withNonce(accountNonce) + .withNonce(firstTransactionAccountNonce) // Always estimate gas with first transaction account nonce .withChainID(this.chainId) .buildTransaction(); try { const gas = await this.transactionsHelper.getTransactionGas(transaction, messageApproved.retry); + transaction.setGasLimit(gas); this.feeHelper.checkGasCost(gas, transaction.getValue(), transaction.getData(), messageApproved); @@ -184,12 +196,15 @@ export class MessageApprovedProcessorService { transaction.setGasLimit(gasLimit); + // Set retry to last retry (will be incremented by +1 in processPendingMessageApproved) messageApproved.retry = MAX_NUMBER_OF_RETRIES - 1; } else { throw e; } } + transaction.setNonce(accountNonce); // Set correct nonce after gas estimation + const signature = await this.walletSigner.sign(transaction.serializeForSigning()); transaction.applySignature(signature); diff --git a/libs/common/src/contracts/transactions.helper.ts b/libs/common/src/contracts/transactions.helper.ts index 325151a..78895e4 100644 --- a/libs/common/src/contracts/transactions.helper.ts +++ b/libs/common/src/contracts/transactions.helper.ts @@ -106,4 +106,6 @@ export class TransactionsHelper { return false; } } + + } From 88defb983b3b9e77630c04482c2b4777a7db6408 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:19:22 +0200 Subject: [PATCH 4/5] Fix redis atomocity operations. --- .../approvals.processor.service.ts | 18 ++-- .../approvals.processor.spec.ts | 101 ++++++++---------- .../message-approved.processor.service.ts | 2 +- libs/common/src/helpers/redis.helper.ts | 29 +++++ 4 files changed, 82 insertions(+), 68 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 b9cda99..ad0f967 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 @@ -2,7 +2,6 @@ import { BinaryUtils, Locker } from '@multiversx/sdk-nestjs-common'; import { Inject, Injectable, Logger } from '@nestjs/common'; import { Cron } from '@nestjs/schedule'; import { AxelarGmpApi } from '@mvx-monorepo/common/api/axelar.gmp.api'; -import { RedisCacheService } from '@multiversx/sdk-nestjs-cache'; import { CacheInfo, GasServiceContract } from '@mvx-monorepo/common'; import { ProviderKeys } from '@mvx-monorepo/common/utils/provider.enum'; import { UserSigner } from '@multiversx/sdk-wallet/out'; @@ -21,6 +20,7 @@ import ExecuteTask = Components.Schemas.ExecuteTask; import RefundTask = Components.Schemas.RefundTask; import { GasError } from '@mvx-monorepo/common/contracts/entities/gas.error'; import { GasInfo } from '@mvx-monorepo/common/utils/gas.info'; +import { RedisHelper } from '@mvx-monorepo/common/helpers/redis.helper'; const MAX_NUMBER_OF_RETRIES = 3; @@ -30,7 +30,7 @@ export class ApprovalsProcessorService { constructor( private readonly axelarGmpApi: AxelarGmpApi, - private readonly redisCacheService: RedisCacheService, + private readonly redisHelper: RedisHelper, @Inject(ProviderKeys.WALLET_SIGNER) private readonly walletSigner: UserSigner, private readonly transactionsHelper: TransactionsHelper, private readonly gatewayContract: GatewayContract, @@ -52,7 +52,7 @@ export class ApprovalsProcessorService { } async handleNewTasksRaw() { - let lastTaskUUID = (await this.redisCacheService.get(CacheInfo.LastTaskUUID().key)) || undefined; + let lastTaskUUID = (await this.redisHelper.get(CacheInfo.LastTaskUUID().key)) || undefined; this.logger.debug(`Trying to process tasks for multiversx starting from id: ${lastTaskUUID}`); @@ -76,7 +76,7 @@ export class ApprovalsProcessorService { lastTaskUUID = task.id; - await this.redisCacheService.set(CacheInfo.LastTaskUUID().key, lastTaskUUID, CacheInfo.LastTaskUUID().ttl); + await this.redisHelper.set(CacheInfo.LastTaskUUID().key, lastTaskUUID, CacheInfo.LastTaskUUID().ttl); } catch (e) { this.logger.error(`Could not process task ${task.id}`, task, e); @@ -95,11 +95,9 @@ export class ApprovalsProcessorService { } async handlePendingTransactionsRaw() { - const keys = await this.redisCacheService.scan(CacheInfo.PendingTransaction('*').key); + const keys = await this.redisHelper.scan(CacheInfo.PendingTransaction('*').key); for (const key of keys) { - const cachedValue = await this.redisCacheService.get(key); - - await this.redisCacheService.delete(key); + const cachedValue = await this.redisHelper.getDel(key); if (cachedValue === undefined) { continue; @@ -129,7 +127,7 @@ export class ApprovalsProcessorService { this.logger.error(e); // Set value back in cache to be retried again (with same retry number if it failed to even be sent to the chain) - await this.redisCacheService.set( + await this.redisHelper.set( CacheInfo.PendingTransaction(txHash).key, { txHash, @@ -203,7 +201,7 @@ export class ApprovalsProcessorService { const txHash = await this.transactionsHelper.signAndSendTransaction(transaction, this.walletSigner); - await this.redisCacheService.set( + await this.redisHelper.set( CacheInfo.PendingTransaction(txHash).key, { 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 bd21720..292b279 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 @@ -4,7 +4,6 @@ import { Test } from '@nestjs/testing'; import { AxelarGmpApi } from '@mvx-monorepo/common/api/axelar.gmp.api'; import { GatewayContract } from '@mvx-monorepo/common/contracts/gateway.contract'; import { ApprovalsProcessorService } from './approvals.processor.service'; -import { RedisCacheService } from '@multiversx/sdk-nestjs-cache'; import { UserSigner } from '@multiversx/sdk-wallet/out'; import { ProviderKeys } from '@mvx-monorepo/common/utils/provider.enum'; import { UserAddress } from '@multiversx/sdk-wallet/out/userAddress'; @@ -15,17 +14,18 @@ import { Components } from '@mvx-monorepo/common/api/entities/axelar.gmp.api'; import { MessageApprovedStatus } from '@prisma/client'; import { AccountOnNetwork, ApiNetworkProvider } from '@multiversx/sdk-network-providers/out'; import BigNumber from 'bignumber.js'; +import { GasError } from '@mvx-monorepo/common/contracts/entities/gas.error'; +import { RedisHelper } from '@mvx-monorepo/common/helpers/redis.helper'; import GatewayTransactionTask = Components.Schemas.GatewayTransactionTask; import TaskItem = Components.Schemas.TaskItem; import RefundTask = Components.Schemas.RefundTask; import ExecuteTask = Components.Schemas.ExecuteTask; -import { GasError } from '@mvx-monorepo/common/contracts/entities/gas.error'; const mockExternalData = BinaryUtils.base64Encode('approveMessages@61726731@61726732'); describe('ApprovalsProcessorService', () => { let axelarGmpApi: DeepMocked; - let redisCacheService: DeepMocked; + let redisHelper: DeepMocked; let walletSigner: DeepMocked; let transactionsHelper: DeepMocked; let gatewayContract: DeepMocked; @@ -37,7 +37,7 @@ describe('ApprovalsProcessorService', () => { beforeEach(async () => { axelarGmpApi = createMock(); - redisCacheService = createMock(); + redisHelper = createMock(); walletSigner = createMock(); transactionsHelper = createMock(); gatewayContract = createMock(); @@ -53,8 +53,8 @@ describe('ApprovalsProcessorService', () => { return axelarGmpApi; } - if (token === RedisCacheService) { - return redisCacheService; + if (token === RedisHelper) { + return redisHelper; } if (token === ProviderKeys.WALLET_SIGNER) { @@ -85,7 +85,7 @@ describe('ApprovalsProcessorService', () => { }) .compile(); - redisCacheService.get.mockImplementation(() => { + redisHelper.get.mockImplementation(() => { return Promise.resolve(undefined); }); @@ -140,7 +140,7 @@ describe('ApprovalsProcessorService', () => { expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', undefined); - expect(redisCacheService.set).toHaveBeenCalledWith( + expect(redisHelper.set).toHaveBeenCalledWith( CacheInfo.LastTaskUUID().key, 'lastUUID1', CacheInfo.LastTaskUUID().ttl, @@ -178,7 +178,7 @@ describe('ApprovalsProcessorService', () => { await service.handleNewTasksRaw(); - expect(redisCacheService.get).toHaveBeenCalledTimes(1); + expect(redisHelper.get).toHaveBeenCalledTimes(1); expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', undefined); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', 'UUID'); @@ -196,8 +196,8 @@ describe('ApprovalsProcessorService', () => { expect(transactionsHelper.signAndSendTransaction).toHaveBeenCalledTimes(1); expect(transactionsHelper.signAndSendTransaction).toHaveBeenCalledWith(transaction, walletSigner); - expect(redisCacheService.set).toHaveBeenCalledTimes(2); - expect(redisCacheService.set).toHaveBeenCalledWith( + expect(redisHelper.set).toHaveBeenCalledTimes(2); + expect(redisHelper.set).toHaveBeenCalledWith( CacheInfo.PendingTransaction('txHash').key, { txHash: 'txHash', @@ -207,14 +207,9 @@ describe('ApprovalsProcessorService', () => { CacheInfo.PendingTransaction('txHash').ttl, ); - expect(redisCacheService.set).toHaveBeenCalledWith( - CacheInfo.LastTaskUUID().key, - 'UUID', - CacheInfo.LastTaskUUID().ttl, - ); + expect(redisHelper.set).toHaveBeenCalledWith(CacheInfo.LastTaskUUID().key, 'UUID', CacheInfo.LastTaskUUID().ttl); }); - it('Should handle gateway tx task error', async () => { axelarGmpApi.getTasks.mockReturnValueOnce( // @ts-ignore @@ -246,7 +241,7 @@ describe('ApprovalsProcessorService', () => { await service.handleNewTasksRaw(); - expect(redisCacheService.get).toHaveBeenCalledTimes(1); + expect(redisHelper.get).toHaveBeenCalledTimes(1); expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', undefined); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', 'UUID'); @@ -264,8 +259,8 @@ describe('ApprovalsProcessorService', () => { expect(transactionsHelper.signAndSendTransaction).toHaveBeenCalledTimes(1); expect(transactionsHelper.signAndSendTransaction).toHaveBeenCalledWith(transaction, walletSigner); - expect(redisCacheService.set).toHaveBeenCalledTimes(2); - expect(redisCacheService.set).toHaveBeenCalledWith( + expect(redisHelper.set).toHaveBeenCalledTimes(2); + expect(redisHelper.set).toHaveBeenCalledWith( CacheInfo.PendingTransaction('txHash').key, { txHash: 'txHash', @@ -275,11 +270,7 @@ describe('ApprovalsProcessorService', () => { CacheInfo.PendingTransaction('txHash').ttl, ); - expect(redisCacheService.set).toHaveBeenCalledWith( - CacheInfo.LastTaskUUID().key, - 'UUID', - CacheInfo.LastTaskUUID().ttl, - ); + expect(redisHelper.set).toHaveBeenCalledWith(CacheInfo.LastTaskUUID().key, 'UUID', CacheInfo.LastTaskUUID().ttl); }); it('Should handle execute task', async () => { @@ -327,7 +318,7 @@ describe('ApprovalsProcessorService', () => { taskItemId: 'UUID', availableGasBalance: '100', }); - expect(redisCacheService.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledTimes(1); }); it('Should handle execute task invalid gas token', async () => { @@ -376,7 +367,7 @@ describe('ApprovalsProcessorService', () => { taskItemId: 'UUID', availableGasBalance: '0', }); - expect(redisCacheService.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledTimes(1); }); it('Should not save last task uuid if error', async () => { @@ -410,17 +401,17 @@ describe('ApprovalsProcessorService', () => { expect(transactionsHelper.getTransactionGas).toHaveBeenCalledTimes(1); expect(transactionsHelper.getTransactionGas).toHaveBeenCalledWith(transaction, 0); - expect(redisCacheService.set).not.toHaveBeenCalled(); + expect(redisHelper.set).not.toHaveBeenCalled(); // Mock lastUUID - redisCacheService.get.mockImplementation(() => { + redisHelper.get.mockImplementation(() => { return Promise.resolve('lastUUID1'); }); // Will start processing tasks from lastUUID1 await service.handleNewTasksRaw(); - expect(redisCacheService.get).toHaveBeenCalledTimes(2); + expect(redisHelper.get).toHaveBeenCalledTimes(2); expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', 'lastUUID1'); }); @@ -430,24 +421,22 @@ describe('ApprovalsProcessorService', () => { it('Should handle undefined', async () => { const key = CacheInfo.PendingTransaction('txHashUndefined').key; - redisCacheService.scan.mockReturnValueOnce(Promise.resolve([key])); - redisCacheService.get.mockReturnValueOnce(Promise.resolve(undefined)); + redisHelper.scan.mockReturnValueOnce(Promise.resolve([key])); + redisHelper.getDel.mockReturnValueOnce(Promise.resolve(undefined)); await service.handlePendingTransactionsRaw(); - expect(redisCacheService.scan).toHaveBeenCalledTimes(1); - expect(redisCacheService.get).toHaveBeenCalledTimes(1); - expect(redisCacheService.get).toHaveBeenCalledWith(key); - expect(redisCacheService.delete).toHaveBeenCalledTimes(1); - expect(redisCacheService.delete).toHaveBeenCalledWith(key); + expect(redisHelper.scan).toHaveBeenCalledTimes(1); + expect(redisHelper.getDel).toHaveBeenCalledTimes(1); + expect(redisHelper.getDel).toHaveBeenCalledWith(key); expect(transactionsHelper.awaitSuccess).not.toHaveBeenCalled(); }); it('Should handle success', async () => { const key = CacheInfo.PendingTransaction('txHashComplete').key; - redisCacheService.scan.mockReturnValueOnce(Promise.resolve([key])); - redisCacheService.get.mockReturnValueOnce( + redisHelper.scan.mockReturnValueOnce(Promise.resolve([key])); + redisHelper.getDel.mockReturnValueOnce( Promise.resolve({ txHash: 'txHashComplete', executeData: mockExternalData, @@ -458,11 +447,9 @@ describe('ApprovalsProcessorService', () => { await service.handlePendingTransactionsRaw(); - expect(redisCacheService.scan).toHaveBeenCalledTimes(1); - expect(redisCacheService.get).toHaveBeenCalledTimes(1); - expect(redisCacheService.get).toHaveBeenCalledWith(key); - expect(redisCacheService.delete).toHaveBeenCalledTimes(1); - expect(redisCacheService.delete).toHaveBeenCalledWith(key); + expect(redisHelper.scan).toHaveBeenCalledTimes(1); + expect(redisHelper.getDel).toHaveBeenCalledTimes(1); + expect(redisHelper.getDel).toHaveBeenCalledWith(key); expect(transactionsHelper.awaitSuccess).toHaveBeenCalledTimes(1); expect(transactionsHelper.awaitSuccess).toHaveBeenCalledWith('txHashComplete'); expect(transactionsHelper.getTransactionGas).not.toHaveBeenCalled(); @@ -472,8 +459,8 @@ describe('ApprovalsProcessorService', () => { const key = CacheInfo.PendingTransaction('txHashComplete').key; const externalData = mockExternalData; - redisCacheService.scan.mockReturnValueOnce(Promise.resolve([key])); - redisCacheService.get.mockReturnValueOnce( + redisHelper.scan.mockReturnValueOnce(Promise.resolve([key])); + redisHelper.getDel.mockReturnValueOnce( Promise.resolve({ txHash: 'txHashComplete', externalData, @@ -509,8 +496,8 @@ describe('ApprovalsProcessorService', () => { expect(transactionsHelper.signAndSendTransaction).toHaveBeenCalledTimes(1); expect(transactionsHelper.signAndSendTransaction).toHaveBeenCalledWith(transaction, walletSigner); - expect(redisCacheService.set).toHaveBeenCalledTimes(1); - expect(redisCacheService.set).toHaveBeenCalledWith( + expect(redisHelper.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledWith( CacheInfo.PendingTransaction('txHash').key, { txHash: 'txHash', @@ -525,8 +512,8 @@ describe('ApprovalsProcessorService', () => { const key = CacheInfo.PendingTransaction('txHashComplete').key; const executeData = Uint8Array.of(1, 2, 3, 4); - redisCacheService.scan.mockReturnValueOnce(Promise.resolve([key])); - redisCacheService.get.mockReturnValueOnce( + redisHelper.scan.mockReturnValueOnce(Promise.resolve([key])); + redisHelper.getDel.mockReturnValueOnce( Promise.resolve({ txHash: 'txHashComplete', executeData, @@ -546,8 +533,8 @@ describe('ApprovalsProcessorService', () => { const key = CacheInfo.PendingTransaction('txHashComplete').key; const externalData = mockExternalData; - redisCacheService.scan.mockReturnValueOnce(Promise.resolve([key])); - redisCacheService.get.mockReturnValueOnce( + redisHelper.scan.mockReturnValueOnce(Promise.resolve([key])); + redisHelper.getDel.mockReturnValueOnce( Promise.resolve({ txHash: 'txHashComplete', externalData, @@ -571,8 +558,8 @@ describe('ApprovalsProcessorService', () => { expect(transactionsHelper.getTransactionGas).toHaveBeenCalledTimes(1); expect(transactionsHelper.getTransactionGas).toHaveBeenCalledWith(transaction, 1); - expect(redisCacheService.set).toHaveBeenCalledTimes(1); - expect(redisCacheService.set).toHaveBeenCalledWith( + expect(redisHelper.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledWith( CacheInfo.PendingTransaction('txHashComplete').key, { txHash: 'txHashComplete', @@ -586,7 +573,7 @@ describe('ApprovalsProcessorService', () => { describe('processRefundTask', () => { function assertRefundSuccess(userAddress: UserAddress, transaction: Transaction, token: string) { - expect(redisCacheService.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledTimes(1); expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', undefined); expect(axelarGmpApi.getTasks).toHaveBeenCalledWith('multiversx', 'UUID'); @@ -751,7 +738,7 @@ describe('ApprovalsProcessorService', () => { expect(api.getAccount).toHaveBeenCalledTimes(1); - expect(redisCacheService.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledTimes(1); expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(gasServiceContract.refund).not.toHaveBeenCalled(); @@ -803,7 +790,7 @@ describe('ApprovalsProcessorService', () => { expect(api.getFungibleTokenOfAccount).toHaveBeenCalledTimes(1); - expect(redisCacheService.set).toHaveBeenCalledTimes(1); + expect(redisHelper.set).toHaveBeenCalledTimes(1); expect(axelarGmpApi.getTasks).toHaveBeenCalledTimes(2); expect(gasServiceContract.refund).not.toHaveBeenCalled(); 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 741b635..b3a4935 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 @@ -143,7 +143,7 @@ export class MessageApprovedProcessorService { entry.executeTxHash = null; entry.retry = entry.retry === 1 ? 1 : entry.retry - 1; // retry should be 1 or more to not be processed immediately - // re-retrieve account nonce in case not all transactions were succesfully sent + // re-retrieve account nonce in case not all transactions were successfully sent accountNonce = null; } diff --git a/libs/common/src/helpers/redis.helper.ts b/libs/common/src/helpers/redis.helper.ts index da7e3ed..99ae2e9 100644 --- a/libs/common/src/helpers/redis.helper.ts +++ b/libs/common/src/helpers/redis.helper.ts @@ -16,6 +16,14 @@ export class RedisHelper { this.logger = new Logger(RedisHelper.name); } + get(key: string): Promise { + return this.redisCache.get(key); + } + + set(key: string, value: T, ttl?: number | null) { + return this.redisCache.set(key, value, ttl); + } + sadd(key: string, ...values: string[]) { return this.redisCache.sadd(key, ...values); } @@ -24,6 +32,10 @@ export class RedisHelper { return this.redisCache.smembers(key); } + scan(key: string) { + return this.redisCache.scan(key); + } + async srem(key: string, ...values: string[]) { const performanceProfiler = new PerformanceProfiler(); try { @@ -46,4 +58,21 @@ export class RedisHelper { this.metricsService.setRedisDuration('SREM', performanceProfiler.duration); } } + + async getDel(key: string): Promise { + try { + const data = await this.redis.getdel(key); + if (data) { + return JSON.parse(data); + } + } catch (error) { + if (error instanceof Error) { + this.logger.error('RedisCache - An error occurred while trying to getdel from redis cache.', { + cacheKey: key, + error: error?.toString(), + }); + } + } + return undefined; + } } From 3d550362e14e2390ab25eed4b8f6d17226b0194f Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:44:16 +0200 Subject: [PATCH 5/5] Fix tasks processing. --- .../src/approvals-processor/approvals.processor.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/axelar-event-processor/src/approvals-processor/approvals.processor.module.ts b/apps/axelar-event-processor/src/approvals-processor/approvals.processor.module.ts index 6d55997..87cace5 100644 --- a/apps/axelar-event-processor/src/approvals-processor/approvals.processor.module.ts +++ b/apps/axelar-event-processor/src/approvals-processor/approvals.processor.module.ts @@ -4,6 +4,7 @@ import { ApprovalsProcessorService } from './approvals.processor.service'; import { ApiModule } from '@mvx-monorepo/common/api/api.module'; import { ContractsModule } from '@mvx-monorepo/common/contracts/contracts.module'; import { ScheduleModule } from '@nestjs/schedule'; +import { HelpersModule } from '@mvx-monorepo/common/helpers/helpers.module'; @Module({ imports: [ @@ -13,6 +14,7 @@ import { ScheduleModule } from '@nestjs/schedule'; ApiModule, ContractsModule, DatabaseModule, + HelpersModule, ], providers: [ApprovalsProcessorService], })