diff --git a/src/indexer/indexerFillupBlocks.ts b/src/indexer/indexerFillupBlocks.ts index 455e254..c524875 100644 --- a/src/indexer/indexerFillupBlocks.ts +++ b/src/indexer/indexerFillupBlocks.ts @@ -277,7 +277,7 @@ export const FillUpBlocks = async () => { } catch (error) { logger.error('fillUp error:', error); - throw error; // Let the BackoffRetry handle the retry + throw error; } }; diff --git a/src/restRpc/ext/CoinGeko/CoinGekoCache.ts b/src/restRpc/ext/CoinGeko/CoinGekoCache.ts index 10fcdec..81364f3 100644 --- a/src/restRpc/ext/CoinGeko/CoinGekoCache.ts +++ b/src/restRpc/ext/CoinGeko/CoinGekoCache.ts @@ -59,15 +59,7 @@ class CoinGekoCacheClass { for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { - const data = await FetchRestData( - url, - {}, - false, - 3, // retries - 2, // backoff - 1200, // timeout - 5000 // delay - ); + const data = await FetchRestData(url); return data; // Return the fetched data if successful } catch (error: unknown) { const errorMessage = (error instanceof Error) ? error.message : 'Unknown error'; diff --git a/src/restRpc/fetch.ts b/src/restRpc/fetch.ts index fcc5d0b..ea72fd9 100644 --- a/src/restRpc/fetch.ts +++ b/src/restRpc/fetch.ts @@ -1,98 +1,75 @@ import { logger } from "@jsinfo/utils/logger"; -import { BackoffRetry } from "@jsinfo/utils/retry"; +import axios, { AxiosRequestConfig } from "axios"; const activeFetches: Record> = {}; -const RATE_LIMIT_DELAY = 60000; // 1 minute in milliseconds -const rateDelayCache = new Map(); -export async function FetchRestData( +const HTTP_RETRY_CODES = { + 429: { delay: 60000, message: 'Rate Limited' }, + 500: { delay: 5000, message: 'Internal Server Error' }, + 502: { delay: 5000, message: 'Bad Gateway' }, + 503: { delay: 5000, message: 'Service Unavailable' }, + 504: { delay: 5000, message: 'Gateway Timeout' } +}; + +async function doFetch( url: string, - options: RequestInit = {}, - skipBackoff: boolean = false, - retries: number = 8, - factor: number = 2, - minTimeout: number = 1000, - maxTimeout: number = 5000 + options: AxiosRequestConfig = {}, + maxRetries: number = 8, + retryDelay: number = 500, + timeout: number = 30000 ): Promise { - // Check if we need to wait due to previous rate limit - const lastRateLimit = rateDelayCache.get(url); - if (lastRateLimit) { - const timeToWait = lastRateLimit - Date.now(); - if (timeToWait > 0) { - logger.info(`Rate limit cooling down for URL: ${url}, waiting ${timeToWait}ms`); - await new Promise(resolve => setTimeout(resolve, timeToWait)); - } - rateDelayCache.delete(url); - } - - if (url in activeFetches) { - return activeFetches[url] as Promise; - } + let attempt = 0; - const fetchFunc = async () => { + while (attempt < maxRetries) { try { - const maxRetries = 3; // Define the maximum number of retries - let attempt = 0; // Initialize the attempt counter - - while (attempt < maxRetries) { - try { - const lastRateLimit = rateDelayCache.get(url); - if (lastRateLimit) { - const timeToWait = lastRateLimit - Date.now(); - if (timeToWait > 0) { - logger.info(`Rate limit cooling down for URL: ${url}, waiting ${timeToWait}ms`); - await new Promise(resolve => setTimeout(resolve, timeToWait)); - } - rateDelayCache.delete(url); - } - - const response = await fetch(url, options); - - // Handle rate limit (429) specifically - if (response.status === 429) { - logger.warn(`Rate limit hit for ${url}, waiting 60 seconds before retry`); - rateDelayCache.set(url, Date.now() + RATE_LIMIT_DELAY); - await new Promise(resolve => setTimeout(resolve, RATE_LIMIT_DELAY)); - attempt++; // Increment the attempt counter - logger.info(`Retrying fetch for ${url} (Attempt ${attempt})...`); - continue; // Retry the fetch - } - - // Check for other response errors - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + const response = await axios({ + url, + timeout, + validateStatus: null, + ...options + }); - return await response.json() as T; // Return the JSON response if successful - } catch (error) { - logger.error(`Error fetching data from ${url}:`, error); - if (attempt === maxRetries - 1) { - throw error; // Rethrow the error if max retries reached - } - attempt++; // Increment the attempt counter - logger.info(`Retrying fetch for ${url} (Attempt ${attempt})...`); - await new Promise(resolve => setTimeout(resolve, 1000)); // Wait before retrying + if (response.status !== 200) { + const retryConfig = HTTP_RETRY_CODES[response.status]; + if (retryConfig) { + logger.warn(`${retryConfig.message} (${response.status}) for ${url}, waiting ${retryConfig.delay / 1000}s before retry`); + await new Promise(resolve => setTimeout(resolve, retryConfig.delay)); + attempt++; + logger.info(`Retrying fetch for ${url} (Attempt ${attempt}/${maxRetries})...`); + continue; } + + throw new Error(`HTTP ${response.status} for ${url}\nResponse: ${JSON.stringify(response.data).slice(0, 200)}`); } - } finally { - delete activeFetches[url]; - } - }; - // Check for rate limit before making request - const lastRateLimit2 = rateDelayCache.get(url); - if (lastRateLimit2) { - const timeToWait = lastRateLimit2 + RATE_LIMIT_DELAY - Date.now(); - if (timeToWait > 0) { - logger.info(`Rate limit cooling down for URL: ${url}, waiting ${timeToWait}ms`); - await new Promise(resolve => setTimeout(resolve, timeToWait)); - rateDelayCache.delete(url); + return response.data; + } catch (error) { + if (attempt === maxRetries - 1) throw error; + attempt++; + logger.error(`Error fetching data from ${url}:`, axios.isAxiosError(error) ? error.message : error); + await new Promise(resolve => setTimeout(resolve, retryDelay)); } } + throw new Error(`Max retries (${maxRetries}) exceeded for ${url}`); +} + +export async function FetchRestData( + url: string, + options: AxiosRequestConfig = {}, + maxRetries?: number, + retryDelay?: number, + timeout?: number +): Promise { + if (url in activeFetches) { + return activeFetches[url] as Promise; + } - const promise = skipBackoff ? - fetchFunc() : - BackoffRetry(`FetchRestData: ${url}`, fetchFunc, retries, factor, minTimeout, maxTimeout); + const promise = doFetch(url, options, maxRetries, retryDelay, timeout); activeFetches[url] = promise; - return await promise as T; + + try { + return await promise; + } finally { + delete activeFetches[url]; + } } diff --git a/src/restRpc/lavaRpc.ts b/src/restRpc/lavaRpc.ts index d861cdc..ca44e39 100644 --- a/src/restRpc/lavaRpc.ts +++ b/src/restRpc/lavaRpc.ts @@ -9,11 +9,11 @@ const LavaRPCBaseUrl = (() => { return url; })(); -export async function QueryLavaRPC(path: string, skipBackoff: boolean = false): Promise { +export async function QueryLavaRPC(path: string): Promise { if (LavaRPCBaseUrl.endsWith('/') && path.startsWith('/')) { path = path.slice(1); } const url = `${LavaRPCBaseUrl}${path}`; - return FetchRestData(url, {}, skipBackoff); + return FetchRestData(url); } diff --git a/src/utils/retry.ts b/src/utils/retry.ts deleted file mode 100644 index 0c7fded..0000000 --- a/src/utils/retry.ts +++ /dev/null @@ -1,44 +0,0 @@ -import retry from 'async-retry'; -import util from 'util'; -import { logger } from './logger'; -import { TruncateError } from './fmt'; - -// Define the BackoffRetry function -export const BackoffRetry = async ( - title: string, - fn: () => Promise, - retries: number = 8, - factor: number = 2, - minTimeout: number = 1000, - maxTimeout: number = 5000 -): Promise => { - return await retry(fn, - { - retries: retries, // The maximum amount of times to retry the operation - factor: factor, // The exponential factor to use - minTimeout: minTimeout, // The number of milliseconds before starting the first retry - maxTimeout: maxTimeout, // The maximum number of milliseconds between two retries - randomize: true, // Randomizes the timeouts by multiplying with a factor between 1 to 2 - onRetry: (error: any, attempt: any) => { - if (!(error instanceof Error) || !error.message.includes('429')) { - logger.error( - `[${title}] Attempt ${attempt}/${retries} failed: ${error instanceof Error ? error.message : String(error)}` - ); - throw error; - } - let errorMessage = `[Backoff Retry] Function: ${title}\n`; - try { - errorMessage += `Attempt number: ${attempt} has failed.\n`; - if (error instanceof Error) { - errorMessage += `An error occurred during the execution of ${title}: ${TruncateError(error.message)}\n`; - errorMessage += `Stack trace for the error in ${title}: ${TruncateError(error.stack)}\n`; - errorMessage += `Full error object: ${TruncateError(util.inspect(error, { showHidden: true, depth: null }))}\n`; - } else { - errorMessage += `An unknown error occurred during the execution of ${title}: ${TruncateError(String(error))}\n`; - } - } catch (e) { } - logger.error(errorMessage); - } - } - ); -};