在區(qū)塊鏈技術(shù)的浪潮中,以太坊(Ethereum)無疑占據(jù)了舉足輕重的地位,它不僅是一個(gè)加密貨幣平臺(tái),更是一個(gè)去中心化的全球計(jì)算機(jī),允許開發(fā)者構(gòu)建和部署智能合約與去中心化應(yīng)用(DApps),而 Node.js,憑借其異步、事件驅(qū)動(dòng)的特性以及龐大的 npm 生態(tài)系統(tǒng),成為了區(qū)塊鏈開發(fā)中,尤其是與以太坊交互的熱門選擇,本文將深入探討如何利用 Node.js 通過 RPC(Remote Procedure Call,遠(yuǎn)程過程調(diào)用)與以太坊節(jié)點(diǎn)進(jìn)行通信,解鎖區(qū)塊鏈應(yīng)用開發(fā)的大門。
理解核心概念
在深入代碼之前,我們有必要清晰理解幾個(gè)核心概念:
- 以太坊節(jié)點(diǎn) (Ethereum Node):這是運(yùn)行以太坊協(xié)議的軟件實(shí)例,它維護(hù)著整個(gè)以太坊區(qū)塊鏈的副本,能夠驗(yàn)證交易、執(zhí)行智能合約并與網(wǎng)絡(luò)中的其他節(jié)點(diǎn)同步數(shù)據(jù),常見的以太坊節(jié)點(diǎn)客戶端有 Geth、OpenEthereum(原 Parity)等。
- RPC (Remote Procedure Call):這是一種網(wǎng)絡(luò)協(xié)議,允許一臺(tái)程序(客戶端)請(qǐng)求另一臺(tái)程序(服務(wù)器)上的服務(wù),而無需了解底層網(wǎng)絡(luò)的細(xì)節(jié),在以太坊的語境下,節(jié)點(diǎn)通過 RPC 接口暴露其功能,使得外部應(yīng)用(如我們的 Node.js 腳本)可以調(diào)用這些功能,例如查詢賬戶余額、發(fā)送交易、調(diào)用智能合約方法等。
- Node.js:一個(gè)基于 Chrome V8 引擎的 JavaScript 運(yùn)行時(shí),它使得開發(fā)者可以使用 JavaScript 來編寫服務(wù)器端應(yīng)用程序,其非阻塞 I/O 模型非常適合處理網(wǎng)絡(luò)請(qǐng)求,如與以太坊節(jié)點(diǎn)的 RPC 通信。
搭建開發(fā)環(huán)境
在開始之前,確保你的開發(fā)環(huán)境已準(zhǔn)備就緒:
-
安裝 Node.js 和 npm:訪問 Node.js 官網(wǎng) 下載并安裝適合你操作系統(tǒng)的 LTS 版本。
-
安裝以太坊節(jié)點(diǎn)客戶端:這里以 Geth 為例,你可以從 Geth 官方 GitHub 下載對(duì)應(yīng)系統(tǒng)的二進(jìn)制文件,或者通過包管理器安裝(如
brew install gethon macOS)。 -
啟動(dòng)以太坊節(jié)點(diǎn)并啟用 RPC 服務(wù): 啟動(dòng) Geth 節(jié)點(diǎn)時(shí),你需要指定一些參數(shù)來啟用 RPC 服務(wù),并設(shè)置訪問權(quán)限,最簡單的方式是啟動(dòng)一個(gè)測(cè)試網(wǎng)(如 Ropsten 或 Goerli)節(jié)點(diǎn),并允許本地連接:
# 以 Goerli 測(cè)試網(wǎng)為例,啟動(dòng)節(jié)點(diǎn)并啟用 RPC geth --goerli --http --http.addr "0.0.0.0" --http.port "8545" --http.api "eth,net,web3,personal"
--goerli: 連接到 Goerli 測(cè)試網(wǎng)。--http: 啟用 HTTP-RPC 服務(wù)器。--http.addr "0.0.0.0": 允許任何 IP 地址訪問(開發(fā)環(huán)境,生產(chǎn)環(huán)境請(qǐng)謹(jǐn)慎設(shè)置)。--http.port "8545": RPC 服務(wù)監(jiān)聽的端口號(hào),默認(rèn)是 8545。--http.api "eth,net,web3,personal": 指定通過 RPC 可用的 API 命令集合。
你的以太坊節(jié)點(diǎn)已經(jīng)在監(jiān)聽
8545端口的 HTTP-RPC 請(qǐng)求了。
使用 Node.js 連接以太坊節(jié)點(diǎn)
Node.js 通過 HTTP 或 WebSocket 與以太坊節(jié)點(diǎn)的 RPC 接口通信,最常用的庫是 web3.js,它是以太坊官方提供的 JavaScript API 庫,極大地簡化了與以太坊節(jié)點(diǎn)的交互。
-
初始化項(xiàng)目并安裝 web3.js:
mkdir eth-rpc-demo cd eth-rpc-demo npm init -y npm install web3
-
編寫 Node.js 腳本連接 RPC 并調(diào)用方法:
創(chuàng)建一個(gè)名為
app.js的文件,并編寫以下代碼:const Web3 = require('web3'); // 1. 連接到以太坊節(jié)點(diǎn)的 RPC 接口 // 如果節(jié)點(diǎn)在本地運(yùn)行,默認(rèn)地址為 'http://localhost:8545' const web3 = new Web3('http://localhost:8545'); // 2. 檢查連接是否成功 web3.eth.getBlockNumber() .then(blockNumber => { console.log('當(dāng)前區(qū)塊號(hào):', blockNumber); }) .catch(err => { console.error('連接失敗或獲取區(qū)塊號(hào)出錯(cuò):', err); }); // 3. 獲取節(jié)點(diǎn)信息 web3.eth.getNodeInfo() .then(nodeInfo => { console.log('節(jié)點(diǎn)信息:', nodeInfo); }) .catch(err => { console.error('獲取節(jié)點(diǎn)信息出錯(cuò):', err); }); // 4. 獲取賬戶列表 web3.eth.getAccounts() .then(accounts => { console.log('可用賬戶:', accounts); if (accounts.length > 0) { // 示例:獲取第一個(gè)賬戶的余額 const account = accounts[0]; web3.eth.getBalance(account) .then(balance => { // 余額是 Wei,轉(zhuǎn)換為 Ether console.log(`賬戶 ${account} 的余額: ${web3.utils.fromWei(balance, 'ether')} ETH`); }) .catch(err => { console.error('獲取余額出錯(cuò):', err); }); } }) .catch(err => { console.error('獲取賬戶列表出錯(cuò):', err); });運(yùn)行這個(gè)腳本:
node app.js
如果一切正常,你應(yīng)該能看到當(dāng)前區(qū)塊號(hào)、節(jié)點(diǎn)信息、可用賬戶及其余額(測(cè)試網(wǎng)賬戶可能初始沒有 ETH,你需要從測(cè)試網(wǎng)水龍頭獲取一些)。
進(jìn)階應(yīng)用:發(fā)送交易與調(diào)用智能合約
RPC 的強(qiáng)大之處在于它能執(zhí)行幾乎所有的以太坊操作。
-
發(fā)送交易: 發(fā)送交易需要解鎖賬戶、指定接收方、金額和 gas 等參數(shù),這通常需要賬戶的密碼(如果節(jié)點(diǎn)是用
--password參數(shù)啟動(dòng)的,或者使用personal.unlockAccountRPC 方法解鎖)。// 注意:實(shí)際使用時(shí)請(qǐng)妥善處理密碼,不要硬編碼在代碼中 const password = 'your_account_password'; // 替換為你的賬戶密碼 const fromAccount = '0xYourAccountAddress'; // 替換為你的發(fā)送賬戶地址 const toAccount = '0xRecipientAddress'; // 替換為接收賬戶地址 const amount = web3.utils.toWei('0.01', 'ether'); // 轉(zhuǎn)換為 Wei web3.eth.personal.unlockAccount(fromAccount, password) .then(unlocked => { if (unlocked) { console.log('賬戶解鎖成功'); web3.eth.sendTransaction({ from: fromAccount, to: toAccount, value: amount, gas: 21000 // 轉(zhuǎn)賬交易的最小 gas }) .then(receipt => { console.log('交易成功,收據(jù):', receipt); }) .catch(err => { console.error('交易失敗:', err); }) .finally(() => { // 記得鎖定賬戶 web3.eth.personal.lockAccount(fromAccount); console.log('賬戶已鎖定'); }); } }) .catch(err => { console.error('解鎖賬戶失敗:', err); }); -
調(diào)用智能合約: 與智能合約交互需要合約的 ABI(Application Binary Interface,應(yīng)用程序二進(jìn)制接口)和合約地址。
// 假設(shè)我們有一個(gè)簡單的存儲(chǔ)合約 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); // 調(diào)用 view/pure 函數(shù)(不需要發(fā)送交易) contract.methods.get() .call() .then(result => { console.log('合約存儲(chǔ)的值:', result); }) .catch(err => { console.error('調(diào)用合約 get 方法出錯(cuò):', err); }); // 發(fā)送交易調(diào)用非 constant 函數(shù) contract.methods.set(42) .send({ from: fromAccount, gas: 100000 }) .then(receipt => { console.log('調(diào)用合約 set 方法成功,收據(jù):', receipt);