From edcbd0286a908ba44ea767534d057592a6035c9b Mon Sep 17 00:00:00 2001 From: Vasyl Ivanchuk Date: Thu, 31 Oct 2024 11:31:22 +0200 Subject: [PATCH] fix: retryable contract retry functionality (#303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Fix for the retryable contract retry functionality. ## Why ❔ Ethers v6 returns errors differently than Ethers v5, so the function that parses an error and determines whether the request should be retried needs to be fixed. ## Checklist - [X] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [X] Tests for the changes have been added / updated. --- .../src/blockchain/retryableContract.spec.ts | 11 +++----- .../src/blockchain/retryableContract.ts | 26 +++++-------------- .../src/blockchain/retryableContract.spec.ts | 9 ++----- .../src/blockchain/retryableContract.ts | 26 +++++-------------- 4 files changed, 19 insertions(+), 53 deletions(-) diff --git a/packages/data-fetcher/src/blockchain/retryableContract.spec.ts b/packages/data-fetcher/src/blockchain/retryableContract.spec.ts index 248831a4e..3d9447633 100644 --- a/packages/data-fetcher/src/blockchain/retryableContract.spec.ts +++ b/packages/data-fetcher/src/blockchain/retryableContract.spec.ts @@ -100,13 +100,8 @@ describe("RetryableContract", () => { describe("when throws a permanent call exception function error", () => { const callExceptionError = { - code: "CALL_EXCEPTION", - method: "contractFn(address)", - transaction: { - data: "0x00", - to: "to", - }, - message: "call revert exception ....", + code: 3, + shortMessage: "execution reverted...", }; beforeEach(() => { @@ -209,7 +204,7 @@ describe("RetryableContract", () => { describe("when throws a few errors with no method or message before returning a result", () => { const functionResult = "functionResult"; const error = new Error(); - (error as any).code = "CALL_EXCEPTION"; + (error as any).code = 3; beforeEach(() => { let countOfFailedRequests = 0; diff --git a/packages/data-fetcher/src/blockchain/retryableContract.ts b/packages/data-fetcher/src/blockchain/retryableContract.ts index b490aeb54..1e2f23366 100644 --- a/packages/data-fetcher/src/blockchain/retryableContract.ts +++ b/packages/data-fetcher/src/blockchain/retryableContract.ts @@ -1,18 +1,13 @@ import { Logger } from "@nestjs/common"; import { setTimeout } from "timers/promises"; -import { Contract, Interface, ContractRunner, ErrorCode } from "ethers"; +import { Contract, Interface, ContractRunner, ErrorCode, isError } from "ethers"; import config from "../config"; const { blockchain } = config(); interface EthersError { - code: ErrorCode; - method: string; - transaction: { - data: string; - to: string; - }; - message: string; + code: ErrorCode | number; + shortMessage: string; } const MAX_RETRY_INTERVAL = 60000; @@ -24,16 +19,9 @@ const PERMANENT_ERRORS: ErrorCode[] = [ "NOT_IMPLEMENTED", ]; -const shouldRetry = (calledFunctionName: string, error: EthersError): boolean => { - return ( - !PERMANENT_ERRORS.includes(error.code) && - !( - error.code === "CALL_EXCEPTION" && - error.method?.startsWith(`${calledFunctionName}(`) && - !!error.transaction && - error.message?.startsWith("call revert exception") - ) - ); +const shouldRetry = (error: EthersError): boolean => { + const isPermanentErrorCode = PERMANENT_ERRORS.find((errorCode) => isError(error, errorCode)); + return !isPermanentErrorCode && !(error.code === 3 && error.shortMessage?.startsWith("execution reverted")); }; const retryableFunctionCall = async ( @@ -48,7 +36,7 @@ const retryableFunctionCall = async ( try { return await result; } catch (error) { - const isRetryable = shouldRetry(functionName, error); + const isRetryable = shouldRetry(error); if (!isRetryable) { logger.warn({ message: `Requested contract function ${functionName} failed to execute, not retryable`, diff --git a/packages/worker/src/blockchain/retryableContract.spec.ts b/packages/worker/src/blockchain/retryableContract.spec.ts index 8188112b2..a8e78f65a 100644 --- a/packages/worker/src/blockchain/retryableContract.spec.ts +++ b/packages/worker/src/blockchain/retryableContract.spec.ts @@ -92,13 +92,8 @@ describe("RetryableContract", () => { describe("when throws a permanent call exception function error", () => { const callExceptionError = { - code: "CALL_EXCEPTION", - method: "contractFn(address)", - transaction: { - data: "0x00", - to: "to", - }, - message: "call revert exception ....", + code: 3, + shortMessage: "execution reverted ....", }; beforeEach(() => { diff --git a/packages/worker/src/blockchain/retryableContract.ts b/packages/worker/src/blockchain/retryableContract.ts index 409113fe2..e6f398757 100644 --- a/packages/worker/src/blockchain/retryableContract.ts +++ b/packages/worker/src/blockchain/retryableContract.ts @@ -1,15 +1,10 @@ import { Logger } from "@nestjs/common"; import { setTimeout } from "timers/promises"; -import { Contract, Interface, ContractRunner, ErrorCode } from "ethers"; +import { Contract, Interface, ContractRunner, ErrorCode, isError } from "ethers"; interface EthersError { - code: ErrorCode; - method: string; - transaction: { - data: string; - to: string; - }; - message: string; + code: ErrorCode | number; + shortMessage: string; } const MAX_RETRY_INTERVAL = 60000; @@ -21,16 +16,9 @@ const PERMANENT_ERRORS: ErrorCode[] = [ "NOT_IMPLEMENTED", ]; -const shouldRetry = (calledFunctionName: string, error: EthersError): boolean => { - return ( - !PERMANENT_ERRORS.includes(error.code) && - !( - error.code === "CALL_EXCEPTION" && - error.method?.startsWith(`${calledFunctionName}(`) && - !!error.transaction && - error.message?.startsWith("call revert exception") - ) - ); +const shouldRetry = (error: EthersError): boolean => { + const isPermanentErrorCode = PERMANENT_ERRORS.find((errorCode) => isError(error, errorCode)); + return !isPermanentErrorCode && !(error.code === 3 && error.shortMessage?.startsWith("execution reverted")); }; const retryableFunctionCall = async ( @@ -44,7 +32,7 @@ const retryableFunctionCall = async ( try { return await result; } catch (error) { - const isRetryable = shouldRetry(functionName, error); + const isRetryable = shouldRetry(error); logger.warn({ message: `Requested contract function ${functionName} failed to execute, ${ isRetryable ? "retrying..." : "not retryable"