{"version":3,"file":"retryOnEmpty.cjs","sourceRoot":"","sources":["../src/retryOnEmpty.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PollingBlockTracker } from '@metamask/eth-block-tracker';\nimport type { InternalProvider } from '@metamask/eth-json-rpc-provider';\nimport type { JsonRpcMiddleware } from '@metamask/json-rpc-engine/v2';\nimport type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';\nimport { klona } from 'klona';\n\nimport { projectLogger, createModuleLogger } from './logging-utils';\nimport type { Block } from './types';\nimport { blockTagParamIndex } from './utils/cache';\nimport { isExecutionRevertedError } from './utils/error';\nimport { timeout } from './utils/timeout';\n\n//\n// RetryOnEmptyMiddleware will retry any request with an empty response that has\n// a numbered block reference at or lower than the blockTracker's latest block.\n// Its useful for dealing with load-balanced ethereum JSON RPC\n// nodes that are not always in sync with each other.\n//\n\nconst log = createModuleLogger(projectLogger, 'retry-on-empty');\n// empty values used to determine if a request should be retried\n// `<nil>` comes from https://github.com/ethereum/go-ethereum/issues/16925\nconst emptyValues = [null, '\\u003cnil\\u003e'];\n\n/**\n * Creates a middleware that retries requests with empty responses.\n *\n * @param options - The options for the middleware.\n * @param options.provider - The provider to use.\n * @param options.blockTracker - The block tracker to use.\n * @returns The middleware.\n */\nexport function createRetryOnEmptyMiddleware({\n  provider,\n  blockTracker,\n}: {\n  provider?: InternalProvider;\n  blockTracker?: PollingBlockTracker;\n} = {}): JsonRpcMiddleware<JsonRpcRequest, Json> {\n  if (!provider) {\n    throw Error(\n      'RetryOnEmptyMiddleware - mandatory \"provider\" option is missing.',\n    );\n  }\n\n  if (!blockTracker) {\n    throw Error(\n      'RetryOnEmptyMiddleware - mandatory \"blockTracker\" option is missing.',\n    );\n  }\n\n  return async ({ request, next }) => {\n    const blockRefIndex: number | undefined = blockTagParamIndex(\n      request.method,\n    );\n    // skip if method does not include blockRef\n    if (blockRefIndex === undefined) {\n      return next();\n    }\n    // skip if not exact block references\n    let blockRef: string | undefined =\n      Array.isArray(request.params) && request.params[blockRefIndex]\n        ? (request.params[blockRefIndex] as string)\n        : undefined;\n    // omitted blockRef implies \"latest\"\n    blockRef ??= 'latest';\n\n    // skip if non-number block reference\n    if (['latest', 'pending'].includes(blockRef)) {\n      return next();\n    }\n    // skip if block refernce is not a valid number\n    const blockRefNumber: number = Number.parseInt(blockRef.slice(2), 16);\n    if (Number.isNaN(blockRefNumber)) {\n      return next();\n    }\n    // lookup latest block\n    const latestBlockNumberHex: string = await blockTracker.getLatestBlock();\n    const latestBlockNumber: number = Number.parseInt(\n      latestBlockNumberHex.slice(2),\n      16,\n    );\n    // skip if request block number is higher than current\n    if (blockRefNumber > latestBlockNumber) {\n      log(\n        'Requested block number %o is higher than latest block number %o, falling through to original request',\n        blockRefNumber,\n        latestBlockNumber,\n      );\n      return next();\n    }\n\n    log(\n      'Requested block number %o is not higher than latest block number %o, trying request until non-empty response is received',\n      blockRefNumber,\n      latestBlockNumber,\n    );\n\n    // create child request with specific block-ref\n    const childRequest = klona(request);\n    // attempt child request until non-empty response is received\n    const childResult = await retry(10, async () => {\n      log('Performing request %o', childRequest);\n      const attemptResult = await provider.request<JsonRpcParams, Block>(\n        childRequest,\n      );\n      log('Result is %o', attemptResult);\n      // verify result\n      const allEmptyValues: unknown[] = emptyValues;\n      if (allEmptyValues.includes(attemptResult)) {\n        throw new Error(\n          `RetryOnEmptyMiddleware - empty result \"${JSON.stringify(\n            attemptResult,\n          )}\" for request \"${JSON.stringify(childRequest)}\"`,\n        );\n      }\n      return attemptResult;\n    });\n    log('Copying result %o', childResult);\n    return childResult;\n  };\n}\n\n/**\n * Retries an asynchronous function up to a maximum number of times.\n *\n * @param maxRetries - The maximum number of retries.\n * @param asyncFn - The asynchronous function to retry.\n * @returns The result of the asynchronous function.\n */\nasync function retry<Result>(\n  maxRetries: number,\n  asyncFn: () => Promise<Result>,\n): Promise<Result> {\n  for (let index = 0; index < maxRetries; index++) {\n    try {\n      return await asyncFn();\n    } catch (error: unknown) {\n      if (isExecutionRevertedError(error)) {\n        throw error as unknown;\n      }\n      log('(call %i) Request failed, waiting 1s to retry again...', index + 1);\n      await timeout(1000);\n    }\n  }\n  log('Retries exhausted');\n  throw new Error('RetryOnEmptyMiddleware - retries exhausted');\n}\n"]}