2、区块链节点交互:JSON-RPC协议详解、连接以太坊节点(Infura/Alchemy)、使用ethers.js与web3.js
好,我们直接进入正题。
区块链节点交互,说白了就是你的后端程序怎么跟链上“说话”。你写的智能合约部署在链上,你的后端服务要读取链上数据、发送交易、监听事件——所有这些操作,都得通过节点来完成。
我个人习惯把节点交互比作“数据库连接”。你写后端不可能直接操作硬盘文件,而是通过 MySQL 客户端发 SQL 语句。区块链也一样,你通过 JSON-RPC 协议向节点发指令,节点帮你执行并返回结果。
2.1 JSON-RPC 协议:区块链的“SQL”
JSON-RPC 是一种轻量级的远程调用协议。它基于 JSON 格式,通过 HTTP 或 WebSocket 传输。以太坊节点暴露的就是 JSON-RPC 接口。
一个典型的请求长这样:
{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}
节点返回:
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x10d4f2a"
}
注意,返回的区块号是十六进制字符串。嗯,这里要注意:以太坊很多数值都用十六进制表示,比如余额、Gas 价格、nonce 等。你直接拿字符串做算术运算会出问题,得先转成十进制。
常用的 JSON-RPC 方法有哪些?我列几个你一定会用到的:
| 方法名 | 作用 | 参数示例 |
|---|---|---|
eth_blockNumber |
获取最新区块号 | 无参数 |
eth_getBalance |
查询地址余额 | ["0x地址", "latest"] |
eth_call |
调用合约只读方法 | [交易对象, 区块号] |
eth_sendRawTransaction |
发送已签名的交易 | ["0x签名后的交易数据"] |
eth_getTransactionReceipt |
获取交易收据 | ["0x交易哈希"] |
eth_getLogs |
查询事件日志 | [过滤对象] |
你想想看,这些方法其实就是你操作链上数据的“API”。你不需要知道节点内部怎么同步区块、怎么执行交易,你只需要发 JSON 过去,等 JSON 回来就行。
2.2 连接以太坊节点:Infura vs Alchemy
自己跑一个以太坊全节点?说实话,不推荐。全节点要同步几百 GB 的数据,还要保持 24 小时在线,运维成本很高。我建议直接用第三方节点服务商。
目前主流的两家是 Infura 和 Alchemy。它们都提供免费的开发套餐,足够你学习和做小项目。
我的个人经验: 早期我用 Infura 比较多,因为它出现得早,文档也全。但后来我发现 Alchemy 的开发者体验更好——它的 Dashboard 能看到实时请求量、错误率,还有 WebSocket 的稳定性明显优于 Infura。如果你做生产环境,我建议优先考虑 Alchemy。
连接方式很简单。你注册账号后,会得到一个项目 ID 或 API Key。然后拼出 RPC URL:
// Infura
https://mainnet.infura.io/v3/YOUR_PROJECT_ID
// Alchemy
https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
用 curl 测试一下:
curl -X POST https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
如果返回了区块号,恭喜你,你已经成功连接到了以太坊主网。
2.3 使用 ethers.js:更现代的选择
直接裸写 JSON-RPC 请求太原始了。在实际项目中,我们会用 SDK 来封装这些调用。目前最流行的是 ethers.js 和 web3.js。
我个人强烈推荐 ethers.js。为什么?因为它更轻量、API 设计更清晰、对 TypeScript 支持更好。而且它把很多坑都帮你填了——比如上面提到的十六进制转换问题,ethers.js 内部会自动处理。
安装:
npm install ethers
连接节点:
const { ethers } = require("ethers");
// 使用 Infura
const provider = new ethers.providers.JsonRpcProvider(
"https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
);
// 或者使用 Alchemy
const provider = new ethers.providers.JsonRpcProvider(
"https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
);
// 查询最新区块号
async function getBlockNumber() {
const blockNumber = await provider.getBlockNumber();
console.log("当前区块号:", blockNumber);
}
getBlockNumber();
你看,代码简洁多了。ethers.js 把 JSON-RPC 的细节都封装好了,你只需要调用 JavaScript 方法就行。
再比如查询余额:
const balance = await provider.getBalance("0x地址");
// balance 是一个 BigNumber,需要格式化
console.log("余额 (ETH):", ethers.utils.formatEther(balance));
这里有个细节:getBalance 返回的是 BigNumber 类型,不是普通数字。因为以太坊的余额单位是 wei(1 ETH = 10^18 wei),普通 JavaScript 数字会溢出。ethers.js 用 BigNumber 来保证精度。
balance.toString() 转成字符串然后做算术运算。BigNumber 有自己的加减乘除方法:balance.add()、balance.mul() 等。我见过有人直接用 + 运算符,结果精度丢失,对账对不上。
2.4 使用 web3.js:老牌但依然能打
web3.js 是以太坊基金会官方维护的 SDK,历史更久,生态更成熟。如果你维护的是老项目,或者需要跟一些只支持 web3.js 的工具集成,那还是得用它。
安装:
npm install web3
连接节点:
const Web3 = require("web3");
const web3 = new Web3("https://mainnet.infura.io/v3/YOUR_PROJECT_ID");
// 查询区块号
web3.eth.getBlockNumber().then(console.log);
// 查询余额
web3.eth.getBalance("0x地址").then((wei) => {
console.log("余额 (ETH):", web3.utils.fromWei(wei, "ether"));
});
web3.js 的 API 风格跟 ethers.js 有些不同。比如它用 web3.utils.fromWei 来转换单位,而 ethers.js 用 ethers.utils.formatEther。本质上是一样的,但命名习惯不同。
我个人觉得 web3.js 的文档有点乱,方法命名也不够直观。比如获取交易收据,ethers.js 叫 provider.getTransactionReceipt(),web3.js 叫 web3.eth.getTransactionReceipt()——多了一层 eth 命名空间。不过用习惯了也还好。
2.5 实战:监听新区块和事件
光会查询还不够,很多时候你需要实时监听链上变化。比如你的 DApp 要监听用户存款事件,或者监听新区块来更新价格预言机。
ethers.js 的监听方式:
// 监听新区块
provider.on("block", (blockNumber) => {
console.log("新区块:", blockNumber);
});
// 监听合约事件
const contract = new ethers.Contract(contractAddress, abi, provider);
contract.on("Transfer", (from, to, value, event) => {
console.log(`转账: ${from} -> ${to}, 金额: ${ethers.utils.formatEther(value)} ETH`);
});
web3.js 的监听方式:
// 监听新区块
web3.eth.subscribe("newBlockHeaders", (error, blockHeader) => {
if (!error) {
console.log("新区块:", blockHeader.number);
}
});
// 监听合约事件
const contract = new web3.eth.Contract(abi, contractAddress);
contract.events.Transfer({
filter: {}, // 可以加过滤条件
fromBlock: "latest"
}, (error, event) => {
console.log("转账事件:", event.returnValues);
});
这里有个关键点:WebSocket 连接。上面的监听功能依赖 WebSocket 协议,不是 HTTP。如果你用 Infura 或 Alchemy,需要切换到 WebSocket 的 URL:
// Infura WebSocket
wss://mainnet.infura.io/ws/v3/YOUR_PROJECT_ID
// Alchemy WebSocket
wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
2.6 总结与选择建议
好,我们来捋一捋。
- JSON-RPC 是底层协议,理解它有助于你排查问题。但日常开发不建议直接写 JSON。
- Infura 和 Alchemy 都是优秀的节点服务商。Alchemy 的开发者体验更好,Infura 的免费额度更慷慨。我建议你两个都注册,哪个顺手用哪个。
- ethers.js vs web3.js:新项目无脑选 ethers.js。老项目或团队习惯用 web3.js 的,继续用也没问题。两者都能完成工作,只是 ethers.js 更现代、更省心。
最后送你一句话:节点交互是 Web3 后端的“地基”。地基没打好,上层建筑再漂亮也白搭。花点时间把 JSON-RPC 的常用方法、SDK 的 API 摸熟,后面写合约交互、写后端服务会顺畅很多。
下一章,我们会深入智能合约的部署和调用。到时候你会看到,今天学的节点交互知识,全都会用上。