PHP 与以太坊 RPC 对接,实现区块链交互的实践指南

默认分类 2026-03-25 0:51 16 0

在当今区块链技术飞速发展的时代,以太坊作为全球领先的智能合约平台,吸引了无数开发者的目光,对于许多基于 Web 技术栈的开发者而言,使用 PHP 与以太坊进行交互是一个常见的需求,而实现这种交互的核心方式之一,就是通过以太坊的 JSON-RPC (JSON-RPC) 接口,本文将详细介绍如何使用 PHP 对接以太坊 RPC,实现从环境搭建到具体功能调用的一系列操作。

什么是以太坊 RPC?

以太坊 JSON-RPC 是一个标准的通信协议,它允许客户端(如我们的 PHP 应用程序)与以太坊节点(如 Geth, Parity 或 Infura 这样的远程节点)进行通信,通过发送 JSON 格式的请求,客户端可以调用节点提供的各种方法,例如查询账户余额、发送交易、部署智能合约、读取智能合约状态等,你可以把 RPC 看作是 PHP 应用与以太坊区块链之间的“翻译官”和“桥梁”。

准备工作

在开始编码之前,我们需要准备以下几样东西:

  1. PHP 环境:确保你的系统已经安装了 PHP,建议版本在 7.4 或更高,以获得更好的性能和安全性支持。
  2. 以太坊节点访问方式
    • 本地节点:在你的机器上运行一个以太坊客户端,如 Geth 或 Parity,优点是完全自主可控,缺点是对硬件资源要求较高,同步区块可能需要较长时间和大量存储空间。
    • 远程节点服务(推荐初学者):使用 Infura、Alchemy 等提供的远程节点服务,你只需要注册账号,获取一个 RPC URL 即可,无需维护节点,非常方便,Infura 提供的以太坊主网或测试网 RPC URL。
  3. PHP HTTP 客户端库:PHP 本身没有内置直接、高效的 JSON-RPC 客户端,我们可以使用 cURL随机配图
> 扩展(PHP 默认自带)或者一些轻量级的 HTTP 客户端库,如 Guzzle,本文主要使用 cURL 进行演示,因为它更通用,无需额外安装依赖。
  • 以太坊钱包(可选,用于发送交易):如果你需要发送交易,你需要一个包含一定 ETH 的钱包,以及对应的私钥(注意:私钥务必妥善保管,切勿泄露)。
  • PHP 对接以太坊 RPC 核心步骤

    对接以太坊 RPC 的核心步骤无非就是:构造 JSON-RPC 请求 -> 通过 HTTP 发送请求 -> 接收并解析 JSON 响应。

    1. 构造 JSON-RPC 请求

      一个标准的 JSON-RPC 请求包含以下字段:

      • jsonrpc: 版本,通常为 "2.0"。
      • method: 要调用的以太坊 RPC 方法名,如 eth_blockNumber, eth_getBalance
      • params: 方法调用所需的参数数组,顺序和类型需与方法定义一致,如果没有参数则为空数组。
      • id: 请求 ID,用于标识请求,响应中会包含相同的 ID。

      获取最新区块号的请求 JSON 如下:

      {
          "jsonrpc": "2.0",
          "method": "eth_blockNumber",
          "params": [],
          "id": 1
      }
    2. 发送 HTTP 请求并接收响应

      我们可以使用 PHP 的 cURL 扩展来发送 POST 请求到以太坊节点的 RPC URL。

    PHP 代码示例

    下面我们通过几个常用的场景来展示具体的 PHP 代码实现。

    连接以太坊节点并获取最新区块号

    <?php
    /**
     * 以太坊 RPC 客户端类示例
     */
    class EthereumRpcClient
    {
        private $rpcUrl;
        private $id = 0;
        public function __construct($rpcUrl)
        {
            $this->rpcUrl = $rpcUrl;
        }
        /**
         * 发送 JSON-RPC 请求
         * @param string $method 方法名
         * @param array $params 参数数组
         * @return array 响应结果
         * @throws Exception
         */
        public function request($method, $params = [])
        {
            $this->id++;
            $payload = json_encode([
                'jsonrpc' => '2.0',
                'method' => $method,
                'params' => $params,
                'id' => $this->id
            ]);
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $this->rpcUrl);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 设置超时时间
            $response = curl_exec($ch);
            if (curl_errno($ch)) {
                throw new Exception('cURL Error: ' . curl_error($ch));
            }
            curl_close($ch);
            $result = json_decode($response, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new Exception('JSON Decode Error: ' . json_last_error_msg());
            }
            // 检查 RPC 响应中的 error 字段
            if (isset($result['error']) && $result['error'] !== null) {
                throw new Exception('RPC Error: ' . $result['error']['message'] . ' (Code: ' . $result['error']['code'] . ')');
            }
            return $result['result'] ?? null;
        }
    }
    // 使用示例
    try {
        // 替换成你的 Infura 或其他节点服务的 RPC URL
        // 测试网示例 (Goerli)
        $rpcUrl = 'https://goerli.infura.io/v3/YOUR_INFURA_PROJECT_ID';
        // 主网示例 (需要替换为你的实际 RPC URL)
        // $rpcUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
        $client = new EthereumRpcClient($rpcUrl);
        // 1. 获取最新区块号
        $latestBlockNumber = $client->request('eth_blockNumber');
        echo "最新区块号 (Hex): " . $latestBlockNumber . "\n";
        // 将十六进制区块号转为十进制
        $latestBlockNumberDec = hexdec($latestBlockNumber);
        echo "最新区块号 (Dec): " . $latestBlockNumberDec . "\n";
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
    ?>

    查询指定地址的 ETH 余额

    // 接续上面的 EthereumRpcClient 类
    // 使用示例
    try {
        $client = new EthereumRpcClient($rpcUrl); // 确保 $rpcUrl 已定义
        // 要查询的地址
        $address = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'; // 示例地址,可以替换成你想查询的地址
        // eth_getBalance 的第二个参数是区块号,可以是 'latest', 'pending', 'earliest' 或十六进制区块号
        $balance = $client->request('eth_getBalance', [$address, 'latest']);
        echo "地址 {$address} 的余额 (Hex Wei): " . $balance . "\n";
        // 将 Wei 转为 ETH (1 ETH = 10^18 Wei)
        $balanceEth = bcdiv(hexdec($balance), 1000000000000000000, 18);
        echo "地址 {$address} 的余额 (ETH): " . $balanceEth . "\n";
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }

    发送交易(转账 ETH)

    发送交易比查询操作要复杂,因为它需要私钥签名。注意:在实际应用中,切勿在代码中硬编码私钥! 应该使用硬件钱包、环境变量或专门的密钥管理服务。

    这里我们为了演示,会使用一个私钥,但请务必理解其风险。

    // 接续上面的 EthereumRpcClient 类
    // 辅助函数:将私钥转换为地址(简化版,实际应使用更安全的库如 web3.php)
    function privateKeyToAddress($privateKey) {
        // 这里仅作概念演示,实际需要椭圆曲线运算等复杂逻辑
        // 通常我们会使用如 `web3.php` 这样的库来处理
        // 假设这个函数能返回地址
        // 注意:这是一个简化的示意,真实实现非常复杂且不安全直接写
        return '0x' . substr(keccak256(hex2bin(str_pad($privateKey, 64, '0', STR_PAD_LEFT))), -40);
    }
    // 辅助函数:签名交易