diff --git a/src/helpers/common.ts b/src/helpers/common.ts index a69a673..6d814a4 100644 --- a/src/helpers/common.ts +++ b/src/helpers/common.ts @@ -1,7 +1,8 @@ +import { JRPCResponse } from "@toruslabs/constants"; import { Ecies } from "@toruslabs/eccrypto"; import JsonStringify from "json-stable-stringify"; -import { EciesHex, VerifierLookupResponse } from "../interfaces"; +import { CommitmentRequestResult, EciesHex, VerifierLookupResponse } from "../interfaces"; // this function normalizes the result from nodes before passing the result to threshold check function // For ex: some fields returns by nodes might be different from each other @@ -104,3 +105,60 @@ export function calculateMedian(arr: number[]): number { const mid2 = sortedArr[arrSize / 2]; return (mid1 + mid2) / 2; } + +export function waitFor(milliseconds: number) { + return new Promise((resolve, reject) => { + // hack to bypass eslint warning. + if (milliseconds > 0) { + setTimeout(resolve, milliseconds); + } else { + reject(new Error("value of milliseconds must be greater than 0")); + } + }); +} + +export function retryCommitment(executionPromise: () => Promise>, maxRetries: number) { + // Notice that we declare an inner function here + // so we can encapsulate the retries and don't expose + // it to the caller. This is also a recursive function + async function retryWithBackoff(retries: number) { + try { + // we don't wait on the first attempt + if (retries > 0) { + // on every retry, we exponentially increase the time to wait. + // Here is how it looks for a `maxRetries` = 4 + // (2 ** 1) * 100 = 200 ms + // (2 ** 2) * 100 = 400 ms + // (2 ** 3) * 100 = 800 ms + const timeToWait = 2 ** retries * 100; + await waitFor(timeToWait); + } + const a = await executionPromise(); + return a; + } catch (e: unknown) { + const errorMsg = (e as Error).message; + const acceptedErrorMsgs = [ + // Slow node + "Timed out", + "Failed to fetch", + "fetch failed", + "Load failed", + "cancelled", + "NetworkError when attempting to fetch resource.", + // Happens when the node is not reachable (dns issue etc) + "TypeError: Failed to fetch", // All except iOS and Firefox + "TypeError: cancelled", // iOS + "TypeError: NetworkError when attempting to fetch resource.", // Firefox + ]; + + if (retries < maxRetries && (acceptedErrorMsgs.includes(errorMsg) || (errorMsg && errorMsg.includes("reason: getaddrinfo EAI_AGAIN")))) { + // only retry if we didn't reach the limit + // otherwise, let the caller handle the error + return retryWithBackoff(retries + 1); + } + throw e; + } + } + + return retryWithBackoff(0); +} diff --git a/src/helpers/nodeUtils.ts b/src/helpers/nodeUtils.ts index 52607b0..7837a46 100644 --- a/src/helpers/nodeUtils.ts +++ b/src/helpers/nodeUtils.ts @@ -23,7 +23,7 @@ import { } from "../interfaces"; import log from "../loglevel"; import { Some } from "../some"; -import { calculateMedian, kCombinations, normalizeKeysResult, thresholdSame } from "./common"; +import { calculateMedian, kCombinations, normalizeKeysResult, retryCommitment, thresholdSame } from "./common"; import { generateAddressFromPrivKey, generateAddressFromPubKey, keccak256 } from "./keyUtils"; import { lagrangeInterpolation } from "./langrangeInterpolatePoly"; import { decryptNodeData, getMetadata, getOrSetNonce, getOrSetSapphireMetadataNonce } from "./metadataUtils"; @@ -209,19 +209,21 @@ export async function retrieveOrImportShare(params: { VerifierIdentifier string `json:"verifieridentifier"` } */ - const p = post>( - endpoints[i], - generateJsonRPCObject(JRPC_METHODS.COMMITMENT_REQUEST, { - messageprefix: "mug00", - tokencommitment: tokenCommitment.slice(2), - temppubx: pubKeyX, - temppuby: pubKeyY, - verifieridentifier: verifier, - }), - null, - { logTracingHeader: config.logRequestTracing } - ); - promiseArr.push(p); + const p = () => + post>( + endpoints[i], + generateJsonRPCObject(JRPC_METHODS.COMMITMENT_REQUEST, { + messageprefix: "mug00", + tokencommitment: tokenCommitment.slice(2), + temppubx: pubKeyX, + temppuby: pubKeyY, + verifieridentifier: verifier, + }), + null, + { logTracingHeader: config.logRequestTracing } + ); + const r = retryCommitment(p, 4); + promiseArr.push(r); } // send share request once k + t number of commitment requests have completed return Some, (void | JRPCResponse)[]>(promiseArr, (resultArr) => {