From 9d4a60a865fda9eefe22ba3a3cc815b7462b7b04 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Thu, 12 Oct 2023 15:47:07 +0530 Subject: [PATCH 01/10] Revert error should not be retried --- src/retryOnEmpty.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index f4e717e4..31d0305c 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -140,7 +140,10 @@ async function retry( for (let index = 0; index < maxRetries; index++) { try { return await asyncFn(); - } catch (err) { + } catch (err: any) { + if (err.code === -32000) { + throw err; + } log('(call %i) Request failed, waiting 1s to retry again...', index + 1); await timeout(1000); } From 714d4bf216932205fd2141a8d46becf57a059cf1 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 16 Oct 2023 18:49:01 +0530 Subject: [PATCH 02/10] Update --- src/retryOnEmpty.test.ts | 49 ++++++++++++++++++++++++++++++++++++++-- src/retryOnEmpty.ts | 5 +++- src/utils/error.test.ts | 36 +++++++++++++++++++++++++++++ src/utils/error.ts | 13 +++++++++++ test/util/helpers.ts | 3 ++- 5 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/utils/error.test.ts create mode 100644 src/utils/error.ts diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index cbda6489..dbccb2db 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -1,3 +1,4 @@ +import { errorCodes } from '@metamask/rpc-errors'; import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; @@ -203,8 +204,8 @@ describe('createRetryOnEmptyMiddleware', () => { id: req.id, jsonrpc: '2.0', error: { - code: -1, - message: 'oops', + code: errorCodes.rpc.invalidInput, + message: 'execution reverted', }, }; }, @@ -627,6 +628,50 @@ describe('createRetryOnEmptyMiddleware', () => { ); }); }); + + describe('when provider return execution revert error', () => { + it('returns the same error to caller', async () => { + await withTestSetup( + { + configureMiddleware: ({ provider, blockTracker }) => { + return { + middlewareUnderTest: createRetryOnEmptyMiddleware({ + provider, + blockTracker, + }), + }; + }, + }, + async ({ engine, provider }) => { + const request: JsonRpcRequest = { + id: 123, + jsonrpc: '2.0', + method: 'eth_call', + params: buildMockParamsWithoutBlockParamAt(2, '100'), + }; + stubProviderRequests(provider, [ + buildStubForBlockNumberRequest(), + { + request, + response: () => { + throw { + code: errorCodes.rpc.invalidInput, + message: 'execution reverted', + } as any; + }, + }, + ]); + const promiseForResponse = engine.handle(request); + expect(await promiseForResponse).toMatchObject({ + error: expect.objectContaining({ + code: errorCodes.rpc.invalidInput, + message: 'execution reverted', + }), + }); + }, + ); + }); + }); }); /** diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index 31d0305c..df6615bb 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -13,6 +13,7 @@ import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { Block } from './types'; import { blockTagParamIndex } from './utils/cache'; +import { isExecutionRevertedError } from './utils/error'; import { timeout } from './utils/timeout'; // @@ -77,6 +78,7 @@ export function createRetryOnEmptyMiddleware({ if (Number.isNaN(blockRefNumber)) { return next(); } + // lookup latest block const latestBlockNumberHex: string = await blockTracker.getLatestBlock(); const latestBlockNumber: number = Number.parseInt( @@ -102,6 +104,7 @@ export function createRetryOnEmptyMiddleware({ // create child request with specific block-ref const childRequest = klona(req); // attempt child request until non-empty response is received + const childResponse: PendingJsonRpcResponse = await retry( 10, async () => { @@ -141,7 +144,7 @@ async function retry( try { return await asyncFn(); } catch (err: any) { - if (err.code === -32000) { + if (isExecutionRevertedError(err)) { throw err; } log('(call %i) Request failed, waiting 1s to retry again...', index + 1); diff --git a/src/utils/error.test.ts b/src/utils/error.test.ts new file mode 100644 index 00000000..49bc5cd9 --- /dev/null +++ b/src/utils/error.test.ts @@ -0,0 +1,36 @@ +import { errorCodes } from '@metamask/rpc-errors'; + +import { isExecutionRevertedError } from './error'; + +const executionRevertedError = { + code: errorCodes.rpc.invalidInput, + message: 'execution reverted', +}; + +describe('error', () => { + it('return false if object is not valid JSON RPC error', async () => { + const result = isExecutionRevertedError({ test: 'dummy' }); + expect(result).toBe(false); + }); + + it('return false if error code is not same as errorCodes.rpc.invalidInput', async () => { + const result = isExecutionRevertedError({ + ...executionRevertedError, + code: 123, + }); + expect(result).toBe(false); + }); + + it('return false if error message is not "execution reverted"', async () => { + const result = isExecutionRevertedError({ + ...executionRevertedError, + message: 'test', + }); + expect(result).toBe(false); + }); + + it('return true for correct executionRevertedError', async () => { + const result = isExecutionRevertedError(executionRevertedError); + expect(result).toBe(true); + }); +}); diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 00000000..81a92d7c --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,13 @@ +import { isJsonRpcError } from '@metamask/utils'; +import type { JsonRpcError } from '@metamask/utils'; +import { errorCodes } from '@metamask/rpc-errors'; + +export function isExecutionRevertedError( + error: unknown, +): error is JsonRpcError { + return ( + isJsonRpcError(error) && + error.code === errorCodes.rpc.invalidInput && + error.message === 'execution reverted' + ); +} diff --git a/test/util/helpers.ts b/test/util/helpers.ts index f6380304..ec56ef8f 100644 --- a/test/util/helpers.ts +++ b/test/util/helpers.ts @@ -121,11 +121,12 @@ export function buildMockParamsWithBlockParamAt( */ export function buildMockParamsWithoutBlockParamAt( blockParamIndex: number, + value?: string, ): string[] { const params = []; for (let i = 0; i < blockParamIndex; i++) { - params.push('some value'); + params.push(value ?? 'some value'); } return params; From 0707f7a70d9c33adefb3ce75b692e41334efb269 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 16 Oct 2023 19:15:06 +0530 Subject: [PATCH 03/10] Update --- src/retryOnEmpty.test.ts | 2 +- src/utils/error.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index dbccb2db..b6287ed8 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -1,8 +1,8 @@ -import { errorCodes } from '@metamask/rpc-errors'; import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { errorCodes } from '@metamask/rpc-errors'; import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { PollingBlockTracker } from 'eth-block-tracker'; diff --git a/src/utils/error.ts b/src/utils/error.ts index 81a92d7c..dbc693c0 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -1,6 +1,6 @@ +import { errorCodes } from '@metamask/rpc-errors'; import { isJsonRpcError } from '@metamask/utils'; import type { JsonRpcError } from '@metamask/utils'; -import { errorCodes } from '@metamask/rpc-errors'; export function isExecutionRevertedError( error: unknown, From 8dddea79b01f2d6f6dc904b604ab5e2bfa0d5438 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 16 Oct 2023 19:40:17 +0530 Subject: [PATCH 04/10] Update --- src/retryOnEmpty.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index df6615bb..97a05507 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -145,7 +145,7 @@ async function retry( return await asyncFn(); } catch (err: any) { if (isExecutionRevertedError(err)) { - throw err; + throw err as unknown; } log('(call %i) Request failed, waiting 1s to retry again...', index + 1); await timeout(1000); From 30dc04cfe5622544d8bf0f63b14c8d3a88568e26 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 16 Oct 2023 19:42:33 +0530 Subject: [PATCH 05/10] Update --- src/retryOnEmpty.test.ts | 4 ++-- src/retryOnEmpty.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index b6287ed8..7b9b9fae 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -204,8 +204,8 @@ describe('createRetryOnEmptyMiddleware', () => { id: req.id, jsonrpc: '2.0', error: { - code: errorCodes.rpc.invalidInput, - message: 'execution reverted', + code: -1, + message: 'oops', }, }; }, diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index 97a05507..aed82126 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -78,7 +78,6 @@ export function createRetryOnEmptyMiddleware({ if (Number.isNaN(blockRefNumber)) { return next(); } - // lookup latest block const latestBlockNumberHex: string = await blockTracker.getLatestBlock(); const latestBlockNumber: number = Number.parseInt( @@ -104,7 +103,6 @@ export function createRetryOnEmptyMiddleware({ // create child request with specific block-ref const childRequest = klona(req); // attempt child request until non-empty response is received - const childResponse: PendingJsonRpcResponse = await retry( 10, async () => { From 4df7877f4e205827ea8eb80ec78977ecb4da86cb Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 17 Oct 2023 13:54:37 +0530 Subject: [PATCH 06/10] Update src/retryOnEmpty.test.ts Co-authored-by: Elliot Winkler --- src/retryOnEmpty.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index 7b9b9fae..51b3c885 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -654,10 +654,7 @@ describe('createRetryOnEmptyMiddleware', () => { { request, response: () => { - throw { - code: errorCodes.rpc.invalidInput, - message: 'execution reverted', - } as any; + throw rpcErrors.invalidInput('execution reverted'); }, }, ]); From dcf5ab3248cfe105c9159570c04cd7053ca39af6 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 17 Oct 2023 13:54:57 +0530 Subject: [PATCH 07/10] Update src/retryOnEmpty.ts Co-authored-by: Elliot Winkler --- src/retryOnEmpty.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index aed82126..e6060cc7 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -141,9 +141,9 @@ async function retry( for (let index = 0; index < maxRetries; index++) { try { return await asyncFn(); - } catch (err: any) { + } catch (err: unknown) { if (isExecutionRevertedError(err)) { - throw err as unknown; + throw err; } log('(call %i) Request failed, waiting 1s to retry again...', index + 1); await timeout(1000); From 94141beb4c42b7cd47a45f68aeb5ddf9e0d6a2ff Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 17 Oct 2023 13:55:07 +0530 Subject: [PATCH 08/10] Update src/utils/error.test.ts Co-authored-by: Elliot Winkler --- src/utils/error.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/error.test.ts b/src/utils/error.test.ts index 49bc5cd9..d51eca67 100644 --- a/src/utils/error.test.ts +++ b/src/utils/error.test.ts @@ -7,7 +7,7 @@ const executionRevertedError = { message: 'execution reverted', }; -describe('error', () => { +describe('isExecutionRevertedError', () => { it('return false if object is not valid JSON RPC error', async () => { const result = isExecutionRevertedError({ test: 'dummy' }); expect(result).toBe(false); From 323bdb7a2699ebea7b665607c06d5a5dc48bdae5 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Tue, 17 Oct 2023 15:28:14 +0530 Subject: [PATCH 09/10] Update --- src/retryOnEmpty.test.ts | 4 ++-- src/retryOnEmpty.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index 51b3c885..b219b23c 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -2,7 +2,7 @@ import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; -import { errorCodes } from '@metamask/rpc-errors'; +import { errorCodes, rpcErrors } from '@metamask/rpc-errors'; import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { PollingBlockTracker } from 'eth-block-tracker'; @@ -647,7 +647,7 @@ describe('createRetryOnEmptyMiddleware', () => { id: 123, jsonrpc: '2.0', method: 'eth_call', - params: buildMockParamsWithoutBlockParamAt(2, '100'), + params: buildMockParamsWithBlockParamAt(1, '100'), }; stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index e6060cc7..b777f8c2 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -143,7 +143,7 @@ async function retry( return await asyncFn(); } catch (err: unknown) { if (isExecutionRevertedError(err)) { - throw err; + throw err as unknown; } log('(call %i) Request failed, waiting 1s to retry again...', index + 1); await timeout(1000); From 03bd3d03d5d303ac21d58e39009777a242a5b5d4 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Fri, 20 Oct 2023 14:20:03 +0530 Subject: [PATCH 10/10] Update --- test/util/helpers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/util/helpers.ts b/test/util/helpers.ts index ec56ef8f..f6380304 100644 --- a/test/util/helpers.ts +++ b/test/util/helpers.ts @@ -121,12 +121,11 @@ export function buildMockParamsWithBlockParamAt( */ export function buildMockParamsWithoutBlockParamAt( blockParamIndex: number, - value?: string, ): string[] { const params = []; for (let i = 0; i < blockParamIndex; i++) { - params.push(value ?? 'some value'); + params.push('some value'); } return params;