From 974c05de18fb95a1ab9679d382b5684c8db69b30 Mon Sep 17 00:00:00 2001 From: Kris Kaczor Date: Sun, 29 Oct 2023 23:54:45 +0400 Subject: [PATCH] Retry etherscan calls (#86) --- packages/ethereum-viewer/package.json | 1 + .../src/explorer/fetchFiles.ts | 14 +++++- .../ethereum-viewer/src/openContractSource.ts | 3 +- .../ethereum-viewer/src/util/solidFetch.ts | 45 +++++++++++++++++++ pnpm-lock.yaml | 7 +++ 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 packages/ethereum-viewer/src/util/solidFetch.ts diff --git a/packages/ethereum-viewer/package.json b/packages/ethereum-viewer/package.json index f0c72af..bc2746c 100644 --- a/packages/ethereum-viewer/package.json +++ b/packages/ethereum-viewer/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "fast-json-stable-stringify": "^2.1.0", + "fetch-retry": "^5.0.6", "match-sorter": "^6.3.1", "p-finally": "^2.0.1", "path-browserify": "^1.0.1", diff --git a/packages/ethereum-viewer/src/explorer/fetchFiles.ts b/packages/ethereum-viewer/src/explorer/fetchFiles.ts index bc22b2e..cc27921 100644 --- a/packages/ethereum-viewer/src/explorer/fetchFiles.ts +++ b/packages/ethereum-viewer/src/explorer/fetchFiles.ts @@ -2,12 +2,20 @@ import { join } from "path"; import { assert, StrictOmit } from "ts-essentials"; import { fetch as _fetch } from "../util/fetch"; +import { makeSolidFetch } from "../util/solidFetch"; import { prettyStringify } from "../util/stringify"; import * as types from "./api-types"; import { apiUrlToWebsite } from "./apiUrlToWebsite"; import { fileExtension } from "./fileExtension"; import { ApiName, explorerApiKeys, explorerApiUrls } from "./networks"; +const fetchEtherscanResponse = makeSolidFetch({ + async verifyResponse(response: unknown): Promise { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (response as any)?.message === "OK" || false; + }, +}); + interface FetchFilesOptions { /** * For unit testing. @@ -27,7 +35,11 @@ interface FetchFilesOptions { export async function fetchFiles( apiName: ApiName, contractAddress: string, - { fetch = _fetch, proxyDepth = 3, skipPrefix = false }: FetchFilesOptions = {} + { + fetch = fetchEtherscanResponse, + proxyDepth = 3, + skipPrefix = false, + }: FetchFilesOptions = {} ): Promise { const apiUrl = explorerApiUrls[apiName]; const url = diff --git a/packages/ethereum-viewer/src/openContractSource.ts b/packages/ethereum-viewer/src/openContractSource.ts index 67d0b69..aba4869 100644 --- a/packages/ethereum-viewer/src/openContractSource.ts +++ b/packages/ethereum-viewer/src/openContractSource.ts @@ -118,6 +118,7 @@ async function saveSingleContractFilesToFs( return { entries, mainFile: withPrefix(mainFile), + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition contractName: result.info.ContractName ?? "contract", }; } @@ -133,7 +134,7 @@ function getMainContractFile( if (!fileToShow) { const regexp = new RegExp(`contract\\s+${name}`); - fileToShow = files.find(([path, source]) => regexp.test(source)); + fileToShow = files.find(([_path, source]) => regexp.test(source)); } if (!fileToShow) fileToShow = files.sort(byPathLength)[0]; diff --git a/packages/ethereum-viewer/src/util/solidFetch.ts b/packages/ethereum-viewer/src/util/solidFetch.ts new file mode 100644 index 0000000..6b208e3 --- /dev/null +++ b/packages/ethereum-viewer/src/util/solidFetch.ts @@ -0,0 +1,45 @@ +import fetchRetry from "fetch-retry"; + +interface MakeSolidFetchOptions { + /** + * return false if the response should be retried + */ + verifyResponse: (response: unknown) => Promise; +} + +// @note: returns already parsed response +type SolidFetch = (input: string, init?: RequestInit) => Promise; + +export function makeSolidFetch(opts: MakeSolidFetchOptions): SolidFetch { + const solidFetch = fetchRetry(self.fetch, { + retries: 3, + async retryOn(_attempt, error, response) { + const retry = error !== null || !response?.ok; + if (retry) { + // eslint-disable-next-line no-console + console.log("Retrying failed fetch", { + error, + status: response?.status, + }); + } + // if we have a response, we verify it and decide if we should retry + if (!retry) { + const responseJson = await response.clone().json(); + const verified = await opts.verifyResponse(responseJson); + if (!verified) { + console.log("Response verification failed, retrying..."); + } + + return !verified; + } + + return true; + }, + retryDelay: function (attempt) { + return Math.pow(2, attempt) * 1000; + }, + }); + + return (input: string, init?: RequestInit) => + solidFetch(input, init).then((response) => response.json()); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9b4177..7f48148 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: fast-json-stable-stringify: specifier: ^2.1.0 version: 2.1.0 + fetch-retry: + specifier: ^5.0.6 + version: 5.0.6 match-sorter: specifier: ^6.3.1 version: 6.3.1 @@ -2077,6 +2080,10 @@ packages: bser: 2.1.1 dev: true + /fetch-retry@5.0.6: + resolution: {integrity: sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ==} + dev: false + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0}