Repository: ponder-sh/ponder (ts)

Issue:
[Bug]: Query returned more than 10000 results at zksync.
## description

It looks like a one-time request for a large range of data.

## error

![image](https://github.com/ponder-sh/ponder/assets/128128121/9d753b1a-4072-4937-9618-fdf0959f5ccf)

```bash
1:17:39 AM WARN  historical Log filter task failed, retrying... [18406545, 18456544] (contract=PoolContract, network=mainnet, error=InvalidParamsRpcError: Invalid parameters were provided to the RPC method.
Double check you have provided the correct parameters.

URL: https://mainnet.era.zksync.io
Request body: {"method":"eth_getLogs","params":[{"fromBlock":"0x118dc91","toBlock":"0x1199fe0","address":"0xfc00dac251711508d4dd7b0c310e913575988838"}]}

Details: Query returned more than 10000 results. Try with this block range [0x118dc91, 0x1198cc3].
Version: viem@1.21.3)
```

## rpc endpoint

```bash
-> curl https://mainnet.era.zksync.io \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_getLogs","params": [{"fromBlock":"0x118dc91","toBlock":"0x1199fe0","address":["0xfc00dac251711508d4dd7b0c310e913575988838"]}], "id": 1}'

-> {"jsonrpc":"2.0","error":{"code":-32602,"message":"Query returned more than 10000 results. Try with this block range [0x118dc91, 0x1198cc3]."},"id":1}
```

Pull request description:
add zksync url to retry ranges
Closes #724 

Patch:
diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts
index 5ad30c0a..ef08166b 100644
--- a/packages/core/vite.config.ts
+++ b/packages/core/vite.config.ts
@@ -16,6 +16,7 @@ export default defineConfig({
     poolOptions: {
       threads: {
         maxThreads: 4,
+        minThreads: 1,
       },
     },
     sequence: { hooks: "stack" },
diff --git a/packages/utils/src/getLogsRetryHelper.ts b/packages/utils/src/getLogsRetryHelper.ts
index e69c3a67..d931d0db 100644
--- a/packages/utils/src/getLogsRetryHelper.ts
+++ b/packages/utils/src/getLogsRetryHelper.ts
@@ -1,12 +1,8 @@
 import {
   type Address,
   type Hex,
-  HttpRequestError,
-  InvalidParamsRpcError,
-  LimitExceededRpcError,
   type LogTopic,
   RpcError,
-  RpcRequestError,
   hexToBigInt,
   numberToHex,
 } from "viem";
@@ -39,15 +35,13 @@ export const getLogsRetryHelper = ({
   params,
   error,
 }: GetLogsRetryHelperParameters): GetLogsRetryHelperReturnType => {
+  const sError = JSON.stringify(error);
+  let match: RegExpMatchArray | null;
+
   // Cloudflare
-  if (
-    error instanceof RpcRequestError &&
-    error.code === -32047 &&
-    error.details.includes(
-      "Invalid eth_getLogs request. 'fromBlock'-'toBlock' range too large. Max range: 800",
-    )
-  ) {
-    const ranges = chunk({ params, range: 799n });
+  match = sError.match(/Max range: (\d+)/);
+  if (match !== null) {
+    const ranges = chunk({ params, range: BigInt(match[1]!) - 1n });
 
     if (isRangeUnchanged(params, ranges)) {
       return { shouldRetry: false } as const;
@@ -59,13 +53,16 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
-  // Ankr
-  if (
-    error instanceof RpcError &&
-    error.code === -32600 &&
-    error.details.startsWith("block range is too wide")
-  ) {
-    const ranges = chunk({ params, range: 3000n });
+  // Infura, Thirdweb, zkSync
+  match = sError.match(
+    /Try with this block range \[0x([0-9a-fA-F]+),\s*0x([0-9a-fA-F]+)\]/,
+  )!;
+  if (match !== null) {
+    const start = hexToBigInt(`0x${match[1]}`);
+    const end = hexToBigInt(`0x${match[2]}`);
+    const range = end - start;
+
+    const ranges = chunk({ params, range });
 
     if (isRangeUnchanged(params, ranges)) {
       return { shouldRetry: false } as const;
@@ -77,42 +74,25 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
-  // Alchemy
-  if (
-    error.code === InvalidParamsRpcError.code &&
-    error.details.startsWith("Log response size exceeded.")
-  ) {
-    const match = error.details.match(
-      /this block range should work: \[0x([0-9a-fA-F]+),\s*0x([0-9a-fA-F]+)\]/,
-    )!;
-
-    if (match.length === 3) {
-      const start = hexToBigInt(`0x${match[1]}`);
-      const end = hexToBigInt(`0x${match[2]}`);
-      const range = end - start;
-
-      const ranges = chunk({ params, range });
-
-      if (isRangeUnchanged(params, ranges)) {
-        return { shouldRetry: false } as const;
-      }
-
-      return {
-        shouldRetry: true,
-        ranges,
-      } as const;
+  // Thirdweb
+  match = sError.match(/bigger than range limit (\d+)/);
+  if (match !== null) {
+    const ranges = chunk({ params, range: BigInt(match[1]!) });
+
+    if (isRangeUnchanged(params, ranges)) {
+      return { shouldRetry: false } as const;
     }
+
+    return {
+      shouldRetry: true,
+      ranges,
+    } as const;
   }
 
-  // Quicknode
-  if (
-    error instanceof HttpRequestError &&
-    JSON.parse(error.details).code === -32614 &&
-    JSON.parse(error.details).message.includes(
-      "eth_getLogs is limited to a 10,000 range",
-    )
-  ) {
-    const ranges = chunk({ params, range: 10000n });
+  // Ankr
+  match = sError.match("block range is too wide");
+  if (match !== null && error.code === -32600) {
+    const ranges = chunk({ params, range: 3000n });
 
     if (isRangeUnchanged(params, ranges)) {
       return { shouldRetry: false } as const;
@@ -124,55 +104,16 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
-  // Infura
-  if (
-    error instanceof LimitExceededRpcError &&
-    error.code === -32005 &&
-    (error.cause as any)?.cause.message.includes(
-      "query returned more than 10000 results. Try with this block range",
-    )
-  ) {
-    // @ts-ignore
-    const match = error.cause.cause.message.match(
-      /Try with this block range \[0x([0-9a-fA-F]+),\s*0x([0-9a-fA-F]+)\]/,
-    )!;
-
-    if (match.length === 3) {
-      const start = hexToBigInt(`0x${match[1]}`);
-      const end = hexToBigInt(`0x${match[2]}`);
-      const range = end - start;
-
-      const ranges = chunk({ params, range });
-
-      if (isRangeUnchanged(params, ranges)) {
-        return { shouldRetry: false } as const;
-      }
-
-      return {
-        shouldRetry: true,
-        ranges,
-      } as const;
-    }
-  }
+  // Alchemy
+  match = sError.match(
+    /this block range should work: \[0x([0-9a-fA-F]+),\s*0x([0-9a-fA-F]+)\]/,
+  );
+  if (match !== null) {
+    const start = hexToBigInt(`0x${match[1]}`);
+    const end = hexToBigInt(`0x${match[2]}`);
+    const range = end - start;
 
-  // Thirdweb
-  /**
-   * "code": -32602,
-   * "message": "invalid params",
-   * "data": "range 20000 is bigger than range limit 2000"
-   *
-   * "code": -32602,
-   * "message": "invalid params",
-   * "data": "Query returned more than 10000 results. Try with this block range [0x800000, 0x800054]."
-   */
-
-  if (
-    error instanceof InvalidParamsRpcError &&
-    error.code === -32602 &&
-    // @ts-ignore
-    error.cause.cause.data.includes("is bigger than range limit 2000")
-  ) {
-    const ranges = chunk({ params, range: 2000n });
+    const ranges = chunk({ params, range });
 
     if (isRangeUnchanged(params, ranges)) {
       return { shouldRetry: false } as const;
@@ -184,33 +125,22 @@ export const getLogsRetryHelper = ({
     } as const;
   }
 
-  if (
-    error instanceof InvalidParamsRpcError &&
-    error.code === -32602 &&
-    // @ts-ignore
-    error.cause.cause.data.includes("Query returned more than 10000 results")
-  ) {
-    // @ts-ignore
-    const match = error.cause.cause.data.match(
-      /Try with this block range \[0x([0-9a-fA-F]+),\s*0x([0-9a-fA-F]+)\]/,
-    )!;
-
-    if (match.length === 3) {
-      const start = hexToBigInt(`0x${match[1]}`);
-      const end = hexToBigInt(`0x${match[2]}`);
-      const range = end - start;
-
-      const ranges = chunk({ params, range });
-
-      if (isRangeUnchanged(params, ranges)) {
-        return { shouldRetry: false } as const;
-      }
-
-      return {
-        shouldRetry: true,
-        ranges,
-      } as const;
+  // Quicknode
+  match = sError.match(/eth_getLogs is limited to a ([\d,.]+) range/);
+  if (match !== null) {
+    const ranges = chunk({
+      params,
+      range: BigInt(match[1]!.replace(/[,.]/g, "")),
+    });
+
+    if (isRangeUnchanged(params, ranges)) {
+      return { shouldRetry: false } as const;
     }
+
+    return {
+      shouldRetry: true,
+      ranges,
+    } as const;
   }
 
   // No match found
@@ -227,9 +157,10 @@ const isRangeUnchanged = (
   >["ranges"],
 ) => {
   return (
-    ranges.length === 1 &&
-    ranges[0]!.fromBlock === params[0].fromBlock &&
-    ranges[0]!.toBlock === params[0].toBlock
+    ranges.length === 0 ||
+    (ranges.length === 1 &&
+      ranges[0]!.fromBlock === params[0].fromBlock &&
+      ranges[0]!.toBlock === params[0].toBlock)
   );
 };
 


Test patch:
diff --git a/packages/utils/src/_test/zksync.test.ts b/packages/utils/src/_test/zksync.test.ts
new file mode 100644
index 00000000..4c9126b4
--- /dev/null
+++ b/packages/utils/src/_test/zksync.test.ts
@@ -0,0 +1,59 @@
+import { RpcError, numberToHex } from "viem";
+import { expect, test } from "vitest";
+import { getLogsRetryHelper } from "../getLogsRetryHelper.js";
+import { type Params, getRequest } from "./utils.js";
+
+const request = getRequest("https://mainnet.era.zksync.io");
+
+const fromBlock = 18406545n;
+
+test("zksync success", async () => {
+  const logs = await request({
+    method: "eth_getLogs",
+    params: [
+      {
+        address: "0xfc00dac251711508d4dd7b0c310e913575988838",
+        fromBlock: numberToHex(fromBlock),
+        toBlock: numberToHex(fromBlock + 10_000n),
+      },
+    ],
+  });
+
+  expect(logs).toHaveLength(726);
+});
+
+test("zksync block range", async () => {
+  const params: Params = [
+    {
+      address: "0xfc00dac251711508d4dd7b0c310e913575988838",
+      fromBlock: numberToHex(fromBlock),
+      toBlock: numberToHex(fromBlock + 50_000n),
+    },
+  ];
+
+  const error = await request({
+    method: "eth_getLogs",
+    params,
+  }).catch((error) => error);
+
+  expect(error).toBeInstanceOf(RpcError);
+
+  const retry = getLogsRetryHelper({
+    params,
+    error: error,
+  });
+
+  expect(retry).toStrictEqual({
+    shouldRetry: true,
+    ranges: [
+      {
+        fromBlock: numberToHex(fromBlock),
+        toBlock: numberToHex(fromBlock + 45106n),
+      },
+      {
+        fromBlock: numberToHex(fromBlock + 45106n + 1n),
+        toBlock: numberToHex(fromBlock + 50_000n),
+      },
+    ],
+  });
+});