在区块链技术的浪潮中,以太坊(Ethereum)无疑占据了举足轻重的地位,它不仅是一个加密货币平台,更是一个去中心化的全球计算机,允许开发者构建和部署智能合约与去中心化应用(DApps),而 Node.js,凭借其异步、事件驱动的特性以及庞大的 npm 生态系统,成为了区块链开发中,尤其是与以太坊交互的热门选择,本文将深入探讨如何利用 Node.js 通过 RPC(Remote Procedure Call,远程过程调用)与以太坊节点进行通信,解锁区块链应用开发的大门。
理解核心概念
在深入代码之前,我们有必要清晰理解几个核心概念:
- 以太坊节点 (Ethereum Node):这是运行以太坊协议的软件实例,它维护着整个以太坊区块链的副本,能够验证交易、执行智能合约并与网络中的其他节点同步数据,常见的以太坊节点客户端有 Geth、OpenEthereum(原 Parity)等。
- RPC (Remote Procedure Call):这是一种网络协议,允许一台程序(客户端)请求另一台程序(服务器)上的服务,而无需了解底层网络的细节,在以太坊的语境下,节点通过 RPC 接口暴露其功能,使得外部应用(如我们的 Node.js 脚本)可以调用这些功能,例如查询账户余额、发送交易、调用智能合约方法等。
- Node.js:一个基于 Chrome V8 引擎的 JavaScript 运行时,它使得开发者可以使用 JavaScript 来编写服务器端应用程序,其非阻塞 I/O 模型非常适合处理网络请求,如与以太坊节点的 RPC 通信。
搭建开发环境
在开始之前,确保你的开发环境已准备就绪:
-
安装 Node.js 和
npm:访问 Node.js 官网 下载并安装适合你操作系统的 LTS 版本。
-
安装以太坊节点客户端:这里以 Geth 为例,你可以从 Geth 官方 GitHub 下载对应系统的二进制文件,或者通过包管理器安装(如
brew install gethon macOS)。 -
启动以太坊节点并启用 RPC 服务: 启动 Geth 节点时,你需要指定一些参数来启用 RPC 服务,并设置访问权限,最简单的方式是启动一个测试网(如 Ropsten 或 Goerli)节点,并允许本地连接:
# 以 Goerli 测试网为例,启动节点并启用 RPC geth --goerli --http --http.addr "0.0.0.0" --http.port "8545" --http.api "eth,net,web3,personal"
--goerli: 连接到 Goerli 测试网。--http: 启用 HTTP-RPC 服务器。--http.addr "0.0.0.0": 允许任何 IP 地址访问(开发环境,生产环境请谨慎设置)。--http.port "8545": RPC 服务监听的端口号,默认是 8545。--http.api "eth,net,web3,personal": 指定通过 RPC 可用的 API 命令集合。
你的以太坊节点已经在监听
8545端口的 HTTP-RPC 请求了。
使用 Node.js 连接以太坊节点
Node.js 通过 HTTP 或 WebSocket 与以太坊节点的 RPC 接口通信,最常用的库是 web3.js,它是以太坊官方提供的 JavaScript API 库,极大地简化了与以太坊节点的交互。
-
初始化项目并安装 web3.js:
mkdir eth-rpc-demo cd eth-rpc-demo npm init -y npm install web3
-
编写 Node.js 脚本连接 RPC 并调用方法:
创建一个名为
app.js的文件,并编写以下代码:const Web3 = require('web3'); // 1. 连接到以太坊节点的 RPC 接口 // 如果节点在本地运行,默认地址为 'http://localhost:8545' const web3 = new Web3('http://localhost:8545'); // 2. 检查连接是否成功 web3.eth.getBlockNumber() .then(blockNumber => { console.log('当前区块号:', blockNumber); }) .catch(err => { console.error('连接失败或获取区块号出错:', err); }); // 3. 获取节点信息 web3.eth.getNodeInfo() .then(nodeInfo => { console.log('节点信息:', nodeInfo); }) .catch(err => { console.error('获取节点信息出错:', err); }); // 4. 获取账户列表 web3.eth.getAccounts() .then(accounts => { console.log('可用账户:', accounts); if (accounts.length > 0) { // 示例:获取第一个账户的余额 const account = accounts[0]; web3.eth.getBalance(account) .then(balance => { // 余额是 Wei,转换为 Ether console.log(`账户 ${account} 的余额: ${web3.utils.fromWei(balance, 'ether')} ETH`); }) .catch(err => { console.error('获取余额出错:', err); }); } }) .catch(err => { console.error('获取账户列表出错:', err); });运行这个脚本:
node app.js
如果一切正常,你应该能看到当前区块号、节点信息、可用账户及其余额(测试网账户可能初始没有 ETH,你需要从测试网水龙头获取一些)。
进阶应用:发送交易与调用智能合约
RPC 的强大之处在于它能执行几乎所有的以太坊操作。
-
发送交易: 发送交易需要解锁账户、指定接收方、金额和 gas 等参数,这通常需要账户的密码(如果节点是用
--password参数启动的,或者使用personal.unlockAccountRPC 方法解锁)。// 注意:实际使用时请妥善处理密码,不要硬编码在代码中 const password = 'your_account_password'; // 替换为你的账户密码 const fromAccount = '0xYourAccountAddress'; // 替换为你的发送账户地址 const toAccount = '0xRecipientAddress'; // 替换为接收账户地址 const amount = web3.utils.toWei('0.01', 'ether'); // 转换为 Wei web3.eth.personal.unlockAccount(fromAccount, password) .then(unlocked => { if (unlocked) { console.log('账户解锁成功'); web3.eth.sendTransaction({ from: fromAccount, to: toAccount, value: amount, gas: 21000 // 转账交易的最小 gas }) .then(receipt => { console.log('交易成功,收据:', receipt); }) .catch(err => { console.error('交易失败:', err); }) .finally(() => { // 记得锁定账户 web3.eth.personal.lockAccount(fromAccount); console.log('账户已锁定'); }); } }) .catch(err => { console.error('解锁账户失败:', err); }); -
调用智能合约: 与智能合约交互需要合约的 ABI(Application Binary Interface,应用程序二进制接口)和合约地址。
// 假设我们有一个简单的存储合约 const contractABI = [ { "constant": false, "inputs": [{"name": "_x", "type": "uint256"}], "name": "set", "outputs": [], "type": "function" }, { "constant": true, "inputs": [], "name": "get", "outputs": [{"name": "retVal", "type": "uint256"}], "type": "function" } ]; const contractAddress = '0xYourContractAddress'; // 替换为你的合约地址 const contract = new web3.eth.Contract(contractABI, contractAddress); // 调用 view/pure 函数(不需要发送交易) contract.methods.get() .call() .then(result => { console.log('合约存储的值:', result); }) .catch(err => { console.error('调用合约 get 方法出错:', err); }); // 发送交易调用非 constant 函数 contract.methods.set(42) .send({ from: fromAccount, gas: 100000 }) .then(receipt => { console.log('调用合约 set 方法成功,收据:', receipt);