From 8703da5ad89c1d968a4140b5f1049c92a35fdac9 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Mon, 1 Aug 2022 14:08:43 -0400 Subject: [PATCH] Update RetryProvider to use exponential backoff (#859) --- typescript/infra/src/config/chain.ts | 4 +-- typescript/sdk/src/providers/RetryProvider.ts | 25 +++++++++++++------ typescript/utils/src/utils.ts | 11 ++++---- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/typescript/infra/src/config/chain.ts b/typescript/infra/src/config/chain.ts index 37c890e8e..688f333e5 100644 --- a/typescript/infra/src/config/chain.ts +++ b/typescript/infra/src/config/chain.ts @@ -19,8 +19,8 @@ export async function fetchProvider( const provider = celoChainNames.has(chainName) ? new StaticCeloJsonRpcProvider(rpc) : new RetryJsonRpcProvider(new ethers.providers.JsonRpcProvider(rpc), { - retryLimit: 2, - interval: 250, + maxRequests: 6, + baseRetryMs: 50, }); return provider; } diff --git a/typescript/sdk/src/providers/RetryProvider.ts b/typescript/sdk/src/providers/RetryProvider.ts index 1fd6d6a40..fada2f827 100644 --- a/typescript/sdk/src/providers/RetryProvider.ts +++ b/typescript/sdk/src/providers/RetryProvider.ts @@ -2,16 +2,17 @@ // // Mostly taken from the removed version that was in ethers.js // See: https://github.com/ethers-io/ethers.js/discussions/3006 +import { assert } from 'console'; import { ethers } from 'ethers'; import { utils } from '@abacus-network/utils'; export type RetryOptions = { - // The wait interval in between - interval: number; + // Maximum number of times to make the RPC + maxRequests: number; - // Maximum number of times to retry - retryLimit: number; + // Exponential backoff base value + baseRetryMs: number; }; export class RetryProvider extends ethers.providers.BaseProvider { @@ -20,6 +21,10 @@ export class RetryProvider extends ethers.providers.BaseProvider { readonly retryOptions: RetryOptions, ) { super(provider.getNetwork()); + assert( + retryOptions.maxRequests >= 1, + 'RetryOptions.maxRequests must be >= 1', + ); ethers.utils.defineReadOnly(this, 'provider', provider); ethers.utils.defineReadOnly(this, 'retryOptions', retryOptions); } @@ -28,8 +33,8 @@ export class RetryProvider extends ethers.providers.BaseProvider { perform(method: string, params: any): Promise { return utils.retryAsync( () => this.provider.perform(method, params), - this.retryOptions.retryLimit, - this.retryOptions.interval, + this.retryOptions.maxRequests, + this.retryOptions.baseRetryMs, ); } } @@ -41,13 +46,17 @@ export class RetryJsonRpcProvider extends ethers.providers.JsonRpcProvider { readonly retryOptions: RetryOptions, ) { super(provider.connection, provider.network); + assert( + retryOptions.maxRequests >= 1, + 'RetryOptions.maxRequests must be >= 1', + ); } async send(method: string, params: Array): Promise { return utils.retryAsync( async () => this.provider.send(method, params), - this.retryOptions.retryLimit, - this.retryOptions.interval, + this.retryOptions.maxRequests, + this.retryOptions.baseRetryMs, ); } } diff --git a/typescript/utils/src/utils.ts b/typescript/utils/src/utils.ts index 4201b9fb2..70a9a7e24 100644 --- a/typescript/utils/src/utils.ts +++ b/typescript/utils/src/utils.ts @@ -101,12 +101,13 @@ export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -// Retries an async function when it raises an exception -// if all the tries fail it raises the last thrown exception +// Retries an async function if it raises an exception, +// with exponential backoff. +// If all the tries fail it raises the last thrown exception export async function retryAsync( runner: () => T, - attempts = 3, - delay = 500, + attempts = 5, + baseRetryMs = 50, ) { let saveError; for (let i = 0; i < attempts; i++) { @@ -114,7 +115,7 @@ export async function retryAsync( return runner(); } catch (error) { saveError = error; - await sleep(delay * (i + 1)); + await sleep(baseRetryMs * 2 ** i); } } throw saveError;