第四节:RPC接口实战——JSON-RPC基础、eth_call、eth_getLogs、eth_getTransactionReceipt
各位同学,今天咱们来聊点硬核的。RPC接口,说白了就是你和区块链节点对话的「翻译官」。你发一个请求过去,节点把链上的数据打包好扔回来。我刚开始接触这个的时候,总觉得它很神秘,后来发现其实没那么复杂。
我个人习惯把RPC接口分成三类:查询类、交易类、日志类。今天咱们重点讲三个最常用的:eth_call、eth_getLogs、eth_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 -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位)
当然,现在有很多库帮你做编码,比如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;
}
eth_getLogs 实时监听清算事件。每5秒查一次最新区块的日志,一旦发现有清算事件,立即触发套利策略。这个接口的响应速度直接决定了你的套利成功率。
三个接口的配合使用
在实际项目中,这三个接口经常配合使用。我给你画个流程图,你就明白了:
你看,这三个接口串起来,就是一个完整的数据链路。先用 eth_getLogs 发现事件,再用 eth_getTransactionReceipt 确认交易细节,最后用 eth_call 查询链上最新状态。
实战中的注意事项
最后,我分享几个实战中踩过的坑:
- 速率限制: 大部分RPC提供商都有速率限制。我曾经在半小时内发了10万次请求,直接被封IP。解决方案是加本地缓存,或者用多个RPC端点轮询。
- 区块确认数: 查询交易收据时,建议等几个区块确认后再查。我遇到过刚打包就查,结果返回null的情况。一般等12个区块确认比较稳妥。
- 日志解析:
eth_getLogs返回的日志数据是十六进制编码的,需要根据合约ABI来解码。ethers.js的parseLog方法可以帮你省不少事。 - 错误处理: RPC请求可能因为网络问题、节点问题而失败。一定要加重试机制,我一般重试3次,每次间隔1秒。
eth_call 负责查询状态,eth_getTransactionReceipt 负责验证交易,eth_getLogs 负责追溯事件。掌握这三个接口,你就能从链上获取90%以上的数据。
好了,这一节的内容就到这里。记住,理论再强也不如动手敲一遍。找个测试网,拿这三个接口练练手,你会发现链上数据其实没那么神秘。