← 博客
10 分钟阅读
|
2026年10月27日

智能合约事件 API:读取、过滤与订阅

智能合约事件 API 的工作原理:日志、主题与 ABI 解码、历史事件与实时事件的区别,以及何时使用 Webhook、WebSocket 或轮询。

章节
C
Chaingateway Team
区块链专家

事件记录通过过滤器分流进入两条路径

智能合约事件 API 让您的应用程序无需自行扫描每笔交易即可了解合约的行为。您可以通过合约地址和事件签名查询历史日志,然后在新区块到达时订阅新事件。这两者既可以通过原始节点 RPC 实现,也可以通过 ethers.js 等库实现,还可以通过托管 API 实现——该 API 会解码日志并通过 Webhook 推送给您。本指南涵盖各种路径的工作原理、各自的局限性以及如何选择。

TL;DR

  • 合约事件是在交易被挖出时写入的日志条目。您的应用事后读取它。合约本身无法读取它。
  • 历史事件来自日志查询(eth_getLogs 或 queryFilter)。实时事件来自订阅或 Webhook。节点订阅不会重放历史。
  • 只有 indexed 参数可用作过滤器。解码需要合约 ABI。
  • WebSocket 订阅随连接断开而失效,且在处理速度过慢时可能被丢弃。带队列和幂等处理器的 Webhook 在重启后存活能力更强。
  • 托管 API 免去了节点、ABI 和重连工作。这正是 Chaingateway 为支付和代币事件所做的事情。

目录

什么是智能合约事件

事件是合约与外部世界对话的方式。根据 ethereum.org 的说明,事件让合约能够与前端或其他订阅应用程序通信,后者可在交易被验证并添加到区块后处理记录的信息。

由此衍生出三个后果,它们解释了搜索结果中大部分的困惑:

  1. 事件是输出,而非 Solidity 可查询的存储。 合约代码写入日志但无法读回。这就是为什么询问合约如何访问历史事件的帖子总是以“在链下完成”作答。
  2. 事件仅在交易被打包后出现。 仍在内存池中的交易没有日志。
  3. 事件发射成本低、检索成本低,这也是 ERC-20 Transfer 和 Approval 事件、NFT 铸造和 DEX 交换都通过日志追踪而非状态读取的原因。

智能合约事件 API 是指任何能为您的后端提供结构化日志访问能力的工具:节点的 JSON-RPC、封装它的库、索引器,或是向您发送 Webhook 的托管服务。

事件日志内部结构

要正确过滤或解码日志,您需要了解其结构。根据 Solidity ABI 规范,日志包含合约地址、最多四个主题以及一个数据段。匿名事件会省略事件签名哈希。

对于普通事件:

  • 主题 0 是事件签名的 Keccak-256 哈希,例如 Transfer(address,address,uint256)。
  • 主题 1 到 3 存放索引参数。
  • 数据 存放非索引参数,采用 ABI 编码。

ABI 将参数分为索引参数和非索引参数:索引参数作为哈希主题存储,便于高效过滤;非索引参数则作为二进制数据存储在日志中。

有一个值得注意的陷阱。同一规范(在 v0.8.3 版 中)指出,动态长度的索引参数(如字符串和字节数组)存储为特殊编码的 Keccak 哈希,而非原始值。您无法从日志中恢复原始字符串,只能测试候选值是否匹配。

具体示例:ERC-20 Transfer

代币转账日志在主题 0 中包含 Transfer 签名,发送方地址在主题 1,接收方地址在主题 2,金额在数据字段中。如果您想获取某客户地址的所有充值记录,可按主题 0 和主题 2 进行过滤。我们在 ERC-20 Transfer 事件指南 中详细演示了这一过程。

ERC-20 转账日志字段与充值过滤器

读取历史事件

历史读取属于查询操作,而非订阅。标准的 JSON-RPC 方法是 eth_getLogs。它接受一个包含区块范围、可选合约地址和可选主题数组的过滤器对象。Coinbase 开发者文档中关于 eth_getLogs 的参考 展示了其结构:fromBlock、toBlock 和 topics,并指出区块哈希可替代区块范围。

库会对其进行封装。在 ethers.js 中,contract.queryFilter(event, fromBlock, toBlock) 返回匹配的事件,ethers v5 合约文档 说明只有索引参数可用于过滤。将参数留空(null)则匹配任意值。

历史查询的实用原则:

  • 始终限定范围。 许多提供商会限制区块跨度或每次调用的结果数量。分块分页查询(例如每次几千个区块),遇到错误时减半区块跨度。
  • 至少按地址和主题 0 过滤。 无过滤条件的日志查询是触发速率限制的最快途径。
  • 记录已处理的最后一个区块。 此检查点可让您在崩溃后恢复,且不会出现间隙或重复。
  • 使用正确的 ABI 解码。 若仅有主题 0,可查找事件签名;但代理合约可能触发其实现合约中定义的事件,因此应使用实现合约的 ABI。

如果您想复习如何直接与节点交互,我们的 JSON-RPC 指南(含 PHP、Python 和 JavaScript 示例) 涵盖了请求格式。

订阅新事件

实时端的工作方式不同,这也是许多首次实现失败的地方。

go-ethereum 实时事件文档 解释说,订阅使用持久的 WebSocket 或 IPC 连接,通常通过 eth_subscribe,并在节点处理事件时流式传输事件。有两个限制很重要:

  • 无重放。 订阅通知仅涵盖当前事件,不包括过去的事件。如果您的进程停机了十分钟,订阅不会把您错过的内容补发给您。您需要用日志查询来回补。
  • 连接耦合。 订阅绑定到单个连接。连接一旦关闭,其所有订阅也随之关闭。

同一页面还提到,节点会缓冲通知,如果您的客户端处理不够快,连接可能会在缓冲区达到限制时被关闭(Geth 的示例是 10,000)。内联做数据库写入的慢速处理程序正是导致这种情况的原因。

经得起考验的模式

结合两种机制:

  1. 启动时,读取您的检查点区块。
  2. 用 eth_getLogs 从检查点回补到当前链头,分块进行。
  3. 打开订阅,并按交易哈希加日志索引去重。
  4. 把每个事件交给队列并立即确认。在工作进程中处理它。
  5. 断开连接时,回到步骤 1。

即使连接断开(它必然会断开),这也能保持正确性。

重组与确认

您在最新区块看到的日志,若链发生重组可能会消失。对于涉及资金的任何场景,切勿在首次看到时就给用户记账。等待适合该链和金额的确认深度,并处理以太坊风格节点附加在孤儿区块日志上的 “removed” 标志。保留已处理的事件 ID,并让您的处理程序具备幂等性,这样重放或修正后的事件就不会重复记账。

确认链旁的孤儿事件分支

Webhook、WebSocket 还是轮询

三者都能投递事件。它们的区别在于谁持有连接、谁承担故障模式。

方式工作原理优势劣势
轮询您的代码按定时器从上一检查点调用 eth_getLogs简单、无状态、易于恢复延迟等于您的间隔,且消耗请求配额
WebSocket 订阅持久连接从节点流式传输日志延迟最低随连接失效、无重放、有缓冲限制
Webhook服务监控链并向您的端点发送 HTTP POST无需维护长连接、适配无服务器架构需要公网端点、签名校验和去重处理

轮询适合低流量的后台作业。WebSocket 适合必须在一个区块内做出反应且能容忍缺口的仪表盘或机器人。Webhook 适合支付流程,您想要每个事件一次可靠的推送,身后再接一个工作队列。我们关于 Webhook 与 WebSocket 的摄入、排队、扇出模式 的文章更深入地探讨了这些权衡。

正确处理 Webhook 投递

无论是谁发送 Webhook,按相同方式构建接收端:

  • 在解析正文之前验证签名。
  • 快速返回 2xx 响应并异步处理。
  • 使处理程序具有幂等性,以交易哈希和日志索引作为键。
  • 预见重试和乱序交付的情况。
  • 记录失败日志,以便稍后通过日志查询进行对账。

重试行为本身就是一个独立的设计课题。如需深入了解围绕重试和遗漏通知的设计,请参阅这篇关于 支付 webhook 重试处理 的文章。

分步设置事件监控

无论您使用何种工具,以下是针对单个 EVM 合约的最小化实施路径。

  1. 获取 ABI。 从经过验证的合约源码或您自己的构建输出中获取。如果没有 ABI,您看到的将是原始主题(topics)和十六进制数据。
  2. 挑选事件。 列出您关心的签名,例如 Transfer、Approval 或您自定义的 OrderPaid。
  3. 确定过滤器。 合约地址、topic 0 以及任何索引参数(如接收者)。请记住,只有索引值(indexed values)可以被过滤。
  4. 回填数据。 分块查询从部署区块或从您的业务开始日期起的数据。
  5. 正式上线。 通过 WebSocket 订阅,或注册 webhook。
  6. 解码与标准化。 使用代币的 decimals 将十六进制金额转换为数值,并存储区块号、交易哈希、日志索引和合约地址。
  7. 等待最终确认。 将事件标记为待处理(pending),然后标记为已确认(confirmed)。
  8. 监控监控器。 当没有事件到达的时间超过链的正常间隔,或当您的检查点落后于最新区块(head)时发出警报。

跨链事件

EVM 的主题和 ABI 解码数据模型适用于 Ethereum、Polygon、BNB Chain 和 Arbitrum,因此一个解码器可以通过不同的 RPC 终端和确认规则在这些链之间通用。其他网络不共享此模型。Bitcoin 完全没有合约日志,因此您需要监控地址和交易。Tron 和 Solana 有自己的格式和 API。一些 Cosmos 风格的堆栈则有所不同:Tendermint v0.36 弃用了其传统的流式 API,转而采用事件日志 API,客户端通过带有游标的长轮询进行查询。

实际结果是,一个多链产品最终需要为每个网络家族维护一个适配器,每个适配器都有自己的重连、解码和最终确认代码。这是大多数团队低估的部分。有关选项地图,请参阅我们对 六种区块链 API 类型 的比较以及关于 区块链 API 与运行节点 的解释。

自行构建还是使用托管 API

运行自己的技术栈意味着需要一个节点(或付费 RPC 终端)、一个日志扫描器、一个 ABI 注册表、一个重组(reorg)处理程序、一个队列和一个警报设置。如果事件是您的核心产品,或者您需要在数千个合约中进行自定义索引,那么这样做是合理的。

如果事件只是实现目标的手段(例如入账存款或确认订单),那么这样做就不那么合理。在这种情况下,您希望解码后的事件被交付到您控制的终端,而无需操作其他任何内容。

我们的观点

我们看到的大多数事件处理 Bug 都与读取日志无关。它们与日志周围发生的事情有关:部署期间遗漏的通知、重试后的重复入账、针对某个代币错误的精度转换,或者在夜间悄悄断开的连接。

所以我们围绕三个信念构建产品。首先,应用应当接收解码后的结构化数据,而不是需要自行解读的原始 topics。其次,对于支付场景,推送优于轮询,前提是每条消息都经过签名且可验证。第三,开发者不应为了仅仅知道一笔支付已到账,而不得不运行节点或维护特定链的端点同步。

这就是为什么 Chaingateway 通过一个 REST API 覆盖 Ethereum、Bitcoin、Tron、Solana、Polygon、BNB Chain 和 Arbitrum,提供预解码的交易数据、自动 Gas 估算和 HMAC 签名请求。Webhooks 构建在其之上,因此您的处理程序能收到事件,而您的团队可将精力集中在产品上。无论您选择哪种方式,请保留检查点、幂等键和确认规则。这三者决定了您的账目是否正确。

— Bitblade

无需运行节点即可获取事件

如果您的目标是在应用中获取支付和代币事件,我们的 Chaingateway Webhooks 可将实时区块链通知推送至您的端点,而 区块链 API 则通过统一接口提供解码后的交易数据、钱包、转账和费用估算。套餐起步价为 Plus 版每月 €49,Pro 版 €99,Premium 版 €249,Enterprise 版 €499。按年计费分别为 €490、€990、€2,490 和 €4,990。详见 定价页面 了解各层级包含内容。如需针对特定链的示例,请阅读 无需轮询的 Polygon Webhooks。

常见问题

智能合约能读取自己的历史事件吗?

不能。合约只能发射日志,事后无法访问,因此事件数据面向链下消费者。如果您的合约需要在链上获取历史数据,请改为将数值存储在合约状态中,代价是更高的 Gas 成本。

服务离线期间错过的事件如何补齐?

从您最后处理的区块到当前最新区块运行日志查询。节点订阅仅推送当前事件,重新连接不会重放任何内容。在处理完每个区块后存储检查点,并通过交易哈希和日志索引去重。

为什么无法按事件中的某个参数过滤?

只有 indexed 参数会作为 topics 存储,且仅 topics 可被过滤。非 indexed 数值位于 data 字段,因此您必须先获取日志,再在自己的代码中过滤。如果您控制该合约,请将需要搜索的字段标记为 indexed,每个事件最多三个。

支付通知应使用 Webhooks 还是 WebSockets?

对于支付场景,带队列的 Webhooks 通常更易运维,因为无需维护可能断开的长连接。当亚秒级响应至关重要且您能自行回补缺口时,请使用 WebSockets。无论哪种方式,都要幂等处理事件,并在给账户记账前等待足够的确认数。

来源

推荐

准备好自己动手构建了吗? 获取你的 API key — 7 天试用,无需信用卡 — 或查看 区块链 API 获取完整的 endpoint 参考。

C
Chaingateway Team
区块链专家

Chaingateway 团队致力于为全球开发者简化区块链集成。