{"version":3,"file":"block-cache.mjs","sourceRoot":"","sources":["../src/block-cache.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PollingBlockTracker } from '@metamask/eth-block-tracker';\nimport type {\n  JsonRpcMiddleware,\n  MiddlewareContext,\n} from '@metamask/json-rpc-engine/v2';\nimport type { Json, JsonRpcRequest } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from './logging-utils';\nimport type {\n  Block,\n  BlockCache,\n  // eslint-disable-next-line @typescript-eslint/no-shadow\n  Cache,\n} from './types';\nimport {\n  cacheIdentifierForRequest,\n  blockTagForRequest,\n  cacheTypeForMethod,\n  canCache,\n  CacheStrategy,\n} from './utils/cache';\n\nconst log = createModuleLogger(projectLogger, 'block-cache');\n// `<nil>` comes from https://github.com/ethereum/go-ethereum/issues/16925\nconst emptyValues: unknown[] = [undefined, null, '\\u003cnil\\u003e'];\n\ntype BlockCacheMiddlewareOptions = {\n  blockTracker?: PollingBlockTracker;\n};\n\n//\n// Cache Strategies\n//\n\nclass BlockCacheStrategy {\n  #cache: Cache;\n\n  constructor() {\n    this.#cache = {};\n  }\n\n  getBlockCache(blockNumberHex: string): BlockCache {\n    const blockNumber: number = Number.parseInt(blockNumberHex, 16);\n    let blockCache: BlockCache = this.#cache[blockNumber];\n    // create new cache if necesary\n    if (!blockCache) {\n      const newCache: BlockCache = {};\n      this.#cache[blockNumber] = newCache;\n      blockCache = newCache;\n    }\n    return blockCache;\n  }\n\n  async get(\n    request: JsonRpcRequest,\n    requestedBlockNumber: string,\n  ): Promise<Block | undefined> {\n    // lookup block cache\n    const blockCache: BlockCache = this.getBlockCache(requestedBlockNumber);\n    // lookup payload in block cache\n    const identifier: string | null = cacheIdentifierForRequest(request, true);\n    return identifier ? blockCache[identifier] : undefined;\n  }\n\n  async set(\n    request: JsonRpcRequest,\n    requestedBlockNumber: string,\n    result: Block,\n  ): Promise<void> {\n    // check if we can cached this result\n    const canCacheResult: boolean = this.canCacheResult(request, result);\n    if (!canCacheResult) {\n      return;\n    }\n\n    // set the value in the cache\n    const identifier: string | null = cacheIdentifierForRequest(request, true);\n    if (!identifier) {\n      return;\n    }\n    const blockCache: BlockCache = this.getBlockCache(requestedBlockNumber);\n    blockCache[identifier] = result;\n  }\n\n  canCacheRequest(request: JsonRpcRequest): boolean {\n    // check request method\n    if (!canCache(request.method)) {\n      return false;\n    }\n    // check blockTag\n    const blockTag = blockTagForRequest(request);\n\n    if (blockTag === 'pending') {\n      return false;\n    }\n    // can be cached\n    return true;\n  }\n\n  canCacheResult(request: JsonRpcRequest, result: Block): boolean {\n    // never cache empty values (e.g. undefined)\n    if (emptyValues.includes(result)) {\n      return false;\n    }\n\n    // check if transactions have block reference before caching\n    if (\n      request.method &&\n      ['eth_getTransactionByHash', 'eth_getTransactionReceipt'].includes(\n        request.method,\n      )\n    ) {\n      if (\n        !result?.blockHash ||\n        result.blockHash ===\n          '0x0000000000000000000000000000000000000000000000000000000000000000'\n      ) {\n        return false;\n      }\n    }\n    // otherwise true\n    return true;\n  }\n\n  // removes all block caches with block number lower than `oldBlockHex`\n  clearBefore(oldBlockHex: string): void {\n    const oldBlockNumber: number = Number.parseInt(oldBlockHex, 16);\n    // clear old caches\n    Object.keys(this.#cache)\n      .map(Number)\n      .filter((value) => value < oldBlockNumber)\n      .forEach((value) => delete this.#cache[value]);\n  }\n}\n\n/**\n * Creates a middleware that caches block-related requests.\n *\n * @param options - The options for the middleware.\n * @param options.blockTracker - The block tracker to use.\n * @returns The block cache middleware.\n */\nexport function createBlockCacheMiddleware({\n  blockTracker,\n}: BlockCacheMiddlewareOptions = {}): JsonRpcMiddleware<\n  JsonRpcRequest,\n  Json,\n  MiddlewareContext<{ skipCache?: boolean }>\n> {\n  if (!blockTracker) {\n    throw new Error(\n      'createBlockCacheMiddleware - No PollingBlockTracker specified',\n    );\n  }\n\n  const blockCache: BlockCacheStrategy = new BlockCacheStrategy();\n  const strategies: Record<CacheStrategy, BlockCacheStrategy | undefined> = {\n    [CacheStrategy.Permanent]: blockCache,\n    [CacheStrategy.Block]: blockCache,\n    [CacheStrategy.Fork]: blockCache,\n    [CacheStrategy.Never]: undefined,\n  };\n\n  return async ({ request, next, context }) => {\n    if (context.get('skipCache')) {\n      return next();\n    }\n\n    const type = cacheTypeForMethod(request.method);\n    const strategy = strategies[type];\n    if (!strategy) {\n      return next();\n    }\n\n    if (!strategy.canCacheRequest(request)) {\n      return next();\n    }\n\n    const requestBlockTag = blockTagForRequest(request);\n    const blockTag =\n      requestBlockTag && typeof requestBlockTag === 'string'\n        ? requestBlockTag\n        : 'latest';\n\n    log('blockTag = %o, req = %o', blockTag, request);\n\n    // get exact block number\n    let requestedBlockNumber: string;\n    if (blockTag === 'earliest') {\n      // this just exists for symmetry with \"latest\"\n      requestedBlockNumber = '0x00';\n    } else if (blockTag === 'latest') {\n      log('Fetching latest block number to determine cache key');\n      const latestBlockNumber = await blockTracker.getLatestBlock();\n\n      // clear all cache before latest block\n      log(\n        'Clearing values stored under block numbers before %o',\n        latestBlockNumber,\n      );\n      blockCache.clearBefore(latestBlockNumber);\n      requestedBlockNumber = latestBlockNumber;\n    } else {\n      // we have a hex number\n      requestedBlockNumber = blockTag;\n    }\n\n    // end on a hit, continue on a miss\n    const cacheResult = await strategy.get(request, requestedBlockNumber);\n    if (cacheResult === undefined) {\n      // cache miss\n      // wait for other middleware to handle request\n      log(\n        'No cache stored under block number %o, carrying request forward',\n        requestedBlockNumber,\n      );\n      const result = await next();\n\n      // add result to cache\n      // it's safe to cast res.result as Block, due to runtime type checks\n      // performed when strategy.set is called\n      log('Populating cache with', result);\n      await strategy.set(request, requestedBlockNumber, result as Block);\n      return result;\n    }\n    log(\n      'Cache hit, reusing cache result stored under block number %o',\n      requestedBlockNumber,\n    );\n    return cacheResult;\n  };\n}\n"]}