以太坊作为全球领先的智能合约平台,其去中心化应用(DApps)的开发离不开与区块链网络的交互,而JSON-RPC(JavaScript Object Notation Remote Procedure Call)规范正是以太坊节点(如Geth、Parity等)与外部应用程序进行通信的标准协议,对于PHP开发者而言,掌握如何利用PHP实现与以太坊JSON-RPC接口的交互,是构建基于以太坊的DApps或后端服务的关键技能,本文将深入探讨以太坊JSON-RPC规范,并详细阐述如何在PHP中对其进行调用和实践。

以太坊JSON-RPC规范概述

以太坊JSON-RPC规范是一种轻量级的远程过程调用协议,它使用JSON格式进行数据编码和解码,通过该规范,客户端(如我们的PHP应用)可以向以太坊节点发送请求,并接收响应,以太坊节点提供了丰富的API方法,涵盖了账户管理、交易发送、智能合约交互、区块查询、日志过滤等方方面面。

核心特点:

  1. 基于HTTP/HTTPS:通常通过HTTP或HTTPS POST请求进行通信。
  2. JSON数据格式:请求和响应均采用JSON格式,易于解析和生成。
  3. 请求结构:每个请求包含以下字段:
    • jsonrpc: 字符串,必须为"2.0"。
    • method: 字符串,要调用的RPC方法名(如eth_blockNumber, eth_getBalance, eth_sendTransaction)。
    • params: 数组,传递给方法的参数,顺序和数量需与方法定义一致。
    • id: 任意类型,请求标识符,用于匹配响应和请求,可以是数字、字符串或null。
  4. 响应结构:响应包含以下字段:
    • jsonrpc: 字符串,必须为"2.0"。
    • result: 任意类型,请求成功时返回的结果。
    • error: 对象,请求失败时返回的错误信息,包含code(错误码)、message(错误描述)等。
    • id: 与请求中的id对应。

常用以太坊JSON-RPC方法示例:

  • eth_blockNumber: 获取最新区块号。
  • eth_getBalance: 查询指定地址的ETH余额。
  • eth_getTransactionCount: 查询指定地址的交易次数(用于nonce值)。
  • eth_sendRawTransaction: 发签名的原始交易。
  • eth_call: 执行智能合约的常量函数(不修改链上状态)。
  • eth_estimateGas: 估算交易执行所需的Gas数量。
  • eth_getLogs: 查询匹配条件的日志。

PHP环境准备与HTTP客户端

在PHP中与以太坊JSON-RPC接口交互,最核心的是能够发送HTTP POST请求并处理JSON响应,PHP内置了cURL扩展,这是进行HTTP请求的强大工具,确保你的PHP环境已启用cURL扩展。

也可以使用一些流行的HTTP客户端库,如Guzzle,它们提供了更简洁、更强大的API,简化了HTTP请求的构建和发送过程,本文将以原生cURL为例进行讲解,并简要提及Guzzle的便利性。

使用PHP实现以太坊JSON-RPC调用

下面我们通过几个具体的PHP代码示例,展示如何调用以太坊JSON-RPC接口。

示例1:连接以太坊节点并获取最新区块号

假设我们有一个运行中的以太坊节点(本地或远程),其JSON-RPC端点为http://localhost:8545(这是Geth默认的IPC HTTP端口,实际使用时请替换为你的节点地址)。

<?php
// 以太坊节点JSON-RPC端点
$rpcUrl = 'http://localhost:8545';
// 构建JSON-RPC请求
$request = [
    'jsonrpc' => '2.0',
    'method' => 'eth_blockNumber',
    'params' => [],
    'id' => 1 // 请求ID,可以是任意唯一值
];
// 将请求转换为JSON字符串
$jsonRequest = json_encode($request);
// 初始化cURL会话
$ch = curl_init($rpcUrl);
// 设置cURL选项
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 将响应作为字符串返回
curl_setopt($ch, CURLOPT_POST, true); // 发送POST请求
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonRequest); // 设置POST数据
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Content-Length: ' . strlen($jsonRequest)
]);
// 执行cURL请求
$response = curl_exec($ch);
// 检查是否有cURL错误
if (curl_errno($ch)) {
    echo 'cURL Error: ' . curl_error($ch);
    curl_close($ch);
    exit;
}
// 关闭cURL会话
curl_close($ch);
// 解析JSON响应
$responseData = json_decode($response, true);
// 检查响应是否有错误
if (isset($responseData['error'])) {
    echo 'JSON-RPC Error: ' . $responseData['error']['message'] . ' (Code: ' . $responseData['error']['code'] . ')';
} else {
    // 获取区块号(返回的是十六进制字符串,如 "0x1a2b3c")
    $blockNumberHex = $responseData['result'];
    // 将十六进制转换为十进制
    $blockNumberDec = hexdec($blockNumberHex);
    echo "Latest Block Number: " . $blockNumberHex . " (Decimal: " . $blockNumberDec . ")";
}
?>

示例2:查询指定地址的ETH余额

假设我们要查询地址0x742d35Cc6634C0532925a3b844Bc454e4438f44e的余额。

<?php
$rpcUrl = 'http://localhost:8545';
$address = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'; // 替换为你要查询的地址
$request = [
    'jsonrpc' => '2.0',
    'method' => 'eth_getBalance',
    'params' => [$address, 'latest'], // 参数:地址,区块标识符(latest表示最新区块)
    'id' => 2
];
$jsonRequest = json_encode($request);
$ch = curl_init($rpcUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonRequest);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Content-Length: ' . strlen($jsonRequest)
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
    echo 'cURL Error: ' . curl_error($ch);
    curl_close($ch);
    exit;
}
curl_close($ch);
$responseData = json_decode($response, true);
if (isset($responseData['error'])) {
    echo 'JSON-RPC Error: ' . $responseData['error']['message'];
} else {
    $balanceHex = $responseData['result'];
    // 将十六进制余额(单位为wei)转换为ETH (1 ETH = 10^18 wei)
    $balanceEth = bcdiv(hexdec($balanceHex), '1000000000000000000', 18);
    echo "Balance of $address: " . $balanceHex . " wei (ETH: " . $balanceEth . ")";
}
?>

注意: 处理大整数(如以太坊余额,单位为wei)时,PHP的int类型可能会溢出,推荐使用BCMathGMP扩展进行高精度数学运算,如示例中使用的bcdiv

示例3:使用Guzzle简化请求(可选)

如果你项目中使用了Guzzle HTTP客户端,代码会更加简洁:

通过Composer安装Guzzle: composer require guzzlehttp/guzzle

可以这样写:

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$rpcUrl = 'http://localhost:8545';
$address = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e';
$client = new Client();
try {
    $response = $client->post($rpcUrl, [
        'json' => [
            'jsonrpc' => '2.0',
            'method' => 'eth_getBalance',
            'params' => [$address, 'latest'],
            'id' => 3
        ]
    ]);
    $responseData = json_decode($response->getBody()->getContents(), true);
    if (isset($responseData['error'])) {
        echo 'JSON-RPC Error: ' . $responseData['error']['message