以太坊,作为全球领先的智能合约平台,其强大的生态系统离不开各种与区块链交互的方式,JSON-RPC(远程过程调用)是以太坊节点(如 Geth、Parity 或 Nethermind)提供的一套标准 API,允许应用程序与区块链进行通信,对于 Java 开发者而言,掌握如何通过 JSON-RPC 与以太坊网络交互,是构建去中心化应用(DApp)、钱包、数据分析工具等项目的核心技能,本文将深入探讨如何使用 Java 与以太坊 JSON-RPC API 进行交互,涵盖从环境搭建到具体代码实现的完整流程。

理解以太坊 JSON-RPC API

以太坊 JSON-RPC API 是一个基于 HTTP 或 WebSocket 的接口,它定义了一系列方法,允许客户端执行各种操作,

  • 查询区块链状态:获取最新区块号、区块详情、交易收据、账户余额、代码等。
  • 发送交易:将新的交易(如转账、调用智能合约)发送到以太坊网络进行打包。
  • 与智能合约交互:调用智能合约的读函数(call)和写函数(transact)。
  • 事件监听:订阅区块链上的特定事件(如新区块、新交易、智能合约事件)。

这些方法以 JSON 格式通过 HTTP POST 请求发送给以太坊节点,节点处理后将结果以 JSON 格式返回,获取当前区块号的 eth_blockNumber 方法,请求和响应如下:

请求:

{
  "jsonrpc": "2.0",
  "method": "eth_blockNumber",
  "params": [],
  "id": 1
}

响应:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x1234567" // 十六进制格式的区块号
}

Java 环境准备与依赖库

在 Java 中与以太坊 JSON-RPC 交互,核心任务是构造符合规范的 JSON 请求,发送 HTTP 请求,并解析 JSON 响应,虽然可以手动使用如 HttpURLConnectionorg.jsonGsonJackson 等库来实现,但使用成熟的以太坊 Java 库能大大简化开发,提高效率,目前最流行和功能最强大的库是 Web3j

添加 Web3j 依赖:

如果你使用 Maven,在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.web3j</groupId>
    <artifactId>core</artifactId>
    <version>4.9.8</version> <!-- 请使用最新版本 -->
</dependency>

Gradle 用户则添加:

implementation 'org.web3j:core:4.9.8' // 请使用最新版本

Web3j 封装了以太坊 JSON-RPC API 的所有方法,提供了类型安全的 Java 对象,并处理了序列化和反序列化的细节。

以太坊节点连接:

你需要一个正在运行的以太坊节点,并知道其 JSON-RPC 端点地址,这可以是:

  • 本地节点:如果你在自己的机器上运行了 Geth 或 Parity,通常默认端口是 8545 (HTTP) 或 8546 (HTTPS/WSS)。
  • 远程节点服务:如 Infura、Alchemy、QuickNode 等,它们提供公共或私有的 RPC 端点,无需自己运行节点。<
    随机配图
    /li>

使用 Web3j 进行交互实践

连接到以太坊节点:

import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import java.io.IOException;
public class EthereumConnection {
    public static void main(String[] args) {
        // 替换为你的以太坊节点 RPC URL
        String rpcUrl = "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID";
        // 本地节点示例: String rpcUrl = "http://localhost:8545";
        Web3j web3j = Web3j.build(new HttpService(rpcUrl));
        try {
            // 检查连接是否成功
            String clientVersion = web3j.web3ClientVersion().send().getWeb3ClientVersion();
            System.out.println("Connected to Ethereum client: " + clientVersion);
            // 关闭连接
            web3j.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

查询账户余额:

假设我们要查询某个以太坊地址的余额。

import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import java.math.BigInteger;
public class GetBalance {
    public static void main(String[] args) {
        String rpcUrl = "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID";
        Web3j web3j = Web3j.build(new HttpService(rpcUrl));
        String address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; // 示例地址
        try {
            EthGetBalance ethGetBalance = web3j.ethGetBalance(address, org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send();
            BigInteger balanceWei = ethGetBalance.getBalance();
            // 将 Wei 转换为 Ether
            String balanceEth = Convert.fromWei(balanceWei.toString(), Unit.ETHER).toPlainString();
            System.out.println("Balance of " + address + " is: " + balanceEth + " ETH");
            web3j.shutdown();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

发送交易(转账 ETH):

发送交易比查询状态复杂,因为它需要私钥签名交易,并且需要支付 Gas 费。

import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.transaction.Transaction;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.ExecutionException;
public class SendTransaction {
    public static void main(String[] args) {
        String rpcUrl = "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID";
        Web3j web3j = Web3j.build(new HttpService(rpcUrl));
        // 发送方地址和私钥(实际项目中务必安全存储私钥,不要硬编码!)
        String fromAddress = "0xYourFromAddress";
        String privateKey = "YOUR_PRIVATE_KEY"; // 危险!仅用于演示
        // 接收方地址
        String toAddress = "0xReceiverAddress";
        // 转账金额(0.01 ETH)
        BigDecimal amountInEth = new BigDecimal("0.01");
        BigInteger amountInWei = Convert.toWei(amountInEth, Unit.ETHER).toBigIntegerExact();
        try {
            // 1. 获取当前 nonce
            BigInteger nonce = web3j.ethGetTransactionCount(fromAddress, org.web3j.protocol.core.DefaultBlockParameterName.LATEST)
                    .send()
                    .getTransactionCount();
            // 2. 创建交易对象
            Transaction transaction = Transaction.createEtherTransaction(
                    fromAddress,
                    nonce,
                    BigInteger.valueOf(200000), // Gas Limit
                    BigInteger.valueOf(20 * 1000000000), // Gas Price (20 Gwei)
                    toAddress,
                    amountInWei
            );
            // 3. 签名交易
            org.web3j.crypto.RawTransaction rawTransaction = org.web3j.crypto.RawTransaction.createTransaction(
                    nonce,
                    transaction.getGasPrice(),
                    transaction.getGasLimit(),
                    transaction.getTo(),
                    transaction.getValue()
            );
            org.web3j.crypto.Sign.SignatureData signature = org.web3j.crypto.Sign.signMessage(
                    rawTransaction, org.web3j.crypto.Credentials.create(privateKey).getEcKeyPair()
            );
            // 4. 发送签名后的交易
            EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(
                    org.web3j.crypto.TransactionEncoder.signMessage(rawTransaction, signature)
            ).send();
            if (ethSendTransaction.getTransactionHash() != null) {
                System.out.println("Transaction sent! Hash: " + ethSendTransaction.getTransactionHash());
            } else {
                System.out.println("Error sending transaction: " + ethSendTransaction.getError().getMessage());
            }
            web3j.shutdown();
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

注意: 实际项目中,私钥管理至关重要,应使用硬件钱包、密钥管理系统或环境变量等方式,严禁硬编码。

与智能合约交互:

Web3j 也提供了与智能合约