第四节:RPC接口实战——JSON-RPC基础、eth_call、eth_getLogs、eth_getTransactionReceipt

各位同学,今天咱们来聊点硬核的。RPC接口,说白了就是你和区块链节点对话的「翻译官」。你发一个请求过去,节点把链上的数据打包好扔回来。我刚开始接触这个的时候,总觉得它很神秘,后来发现其实没那么复杂。

我个人习惯把RPC接口分成三类:查询类、交易类、日志类。今天咱们重点讲三个最常用的:eth_calleth_getLogseth_getTransactionReceipt。这三个接口,基本上覆盖了90%的链上数据查询场景。

JSON-RPC 基础

先说说JSON-RPC是什么。它就是一个轻量级的远程调用协议,用JSON格式传输数据。你想想看,以太坊节点遍布全球,怎么让它们听懂你的指令?就是靠这个协议。

一个标准的JSON-RPC请求长这样:

{
  "jsonrpc": "2.0",
  "method": "eth_blockNumber",
  "params": [],
  "id": 1
}

嗯,这里要注意几个关键字段:

  • jsonrpc:版本号,目前固定是2.0
  • method:你要调用的方法名
  • params:参数列表,可以是数组或对象
  • id:请求ID,用于匹配响应

返回的响应格式也很固定:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x10c4f2"  // 区块高度,十六进制
}
小技巧: 我个人习惯用curl命令直接测试RPC接口,比如:curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' https://mainnet.infura.io/v3/YOUR_PROJECT_ID

eth_call:链上只读查询的瑞士军刀

eth_call 是我用得最多的接口。它可以在不发送交易的情况下,模拟执行一个智能合约的函数调用。说白了,就是「只读查询」。

为什么需要它?你想想看,如果你想知道某个地址在Uniswap池子里的余额,难道要发一笔交易去查吗?当然不用。用 eth_call 直接查,不花Gas,不改变链上状态。

它的参数结构是这样的:

{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [
    {
      "to": "0x...",           // 合约地址
      "data": "0x..."          // 编码后的函数调用数据
    },
    "latest"                   // 区块高度或标签
  ],
  "id": 1
}

这里最核心的就是 data 字段。它需要你把函数签名和参数进行ABI编码。比如你要调用 balanceOf(address) 这个函数:

// 函数选择器:keccak256("balanceOf(address)") 的前4字节
// 参数:地址左补零到32字节
data = 0x70a08231 + 000000000000000000000000 + 你的地址(去掉0x,左补零到40位)
避坑指南: 我曾经在项目里犯过一个低级错误——把地址直接拼在data后面,忘了左补零。结果查出来的余额永远是0。排查了半天才发现是编码问题。所以,地址一定要左补零到64位十六进制字符

当然,现在有很多库帮你做编码,比如ethers.js的 contract.balanceOf(address) 底层就是调用了 eth_call。但理解原理很重要,万一哪天库出bug了,你还能手动拼。

eth_getTransactionReceipt:交易收据的宝藏

每当你发送一笔交易,矿工打包后就会生成一个交易收据。这个收据里藏着大量信息:交易是否成功、消耗了多少Gas、触发了哪些事件日志等等。

它的调用方式很简单:

{
  "jsonrpc": "2.0",
  "method": "eth_getTransactionReceipt",
  "params": ["0x交易哈希"],
  "id": 1
}

返回的数据结构里,我最关注这几个字段:

字段 说明 我的用法
status 交易状态,0x0失败,0x1成功 先检查这个,失败就不往下看了
gasUsed 实际消耗的Gas 用来估算合约调用的成本
logs 触发的事件日志列表 解析事件数据,后面会细讲
blockNumber 交易被打包的区块高度 确认交易上链的时间点
注意: 交易收据只有在交易被打包后才会生成。如果你查询一个还没上链的交易哈希,返回的是 null。我刚开始做监控系统时,没处理这个null值,结果程序直接崩了。所以记得加个判空逻辑。

eth_getLogs:链上事件的时光机

这个接口是我做数据分析时的最爱。它可以让你按条件查询历史事件日志,就像坐时光机回到过去看链上发生了什么。

它的参数比较灵活:

{
  "jsonrpc": "2.0",
  "method": "eth_getLogs",
  "params": [{
    "address": "0x合约地址",      // 可选,过滤特定合约
    "fromBlock": "0x...",        // 起始区块
    "toBlock": "0x...",          // 结束区块
    "topics": ["0x..."]          // 可选,按事件签名过滤
  }],
  "id": 1
}

这里有个关键点:区块范围不能太大。我记得有一次,我想查一整年的Uniswap交易记录,直接设了fromBlock为0,toBlock为latest。结果节点直接超时了。后来才知道,大部分节点对单次查询的区块范围有限制,比如Infura限制是10000个区块。

那怎么办?分段查。比如每5000个区块查一次,然后合并结果。代码大概长这样:

async function getLogsInRange(contract, fromBlock, toBlock) {
  const step = 5000;
  let logs = [];
  for (let start = fromBlock; start < toBlock; start += step) {
    const end = Math.min(start + step - 1, toBlock);
    const batch = await provider.getLogs({
      address: contract,
      fromBlock: start,
      toBlock: end,
      topics: [eventSignature]
    });
    logs = logs.concat(batch);
  }
  return logs;
}
实战经验: 我在做DeFi清算监控时,就是靠 eth_getLogs 实时监听清算事件。每5秒查一次最新区块的日志,一旦发现有清算事件,立即触发套利策略。这个接口的响应速度直接决定了你的套利成功率。

三个接口的配合使用

在实际项目中,这三个接口经常配合使用。我给你画个流程图,你就明白了:

RPC接口实战流程 eth_getLogs 获取事件日志 eth_getTransactionReceipt 验证交易状态 eth_call 查询链上状态 典型场景:监控某个DeFi协议的清算事件 实战场景:清算监控 1. eth_getLogs → 监听清算事件日志 2. eth_getTransactionReceipt → 确认清算交易成功 3. eth_call → 查询清算后的价格/余额,决定是否套利

你看,这三个接口串起来,就是一个完整的数据链路。先用 eth_getLogs 发现事件,再用 eth_getTransactionReceipt 确认交易细节,最后用 eth_call 查询链上最新状态。

实战中的注意事项

最后,我分享几个实战中踩过的坑:

  • 速率限制: 大部分RPC提供商都有速率限制。我曾经在半小时内发了10万次请求,直接被封IP。解决方案是加本地缓存,或者用多个RPC端点轮询。
  • 区块确认数: 查询交易收据时,建议等几个区块确认后再查。我遇到过刚打包就查,结果返回null的情况。一般等12个区块确认比较稳妥。
  • 日志解析: eth_getLogs 返回的日志数据是十六进制编码的,需要根据合约ABI来解码。ethers.js的 parseLog 方法可以帮你省不少事。
  • 错误处理: RPC请求可能因为网络问题、节点问题而失败。一定要加重试机制,我一般重试3次,每次间隔1秒。
核心总结: JSON-RPC是链上数据的入口,eth_call 负责查询状态,eth_getTransactionReceipt 负责验证交易,eth_getLogs 负责追溯事件。掌握这三个接口,你就能从链上获取90%以上的数据。

好了,这一节的内容就到这里。记住,理论再强也不如动手敲一遍。找个测试网,拿这三个接口练练手,你会发现链上数据其实没那么神秘。

专注资料整理