eth_getLogs block range too large 报错:各链区块范围上限与分段查询

携带 API key 时,单次 eth_getLogs 在 HyperEVM 上最多跨 1,000 个区块,各链上限不同,以公布的 max_logs_block_range 为准;超限返回 logs_range_too_large,应按该上限把区间分段,成功后再从末尾继续。

直接答案

携带 API key 时,单次 eth_getLogs 的上限是目标链在 GET /v1/chains 中的 max_logs_block_range:HyperEVM 为 1,000 个区块,Robinhood Chain 为 1,000 个,区块数按 toBlock − fromBlock + 1 计算;全部链见下表。核对于 。超限返回 HTTP 200、JSON-RPC -32602 与 error.data.reason: logs_range_too_large,且 retryable: false(见错误目录)。将区间分成 [from, min(from + max − 1, end)],成功后从上一段末尾加一继续。

选择 RPC 服务商.

  • 第一步:运行 curl -s "https://api.blockvectra.com/v1/chains"。携带 key 时读取 max_logs_block_range、methods.allow 与 methods.deny;匿名访问先检查 public.methods,再读取 public.logs、public.history_blocks 与公共通用策略。
  • 完成标准:logs-minimal.mjs 输出每个已完成区段的 fromBlock、toBlock 与 result 数组,覆盖至所选 TO_BLOCK,且没有 HTTP 或 JSON-RPC 错误。

链参数与接入方式。

  • 程序化开户:自主 AI Agent、自动化工作流与脚本可通过以太坊钱包签名(EIP-191)免浏览器登录并创建 API key。详见程序化开户指南。
  • Web 控制台:通过以太坊钱包签名登录,并在控制台创建 API key。详细步骤请参阅快速上手指南。

接入模式

携带 API key 时,读取顶层 max_logs_block_range。匿名访问先确认 public.methods 包含 eth_getLogs,再同时遵守 public.logs、public.history_blocks 与公共通用速率及 batch 策略。public.logs.max_block_range 是单次查询跨度,public.history_blocks 是区块引用可回溯窗口,与状态窗口不同。匿名 blockHash 支持由 public.logs.block_hash_supported 决定。策略缺失或未知不表示无限制;主网权限不代表测试网权限。下面的示例使用 API key。

将以下代码保存为 logs-minimal.mjs,设置环境变量 BLOCKVECTRA_API_KEY、合约地址 LOG_ADDRESS 及已确认的区块窗口 FROM_BLOCK、TO_BLOCK,然后执行 node logs-minimal.mjs(Node.js 24 或以上)。 用 CHAIN 选择目标链;默认使用 robinhood_mainnet。

const { BLOCKVECTRA_API_KEY: key, LOG_ADDRESS: address, FROM_BLOCK, TO_BLOCK } = process.env;
if (!key || !/^0x[0-9a-f]{40}$/i.test(address ?? '')) throw new Error('Set BLOCKVECTRA_API_KEY and LOG_ADDRESS');
if (![FROM_BLOCK, TO_BLOCK].every(value => /^(0x[0-9a-f]+|[0-9]+)$/i.test(value ?? ''))) {
  throw new Error('Set FROM_BLOCK and TO_BLOCK to nonnegative block numbers');
}
const start = BigInt(FROM_BLOCK), end = BigInt(TO_BLOCK);
if (start > end) throw new Error('FROM_BLOCK must not exceed TO_BLOCK');
const chainSlug = process.env.CHAIN ?? 'robinhood_mainnet';
const chainsUrl = 'https://api.blockvectra.com/v1/chains';
const catalogResponse = await fetch(chainsUrl, { signal: AbortSignal.timeout(15_000) });
if (!catalogResponse.ok) throw new Error(`Chains HTTP ${catalogResponse.status}`);
const catalog = await catalogResponse.json();
const chain = catalog.chains.find(item => item.chain === chainSlug);
if (!chain || !Number.isSafeInteger(chain.max_logs_block_range) || chain.max_logs_block_range <= 0) {
  throw new Error('Missing or invalid max_logs_block_range');
}
const matches = pattern => pattern.endsWith('*') ? 'eth_getLogs'.startsWith(pattern.slice(0, -1)) : pattern === 'eth_getLogs';
if (!chain.methods?.allow?.some(matches) || chain.methods?.deny?.some(matches)) {
  throw new Error('eth_getLogs is unavailable on this chain');
}
const max = BigInt(chain.max_logs_block_range);
const rpcUrl = new URL(`./${chainSlug}`, chainsUrl).href;
const hex = value => `0x${value.toString(16)}`;
for (let from = start; from <= end;) {
  const to = from + max - 1n < end ? from + max - 1n : end;
  const response = await fetch(rpcUrl, {
    method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15_000),
    headers: { 'Content-Type': 'application/json', 'x-api-key': key },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getLogs',
      params: [{ address, fromBlock: hex(from), toBlock: hex(to) }] }),
  });
  const body = await response.json();
  const valid = body !== null && typeof body === 'object' && !Array.isArray(body) &&
    body.jsonrpc === '2.0' && body.id === 1 && !('error' in body) && Array.isArray(body.result);
  if (!response.ok || !valid) {
    throw new Error(`RPC HTTP ${response.status}: ${JSON.stringify(body?.error ?? 'Invalid result')}`);
  }
  console.log(JSON.stringify({ fromBlock: hex(from), toBlock: hex(to), result: body.result }));
  from = to + 1n;
}

每行输出一个已成功区段;任何 HTTP 或 JSON-RPC 错误都会终止示例,不会跳过失败区段。429 处理见下方批量与速率限制说明。

链头附近的空结果

当没有日志匹配过滤条件时,空的 result: [] 可能是有效结果。如果交易收据看起来与这一结果矛盾,请在同一端点上比较同一区块哈希,并对交易收据中的日志应用相同的 address 与 topics 过滤条件;交易收据中可能包含无关日志。

Nethermind 2.1.0 的一个 issue 报告了另一种情况:在区块成为链头之前,使用 blockHash 的 eth_getLogs 会临时返回 [],而该哈希对应的交易收据已经包含日志。这是关于该客户端及该版本的报告,不是经过确认的 BlockVectra 事件。缩小区块范围并不能诊断这一差异。

遇到可比的差异时,请保存区块哈希与高度、确切的过滤条件、观察到的链头,以及带时间戳的响应;移除 API key 与凭据。如果端点支持交易收据查询,只比较符合你过滤条件的日志。核查这一矛盾期间,在索引器中保持该区块未解决,使用有界重试以及你的重组/checkpoint 恢复策略。不要把所有空结果都当作失败。上面的最小示例查询的是一个已确认的区块窗口,不是链头附近的完整性检查。

BSC:已保存的非空事件对照

BNB Chain 的公开端点文档说明,其列出的公共主网端点禁用 eth_getLogs。方法不可用与成功返回 result: [] 是两种情况;该政策本身不能解释有匹配收据日志时为何成功返回空数组。使用 BlockVectra 时,BSC 访问方式中的 eth_getLogs 需要 API key;公开 RPC 不是该方法的入口。

固定版本的历史样例保存了 2026-10-25 13:26:17–21 UTC 的三次请求:chain ID 56、固定交易的 receipt,以及只按地址过滤的单区块 logs 查询,区块为 129476271(0x7b7a6af)。查询返回四条日志,其中 token 1811 的一条 ERC-721 Transfer 与 receipt 的八个字段一致:address、topics、data、blockHash、blockNumber、transactionHash、transactionIndex 和 logIndex。

下载该固定版本的 ZIP,解压后进入样例文件所在目录。下载需要联网;checker 本身使用 Node.js 22 或以上离线运行,不需要 API key 或 npm install:

node verify-sample.mjs observed-sample.json

预期输出:PASS 1 receipt event matched among 4 returned logs。这只核对历史存档中的一条事件,不证明当前历史可用、全块或全历史完整性、区块为 canonical、性能或事故原因。该样例不是下方全块 checker 的输入,也不构成全块验收。

各链 eth_getLogs 上限

以下为 GET /v1/chains 公布的各链 max_logs_block_range;「未公布」不表示无限制。调用前同时核对该链的 methods.allow 与 methods.deny,其中 deny 优先;区块跨度上限不代表结果数量或查询耗时上限。

链链标识max_logs_block_range(区块数)
Arbitrum Onearb_mainnet1,000
Arcarc_mainnet1,000
Avalanche C-Chainavax_mainnet1,000
Basebase_mainnet1,000
Berachainberachain_mainnet1,000
Bobaboba_mainnet1,000
BNB Smart Chainbsc_mainnet1,000
Celocelo_mainnet1,000
Ethereumeth_mainnet1,000
Ethereum Sepoliaeth_sepolia1,000
Fraxtalfraxtal_mainnet1,000
Gnosisgnosis_mainnet1,000
HyperEVMhyperevm_mainnet1,000
Inkink_mainnet1,000
Katanakatana_mainnet1,000
Linealinea_mainnet1,000
Metal L2metall2_mainnet1,000
Modemode_mainnet1,000
OP Mainnetop_mainnet1,000
Polygonpolygon_mainnet1,000
Robinhood Chainrobinhood_mainnet1,000
Robinhood Chain Testnetrobinhood_testnet1,000
Rootstockrootstock_mainnet1,000
Soneiumsoneium_mainnet1,000
Sonicsonic_mainnet1,000
Unichainunichain_mainnet1,000
World Chainworld_mainnet1,000
X Layerxlayer_mainnet1,000
Zorazora_mainnet1,000

eth_getLogs 的区块跨度上限

在调用 JSON-RPC 方法 eth_getLogs 时,单次请求的区块跨度按照 toBlock − fromBlock + 1 计算,不能超过目标链通过 GET /v1/chains 公布的 max_logs_block_range 上限。

该限制因链而异——各链的参数通过公开接口 GET /v1/chains 发布(链列表见支持的链)。该接口免鉴权、不计费。在编写客户端代码时,请在运行时通过该接口动态读取目标链的 max_logs_block_range,不要将上限数值写死在代码中。

过滤参数 fromBlock 与 toBlock 缺省或为 null 时,按 latest 处理。

常见报错原文

先区分区块跨度、结果数量与查询耗时;同一个 JSON-RPC 错误码可能代表不同问题。

报错原文 / 标识出处对应处理
eth_getLogs block range too large: max <N> blocks;-32602;logs_range_too_largeBlockVectra 错误目录<N> 是该链的 max_logs_block_range;缩小跨度后重发,原样重试无效。
query block range exceeds server limit, narrow your filter: <N>Erigon eth_getLogs 源码<N> 是该节点的区块范围上限;缩小查询区间后重发。
query returns too many logs, narrow your filter: <N>Erigon eth_getLogs 源码<N> 是该节点的结果数量上限;缩小区间,并用 address、topics 收窄过滤。即使只查一个区块,也可能需要更精确的过滤条件。

以上报错模板中的 <N> 在实际响应中替换为端点上限。第三方原文对应其自身的端点与限制,措辞可能随客户端版本变化;使用 BlockVectra 时,以 /v1/chains 和 error.data.reason 为准。

读出 Web3.py HTTPError 背后的 JSON-RPC 错误

同步 HTTP provider 下,Web3.py 通过 Requests 发请求,并在默认的非流式路径上调用 raise_for_status()。因此 4xx/5xx 回复会以 requests.HTTPError 抛出(见 web3.py#3915),服务端返回的 JSON-RPC error 就在 exc.response 里。AsyncHTTPProvider 与 stream=True 是另外的路径,本节不涉及。

Requests 的 Response 在 4xx/5xx 时为假值,所以要判断 exc.response is None,不要写 if response。下面的 w3 和 filter_params 是你已有的客户端与过滤条件,把原来的调用包起来即可:

Inspect an HTTP error
import requests


def rpc_error_from_http(exc):
    response = exc.response
    if response is None:
        return None
    try:
        body = response.json()
    except ValueError:
        return None
    error = body.get("error") if isinstance(body, dict) else None
    if not isinstance(error, dict):
        return None
    data = error.get("data")
    reason = data.get("reason") if isinstance(data, dict) else None
    code = error.get("code")
    return {
        "status": response.status_code,
        "code": code if isinstance(code, int) and not isinstance(code, bool) else None,
        "reason": reason if isinstance(reason, str) else None,
    }


try:
    logs = w3.eth.get_logs(filter_params)
except requests.HTTPError as exc:
    info = rpc_error_from_http(exc)
    if info is not None:
        print(info)
    raise

这个 helper 只读状态码、code 和 data.reason,不输出请求头、密钥、请求内容或完整响应体;响应体不是带 error 对象的 JSON 对象时(例如 502 的 HTML 页面)返回 None。raise 会保留原来的 HTTPError,200 回复不会进入 except。诊断结果不是有效的 RPC 响应,也不含日志。

怎么读:

  • BlockVectra 的跨度超限不是 HTTPError:它返回 HTTP 200,并带 error.data.reason: logs_range_too_large,见下一节。
  • 单看 -32602 不能判定跨度过大。换成其他 provider 时,先核对实际的 message、code 和 data,再决定要不要改区间。
  • 遇到 429 或 502,不要缩小区间、不设上限地重试,也不要推进 checkpoint。有限次重试,有 Retry-After 时遵循它,某个分段成功后再往前走。

超出跨度上限的表现

当单次请求的区块跨度 toBlock − fromBlock + 1 超过该链的 max_logs_block_range 时,请求返回 HTTP 200 与 JSON-RPC 错误:

  • 错误码:-32602
  • 错误信息:eth_getLogs block range too large: max <N> blocks
  • 计费状态:不计费。

示例请求

以下请求仅在区块跨度大于目标链当前的 max_logs_block_range 时超限:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_getLogs",
  "params": [
    {
      "fromBlock": "0x45a2409",
      "toBlock": "0x45a27f1"
    }
  ]
}

示例响应

超出上限时返回的错误响应示例:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "eth_getLogs block range too large: max <N> blocks"
  }
}

其中 <N> 为该链的 max_logs_block_range(通过 GET /v1/chains 发布)。

在包含多个调用的批量请求中,如果其中某个 eth_getLogs 跨度过大,该调用同样会在对应位置返回上述 -32602 错误,且该调用不计费。

分段查询实现

当需要检索跨越较长区间的日志时,应先读取目标链的 max_logs_block_range,再将目标区间按照 [from, from + max - 1] 划分为若干段闭区间,按顺序逐段请求并合并结果。

以下示例以 robinhood_mainnet 为例演示分段查询流程:

export BLOCKVECTRA_API_KEY="rgw_your_api_key"

# 1. 从公开接口读取目标链的 max_logs_block_range(免鉴权、不计费)
curl -s "https://api.blockvectra.com/v1/chains"

# 2. 发起单次合规请求:区块跨度(toBlock - fromBlock + 1)不超过该链的 max_logs_block_range
curl -s "https://api.blockvectra.com/v1/robinhood_mainnet" \
  -H 'Content-Type: application/json' \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_getLogs",
    "params": [{
      "address": "0x1111111111111111111111111111111111111111",
      "fromBlock": "0x45a2409",
      "toBlock": "0x45a246c"
    }]
  }'

离线验证响应处理

使用35组模拟响应用例,通过模拟传输层检查分片边界、HTTP/JSON-RPC错误和合法空结果。另见数据格式与预期输出及MIT许可证。

如需检查进度提交、时间起点估计和取消后的旧写入,请下载 v0.1.0 合成评测用例:evm-log-backfill-checkpoints v0.1.0。11 条用例均为教学与评测数据,不是真实链数据,覆盖 checkpoint、timestamp-start 和 writer-fencing 三组 JSONL 配置。release 还包含压缩包、fixtures.manifest.json 与 SHA256SUMS。这些用例不同于上文的 35 组响应用例,也不同于可恢复客户端。它们无需安装、key 或网络,用 Node.js 22 运行。commit-fence 用例只同步建模内存状态,不证明真实数据库的原子性,也不证明持久化。

可恢复的测量客户端(下载)

下载 getlogs-resumable.mjs,使用 Node.js 24 或以上运行。它没有依赖,只调用固定的生产目录与 RPC 端点;先检查 methods.allow、methods.deny,再按实时 max_logs_block_range 切分窗口,并用 eth_chainId 核对目录中的 chain_id。

export BLOCKVECTRA_API_KEY="rgw_your_api_key"
export LOG_ADDRESS="0x1111111111111111111111111111111111111111"
export FROM_BLOCK="0x45a2409"
export TO_BLOCK="0x45a2900"
export CHAIN="robinhood_mainnet"
export CHECKPOINT_FILE="./getlogs-checkpoint.json"
node getlogs-resumable.mjs

可选:设置 CHUNK_BLOCKS(正的十进制整数,例如 CHUNK_BLOCKS=100),让每次请求的区块数小于目录上限。实际跨度为 min(CHUNK_BLOCKS, max_logs_block_range);大于目录上限时按目录上限钳制而不是报错,非法值会在任何请求前停止。跨度更小意味着请求更多、可能产生更多费用,也不保证结果大小。

它只是执行参数,不属于任务身份。同一链、地址、from 和 to 的任务,只改变 CHUNK_BLOCKS 时可继续使用同一 checkpoint 文件;它只作用于尚未保存的区块,已保存分片不会重新请求。total_chunks = 已保存分片数 + 按有效跨度切分剩余区块所需的分片数;20 个分片的预算包含已保存分片,因此恢复时调小 CHUNK_BLOCKS 可能在第一次 eth_getLogs 之前就停止。

三种限制相互独立:区块跨度(max_logs_block_range)、单次请求可返回的日志数量,以及 2 MiB 的响应体上限。某个分片因结果数量或大小限制失败时,客户端不会提交该分片、不会推进、不会自行缩小范围或重试。若要改变窗口、地址或链,须使用新的 checkpoint 文件,并保留原文件;本客户端不会自动覆盖或迁移任务。这不代表任何真实运行已被恢复,也不表示数据无遗漏或已最终确定。

同一 checkpoint 文件只能由一个进程使用。客户端会在 <CHECKPOINT_FILE>.lock 创建锁文件,把每次候选 checkpoint 作为单个 JSON 文档写入权限为 0600 的临时文件,经 fsync 后原子替换;遇到第一个错误即停止,不跳段也不自动重试。崩溃后若残留锁文件,请先确认没有进程占用,再手工删除。checkpoint 的任务绑定、分片顺序、连续性或 nextBlock 不一致时会被拒绝,不会静默重新开始;已存在文件不是合法 checkpoint 对象时会在任何请求前被拒绝,而不会当作缺省文件。整个任务最多保存 20 个分片(含恢复的分片),达到预算时会在请求额外分片前停止。目录的 max_logs_block_range 变化不会使已保存分片失效:只按当前值切分剩余窗口。每个响应和 checkpoint 文件都走单次 2 MiB 上限的有界读取;超限响应会被取消,最后已提交的 checkpoint 保持原样。

status: "complete" 只表示固定请求区间内的每个区块都成功读取,不表示上游数据完整或已最终确定,也不构成 exactly-once 重试。请自行选择已确认的窗口,并用 block hash 或重叠补查独立校验 reorg。这是客户端测量示例:默认运行不会添加内部 QA 后缀。只有内部自动测试显式设置 BLOCKVECTRA_INTERNAL_QA=1(CLI)或在调用库时传入 internalQa: true,才会在 User-Agent 中加上 BlockVectraQA/1 后缀;内部自动测试必须设置。输出报告客户端观测到的 attempt 延迟,按目录、chainID 和 eth_getLogs 分别列出请求数与成功数;new_attempts 只统计 eth_getLogs,已提交分片与 eth_getLogs RPC 成功数分开计数。request_observations 对每次实际 fetch(目录、chainID 和 eth_getLogs)各给一行并按发生顺序排列,因此其计数分母是所有请求,不同于 new_attempts;fetch 之前的任务或 checkpoint 失败不产生行。每行记录 stage、观测到的 HTTP 状态(未知为 null)、实际消费的应用响应体字节数(未读取为 0,读取中断则为部分值,绝不用 Content-Length 或线路大小代替)、body_read_complete(只有读取器发出结束信号后才为 true),以及 outcome:json_received、http_error、transport_error、timeout、invalid_json、response_limit、unreadable_body 或 invalid_response。json_rpc_error_present 在未知时、目录行、非法 JSON 或非对象 RPC 响应体上为 null;其余情况只表示解析后的 RPC 对象是否自有 error 字段,不校验该字段。json_received 只表示已解析出合法 JSON,不代表 RPC 在语义上被接受;只有 stage_successes 和 ok 的 attempt 才统计被接受的阶段与 RPC。HTTP 错误响应体不读取。committed_chunks 是本次运行成功提交的分片数,累计已保存区间 = resumed_chunks + committed_chunks。离线 mock 运行不是生产实测,示例也不发布 p95、p99 或 SLA。

离线核对单个区块

下载 getlogs-reconcile-block.mjs,把 eth_getLogs 的结果与该区块的 receipt 逐条对照,避免部分结果漏掉整笔交易却没人发现。使用 Node.js 24 或以上,无依赖,不发起网络请求。

node getlogs-reconcile-block.mjs block-material.json

文件(不超过 2 MiB)只放一个区块:{ "block": { "hash", "number", "transactions": [txHash] }, "receipts": [完整 receipt RPC 结果,每笔交易一份,顺序不限], "logs": [eth_getLogs 结果], "filter": { "address", "topics" } }。transactions 是完整的交易 hash 列表,不是交易对象。parentHash、status、logsBloom 等其他标准字段会被忽略。按区块顺序串起所有 receipt 后,logIndex 必须从 0 开始连续递增、不缺号不重复,被 filter 排除的日志也一样。filter 可省略:address 为单个地址或数组,topics 每个位置为 null、单个 topic 或 topic 数组。过滤语义参照 go-ethereum 的 filterLogs(空的 address 列表或空的 topic 列表匹配任意值,日志没有的 topics 位置一律不匹配),其他 provider 可能不同。脚本先用它筛选每个 receipt 的日志,再按 block hash、交易 hash 和 log index 与 logs 对照,同时比较 address、topics、data、区块号和交易序号。

输出 { status, reason, counts }:consistent 退出码 0;inconsistent(日志缺失、多出或内容变化)退出码 1;unresolved(字段缺失或格式错误、全块 logIndex 不连续、receipt 没有覆盖区块内全部交易、日志来自其他区块、身份重复、filter 非法)退出码 2。遇到 1 或 2 就停下,把该区间保持为未决。脚本不会读取或写入 checkpoint。consistent 只表示给定的材料彼此一致,不证明区块是 canonical,不校验 receipts root,也不证明历史完整。

批量请求注意事项

若考虑将切分后的多个分段调用打包进单个 JSON-RPC 批量请求,需要遵循批量与突发上限规则:

  • 批量大小限制:批量请求支持 1~100 个调用;超过 100 个调用会被直接拒绝,返回 HTTP 200 与错误码 -32600 batch too large: max 100 calls(不计费)。
  • 单请求突发上限:若单次请求内所有调用的 CU 权重之和超过该 key 的突发容量(burst_cu),请求会被拒绝并返回 HTTP 429 -32022 request cost <N> CU exceeds burst capacity <M> CU(不计费);请拆分成更小的批次。
  • 令牌桶余量不足:若单请求满权重之和未超过突发上限,但当前令牌桶内可用余量不足,服务返回 HTTP 429 与错误码 -32005 rate limit exceeded(带 Retry-After),详见哪些请求不计费:错误码与计费规则。

因此,在大跨度日志查询时,建议采用顺序逐段请求;若使用批量请求,应严格控制批内调用数量,避免满权重之和超出突发上限。

相关接口与计费规则

下一步

最后更新:

本页目录