在区块链技术的浪潮中,以太坊(Ethereum)无疑占据了举足轻重的地位,它不仅是一个加密货币平台,更是一个去中心化的全球计算机,允许开发者构建和部署智能合约与去中心化应用(DApps),而 Node.js,凭借其异步、事件驱动的特性以及庞大的 npm 生态系统,成为了区块链开发中,尤其是与以太坊交互的热门选择,本文将深入探讨如何利用 Node.js 通过 RPC(Remote Procedure Call,远程过程调用)与以太坊节点进行通信,解锁区块链应用开发的大门。

理解核心概念

在深入代码之前,我们有必要清晰理解几个核心概念:

  1. 以太坊节点 (Ethereum Node):这是运行以太坊协议的软件实例,它维护着整个以太坊区块链的副本,能够验证交易、执行智能合约并与网络中的其他节点同步数据,常见的以太坊节点客户端有 Geth、OpenEthereum(原 Parity)等。
  2. RPC (Remote Procedure Call):这是一种网络协议,允许一台程序(客户端)请求另一台程序(服务器)上的服务,而无需了解底层网络的细节,在以太坊的语境下,节点通过 RPC 接口暴露其功能,使得外部应用(如我们的 Node.js 脚本)可以调用这些功能,例如查询账户余额、发送交易、调用智能合约方法等。
  3. Node.js:一个基于 Chrome V8 引擎的 JavaScript 运行时,它使得开发者可以使用 JavaScript 来编写服务器端应用程序,其非阻塞 I/O 模型非常适合处理网络请求,如与以太坊节点的 RPC 通信。

搭建开发环境

在开始之前,确保你的开发环境已准备就绪:

  1. 安装 Node.js 和

    随机配图
    npm:访问 Node.js 官网 下载并安装适合你操作系统的 LTS 版本。

  2. 安装以太坊节点客户端:这里以 Geth 为例,你可以从 Geth 官方 GitHub 下载对应系统的二进制文件,或者通过包管理器安装(如 brew install geth on macOS)。

  3. 启动以太坊节点并启用 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 库,极大地简化了与以太坊节点的交互。

  1. 初始化项目并安装 web3.js

    mkdir eth-rpc-demo
    cd eth-rpc-demo
    npm init -y
    npm install web3
  2. 编写 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 的强大之处在于它能执行几乎所有的以太坊操作。

  1. 发送交易: 发送交易需要解锁账户、指定接收方、金额和 gas 等参数,这通常需要账户的密码(如果节点是用 --password 参数启动的,或者使用 personal.unlockAccount RPC 方法解锁)。

    // 注意:实际使用时请妥善处理密码,不要硬编码在代码中
    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);
        });
  2. 调用智能合约: 与智能合约交互需要合约的 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);