Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 13 分钟

eth_getBlockReceipts:一次调用批量获取区块收据

通过一次 eth_getBlockReceipts 调用获取区块内所有交易收据,按索引将收据与交易关联,避免数百次逐笔交易的往返请求。

TL;DR

eth_getBlockReceipts(blockParameter) 在一次 JSON-RPC 往返请求中返回区块内所有交易收据的数组,使用与 eth_getBlockByNumber 相同的区块参数形式(区块号、标签或 32 字节哈希)。收据是交易执行后的摘要——包含 status、cumulativeGasUsed、gasUsed、effectiveGasPrice、logsBloom、logs、contractAddress 和 type——区块的 receiptsRoot 对所有收据做出承诺。逐笔交易循环调用 eth_getTransactionReceipt 会使往返请求和速率限制压力随交易数量成倍增加,因此批量获取更适合索引器和逐块分析流程。方法可用性取决于客户端和提供商,因此应在运行时检测支持情况,并在批量方法返回 method-not-found 时降级为逐笔交易调用。

什么是收据,以及为什么区块对所有收据做出承诺

交易收据是交易执行后的摘要。它告诉你交易是成功还是回滚、消耗了多少 gas、每单位 gas 支付了多少费用、发出了哪些日志,以及是否部署了合约。Ethereum execution-apis JSON-RPC 规范定义了收据对象的字段,ethereum.org 的 JSON-RPC API 列表也记录了相同的结构供公众使用。

对分析最重要的字段包括 status(1 表示成功,0 表示回滚)、cumulativeGasUsed(区块中截至并包含该交易的所有交易消耗的 gas)、gasUsed(仅该交易消耗的 gas)、effectiveGasPrice(EIP-1559 之后实际支付的每单位 gas 价格)、logsBloom(区块日志的概率性过滤器)、logs(发出的事件记录)、contractAddress(交易创建合约时设置)和 type(交易类型)。

每个区块头都包含一个 receiptsRoot,这是一个 Merkle-Patricia 根,对区块中的每个收据做出承诺。正是这种承诺使收据成为区块执行结果的权威证明:如果你拥有收据,就可以验证区块实际做了什么,而不仅仅是它包含哪些交易。要更全面地了解这些调用如何融入节点设置,请参阅选择以太坊 RPC 节点

  • status:1 = 成功,0 = 回滚;大多数索引器首先判断的字段。
  • gasUsed 与 cumulativeGasUsed:单笔交易消耗与区块内累计消耗。
  • effectiveGasPrice:EIP-1559 之后实际支付的价格,用于计算 feePaid。
  • logs 和 logsBloom:发出的事件以及区块级别的布隆过滤器。
  • contractAddress:仅当交易部署合约时非空。
  • type:交易类型,在混合传统交易和类型化交易时很有用。

eth_getBlockReceipts:一次调用获取所有收据

eth_getBlockReceipts(blockParameter) 返回由 blockParameter 标识的区块的所有收据数组。该参数接受与 eth_getBlockByNumber 相同的形式:区块号、latest、safe 或 finalized 等标签,或 32 字节区块哈希。响应是按交易索引排序的数组,因此 receipt[i] 对应区块中索引为 i 的交易。

另一种模式是用 eth_getBlockByNumber 获取区块,读取其 transactions 数组,然后对每个交易哈希调用一次 eth_getTransactionReceipt。这可行,但会使往返请求和速率限制压力随交易数量成倍增加。一个有 200 笔交易的区块会变成 1 + 200 次调用,而不是 1 + 1 次。对于索引器或区块处理器来说,这种差异会在你处理的每个区块上累积。

方法可用性取决于客户端和提供商。一些较旧的客户端和某些托管端点没有公开 eth_getBlockReceipts,支持情况可能因网络和提供商配置而异。应将其视为需要在运行时检测的能力,而不是想当然。OnFinality 学习中心(OnFinality Learn hub)涵盖了相关的方法级指南,包括使用 eth_getLogs 和 topics 过滤事件日志,这是收据获取在日志方面的对应内容。

  • 批量:1 次区块调用 + 1 次收据调用 = 每个区块 2 次往返请求。
  • 逐笔交易:1 次区块调用 + N 次收据调用 = 每个区块 1 + N 次往返请求。
  • 排序:收据按交易索引顺序排列;按索引或 receipt.transactionHash 配对。
  • 不要假设日志按合约或主题分组;它们按交易和发出顺序排列。

何时使用批量收据与逐笔交易收据

当你处理整个区块并需要所有收据时,使用 eth_getBlockReceipts:索引器、分析流程、区块浏览器、gas 使用仪表盘以及感知重组的历史回填。批量调用在一次往返请求中提供完整集合,这是逐块工作的有效形式。

当你已经拥有特定的交易哈希并且只需要该收据时,使用 eth_getTransactionReceipt——例如确认用户提交的交易、检查单个合约部署,或在发送交易后轮询收据。在这种情况下,逐笔交易调用是正确的工具,批量调用反而浪费。

对于实时流式处理,日志订阅或回填设计仍然可能优于两者,因为它会在事件发生时推送事件,而不是轮询区块。批量收据是一种拉取模式;如果你的工作负载是实时且事件驱动的,订阅加缺口回填通常是更好的架构。使用 trace 和 debug 命名空间进行以太坊交易追踪指南涵盖了当你需要内部调用追踪时位于收据之下的更深层内省调用。

  • 批量收据:整块处理、分析、浏览器、重组回填。
  • 逐笔交易收据:单哈希确认、面向用户的状态检查。
  • 订阅/回填:推送优于拉取的实时事件流。
  • 追踪:当收据不够、需要内部调用细节时使用。

实用的区块处理模式:获取、关联、输出

模式很直接:对于每个区块,并行调用 eth_getBlockByNumber(withTransactions=true)和 eth_getBlockReceipts,然后按索引关联。从关联后的数据中,你可以输出逐笔交易行(status、gasUsed、effectiveGasPrice、feePaid = gasUsed * effectiveGasPrice、日志数量、合约创建)和逐块聚合(总 gas 使用量、总费用、成功/回滚计数、日志数量)。

按索引关联是最简单的方法,但你还应该用 receipt.transactionHash 对照 block.transactions[i].hash 进行验证。如果两者不一致,你很可能遇到了重组或区块参数不匹配,应该重新获取而不是输出错误行。

对于分析流程,这种模式优于 N 次收据调用,因为它使每个区块的往返请求保持恒定,与交易数量无关。对于实时场景,它仍然不如日志订阅/回填设计,因为它是轮询而不是流式。关于端点选择和健康检查,请参阅监控 RPC 端点和节点健康

  • 并行执行区块和收据调用以减少实际耗时。
  • 按索引关联,然后用 receipt.transactionHash 验证。
  • 从关联集合中输出逐笔交易行和逐块聚合。
  • 哈希不匹配时重新获取,而不是输出不一致的行。

可运行的 Node.js:获取区块及其收据、关联、打印表格

下面的脚本使用现代 Node.js 内置的 fetch。它并行调用 eth_getBlockByNumber 和 eth_getBlockReceipts,按索引将收据与交易关联,并打印包含 status、gas used、effective gas price、fee paid、日志数量和合约创建情况的表格。将 RPC URL 替换为你自己的端点。

用 node script.js 运行。如果你的端点不支持 eth_getBlockReceipts,脚本会暴露错误,你可以切换到下一节描述的降级模式。

const RPC_URL = process.env.RPC_URL || 'https://your-endpoint.example';

async function rpc(method, params) {
  const res = await fetch(RPC_URL, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(`${method}: ${json.error.message}`);
  return json.result;
}

function hexToBigInt(hex) {
  return hex ? BigInt(hex) : 0n;
}

async function processBlock(blockParam) {
  const [block, receipts] = await Promise.all([
    rpc('eth_getBlockByNumber', [blockParam, true]),
    rpc('eth_getBlockReceipts', [blockParam])
  ]);

  if (!block) throw new Error('Block not found: ' + blockParam);
  if (!receipts) throw new Error('No receipts returned for ' + blockParam);

  const rows = receipts.map((r, i) => {
    const tx = block.transactions[i];
    const gasUsed = hexToBigInt(r.gasUsed);
    const effGasPrice = hexToBigInt(r.effectiveGasPrice);
    const feePaid = gasUsed * effGasPrice;
    return {
      index: i,
      hash: r.transactionHash,
      matchesBlockTx: tx && tx.hash.toLowerCase() === r.transactionHash.toLowerCase(),
      status: parseInt(r.status, 16),
      gasUsed: gasUsed.toString(),
      effectiveGasPrice: effGasPrice.toString(),
      feePaidWei: feePaid.toString(),
      logCount: r.logs ? r.logs.length : 0,
      contractCreated: r.contractAddress || null
    };
  });

  const totalGas = rows.reduce((a, r) => a + BigInt(r.gasUsed), 0n);
  const totalFees = rows.reduce((a, r) => a + BigInt(r.feePaidWei), 0n);
  const reverts = rows.filter(r => r.status === 0).length;

  console.log(`Block ${block.number} txs=${rows.length} reverts=${reverts}`);
  console.log(`totalGasUsed=${totalGas} totalFeesWei=${totalFees}`);
  console.table(rows.map(r => ({
    i: r.index,
    status: r.status,
    gasUsed: r.gasUsed,
    effGasPrice: r.effectiveGasPrice,
    feePaidWei: r.feePaidWei,
    logs: r.logCount,
    contract: r.contractCreated ? 'yes' : ''
  })));
}

processBlock('latest').catch(err => {
  console.error('Failed:', err.message);
  process.exit(1);
});

检测支持情况并降级为逐笔交易收据

由于 eth_getBlockReceipts 并非普遍可用,应在运行时检测支持情况。最干净的方法是尝试批量调用并捕获 method-not-found 错误,然后降级为逐笔交易收据调用。缓存能力检测结果,这样就不必在每个区块上付出检测成本。

降级方案会从区块的 transactions 数组中为每个交易哈希调用一次 eth_getTransactionReceipt。这就是 1 + N 模式,它正确但成本更高。如果你的端点始终缺少批量方法,请考虑其他端点或提供商是否更适合你的工作负载;RPC 定价API 服务页面描述了 OnFinality 如何组织访问,以太坊网络列出了可用的网络端点。

健壮的降级方案还应处理部分失败:如果某次逐笔交易调用失败,应使用退避重试,而不是丢弃整个区块。记录哪些区块使用了降级方案,以便日后切换端点时可以重新处理。

  • 先尝试批量;捕获 method-not-found 后切换到逐笔交易调用。
  • 按端点缓存能力检测结果,避免重复检测。
  • 对单笔交易失败使用退避重试,而不是丢弃区块。
  • 记录降级使用情况,以便长期审计端点能力。

针对你自己的端点测量成本:待填写的测量结果表

不要相信通用的性能声明;针对你自己的端点进行测量。比较很简单:批量收据每个区块花费 1 次区块调用 + 1 次收据调用,而逐笔交易收据花费 1 次区块调用 + N 次收据调用,其中 N 是交易数量。往返请求的比率大致为 (1 + N) / 2,随区块大小增长。

用两种模式运行同一个区块,记录实际耗时、请求数量和任何速率限制响应。用你自己的测量结果填写下表。提供商特定的延迟和速率限制因提供商而异,因此你的数据才是容量规划中真正重要的。

一个有用的次要测量指标是传输字节数:批量响应在一个负载中包含所有收据,可能比单个响应更大,但避免了每次调用的开销。注意超大区块上提供商的响应大小限制。

  • 往返请求(批量):每个区块 2 次,与交易数量无关。
  • 往返请求(逐笔):每个区块 1 + N 次,随交易数量增长。
  • 测量:实际耗时毫秒、请求数、速率限制错误、响应字节数。
  • 在每次测量旁边记录区块号和交易数量,以便比较。
| Block | Tx count | Pattern | Requests | Wall-clock ms | Rate-limit errors | Response bytes |
|-------|----------|---------|----------|---------------|-------------------|----------------|
|       |          | bulk    |          |               |                   |                |
|       |          | per-tx  |          |               |                   |                |
|       |          | bulk    |          |               |                   |                |
|       |          | per-tx  |          |               |                   |                |

故障模式:不支持的方法、空区块、重组和限制

不支持的方法:一些客户端和托管端点对 eth_getBlockReceipts 返回 method-not-found 错误。应显式处理并降级为逐笔交易调用,而不是让管道崩溃。

待处理或不可用区块返回 null:如果你请求的区块尚不存在或在你的节点上不可用,调用可能返回 null。将 null 视为稍后重试或跳过的信号,而不是空收据集。待处理区块也可能还没有收据。

被重组掉的区块的收据:如果发生重组,你为旧区块获取的收据可能不再对应规范链。通过重新获取区块并比较哈希来验证,并重新处理受影响的范围。对于具有异步执行语义的网络,收据状态时机可能不同;相关讨论请参阅 Monad 交易生命周期与异步执行收据状态

对未最终确定的区块使用区块哈希:区块哈希标识特定区块,但如果该区块后来被重组掉,该哈希可能不再解析。对于稳定处理,优先使用 finalized 等标签,并将 latest 或 safe 视为临时状态。提供商的区块范围和响应大小限制也可能在超大区块或宽范围上导致失败;请检查提供商文档中记录的限制。

  • method-not-found:降级为逐笔交易收据调用。
  • null 结果:区块待处理或不可用;重试或跳过。
  • 重组:重新获取并重新处理受影响的范围。
  • 未最终确定的哈希:优先使用 finalized 标签进行稳定处理。
  • 提供商限制:区块范围和响应大小上限因提供商而异。

局限性与权衡:收据与日志、归档要求

当你需要跨多个区块按主题或地址过滤时,收据不能替代 eth_getLogs。eth_getLogs 专为日志过滤设计,可以高效扫描范围,而收据提供单个区块的完整逐笔交易情况。使用收据获取逐块执行结果,使用日志进行跨区块事件查询。

旧区块可能需要归档节点。如果你的端点未启用归档,历史收据获取可能会失败,或对超出修剪范围的区块返回错误。在回填历史之前,请确认端点的归档能力。

安全性和正确性权衡:收据是权威的执行摘要,但当需要密码学保证时,你仍应验证 receiptsRoot。对于大多数分析工作负载,信任节点响应是可以接受的;对于高保证系统,应针对区块头进行验证。另请注意 logsBloom 是概率性的,不能替代扫描日志。

  • 收据:逐块执行结果;日志:跨区块事件过滤。
  • 归档要求:历史收据可能需要启用归档的端点。
  • receiptsRoot:需要密码学保证时进行验证。
  • logsBloom:概率性过滤器,不能替代日志扫描。

批量收据获取的故障排查清单

当 eth_getBlockReceipts 行为不符合预期时,按顺序检查以下各项。大多数问题属于少数几类:方法支持、区块可用性、重组时机和提供商限制。

首先用针对 latest 的简单调用确认端点支持该方法。如果失败并返回 method-not-found,则切换到降级方案。如果成功但返回 null,请检查区块参数是否有效以及区块在你的节点上是否可用。如果收据看起来过时或不匹配,请检查是否发生重组并重新获取。

  • 用 latest 区块调用确认方法支持。
  • 验证区块参数形式(区块号、标签或 32 字节哈希)。
  • 检查 null 结果,区分待处理和不可用。
  • 用 block.transactions[i].hash 验证 receipt.transactionHash。
  • 不匹配时重新获取以处理重组。
  • 检查提供商对超大区块的区块范围和响应大小限制。
  • 确认历史区块的归档能力。
  • 记录降级使用情况和速率限制错误,用于容量规划。

下一步:构建收据驱动的管道

批量收据就绪后,下一步是决定管道如何处理实时数据与历史数据。对于实时数据,考虑日志订阅设计并配合缺口回填;对于历史分析,逐块批量收据是高效的形式。OnFinality 学习中心(OnFinality Learn hub)有关于日志、追踪和监控的相关指南,可与本文互补。

如果你正在选择端点,请查看选择以太坊 RPC 节点以太坊网络页面。关于访问和定价详情,请参阅 RPC 定价API 服务页面。在确定设计之前,先测量你自己的往返请求和速率限制行为,并保留降级路径,以便管道在缺少批量方法的端点上也能够存活。

  • 实时:订阅加回填;历史:逐块批量收据。
  • 为没有批量方法的端点保留逐笔交易降级方案。
  • 针对你自己的端点测量往返请求和速率限制行为。
  • 在扩展之前审查端点选择和定价。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始