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 等。你直接拿字符串做算术运算会出问题,得先转成十进制。

我曾经踩过的坑: 有一次我直接拿 "0x10d4f2a" 当数字比较大小,结果 JavaScript 把它当字符串比较了,逻辑全错。后来我养成了习惯——所有从链上拿到的十六进制字符串,第一时间用 BigInt 或 parseInt 转成数字。

常用的 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 小时在线,运维成本很高。我建议直接用第三方节点服务商。

目前主流的两家是 InfuraAlchemy。它们都提供免费的开发套餐,足够你学习和做小项目。

我的个人经验: 早期我用 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}'

如果返回了区块号,恭喜你,你已经成功连接到了以太坊主网。

小技巧: 开发阶段建议用测试网(Goerli 或 Sepolia)。主网每笔交易都要花真金白银的 Gas,测试网可以免费领水。我刚开始做项目时,因为测试不充分,在主网上白白烧了几百美元的 Gas——心疼啊。

2.3 使用 ethers.js:更现代的选择

直接裸写 JSON-RPC 请求太原始了。在实际项目中,我们会用 SDK 来封装这些调用。目前最流行的是 ethers.jsweb3.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
避坑指南: 我曾经在生产环境用 HTTP 连接去监听事件,结果发现事件根本收不到。查了半天文档才意识到——HTTP 是请求-响应模式,没法做推送。监听必须用 WebSocket。另外,WebSocket 连接可能会断线,记得加重连逻辑。

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 摸熟,后面写合约交互、写后端服务会顺畅很多。

下一章,我们会深入智能合约的部署和调用。到时候你会看到,今天学的节点交互知识,全都会用上。