前言
在开发过程中,有时出于性能考虑需要将原有的 JavaScript 项目迁移到 Go 语言实现。当原项目使用 web3.js 与以太坊区块链交互时,改用 Go 实现同样需要实现相应的区块链交互功能。
那么,如何使用 Go 与以太坊区块链进行交互呢?本文介绍的方法基于 JSON RPC API。虽然 Go 社区中有不少开源库(如 go-web3 或 web3.go)可供选择,但很多项目已长期未更新。幸运的是,官方维护的 go-ethereum 库提供了稳定支持。本文将重点介绍如何通过 go-ethereum 实现与以太坊区块链的交互(基于 JSON RPC API)。
本文不涉及 Go 开发环境的搭建,有关环境配置请参考官方文档或其他教程资源。
建立连接
首先需要导入必要的包并建立与以太坊节点的连接。go-ethereum 提供了两种连接方式:
方式一:使用底层 rpc 包创建连接
import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
)
func Connect(host string) (*ethclient.Client, error) {
ctx, err := rpc.Dial(host)
if err != nil {
return nil, err
}
conn := ethclient.NewClient(ctx)
return conn, nil
}方式二:使用 ethclient 包封装的方法
import (
"github.com/ethereum/go-ethereum/ethclient"
)
func Connect(host string) (*ethclient.Client, error) {
conn, err := ethclient.Dial(host)
if err != nil {
return nil, err
}
return conn, nil
}推荐使用第一种方式,因为 *ethclient.Client 并未支持所有 JSON RPC API。保留 *rpc.Client 实例可以方便后续扩展调用其他接口,保持代码灵活性。
*ethclient.Client 支持的 API 列表可参考官方文档。
扩展 JSON RPC API
为了更灵活地调用各种 JSON RPC 接口,我们可以自定义一个 Client 结构体,将 *rpc.Client 和 *ethclient.Client 封装在一起:
// Client 封装了以太坊 RPC API 的客户端
type Client struct {
rpcClient *rpc.Client
EthClient *ethclient.Client
}调整之前的 Connect 函数,返回自定义的 Client 实例:
// Connect 创建并返回一个连接到指定主机的客户端
func Connect(host string) (*Client, error) {
rpcClient, err := rpc.Dial(host)
if err != nil {
return nil, err
}
ethClient := ethclient.NewClient(rpcClient)
return &Client{rpcClient, ethClient}, nil
}获取当前区块号
ethclient 原生不支持获取当前区块号的功能,我们可以通过扩展 JSON RPC API 来实现:
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// GetBlockNumber 返回当前区块号
func (ec *Client) GetBlockNumber(ctx context.Context) (*big.Int, error) {
var result hexutil.Big
err := ec.rpcClient.CallContext(ctx, &result, "eth_blockNumber")
return (*big.Int)(&result), err
}使用示例:
client, err := Connect("http://localhost:8545")
if err != nil {
fmt.Println(err.Error())
}
blockNumber, err := client.GetBlockNumber(context.TODO())发送交易
虽然 ethclient 提供了 SendRawTransaction 方法,但我们需要实现的是使用节点上存储的私钥发送交易,即对应 eth_sendTransaction RPC 请求。
首先定义交易消息结构体:
import "github.com/ethereum/go-ethereum/common"
// Message 代表一个完整的交易消息
type Message struct {
To *common.Address `json:"to"`
From common.Address `json:"from"`
Value string `json:"value"`
GasLimit string `json:"gas"`
GasPrice string `json:"gasPrice"`
Data []byte `json:"data"`
}注意:根据 API 规范,某些字段可以为空,但在此实现中我们要求所有字段都必须填写。
添加辅助函数创建消息实例:
// NewMessage 创建并返回一个新的交易消息
func NewMessage(from common.Address, to *common.Address, value *big.Int, gasLimit *big.Int, gasPrice *big.Int, data []byte) Message {
return Message{
From: from,
To: to,
Value: toHexInt(value),
GasLimit: toHexInt(gasLimit),
GasPrice: toHexInt(gasPrice),
Data: data,
}
}实现发送交易的方法:
// SendTransaction 将交易注入待处理池以待执行
//
// 如果交易是合约创建交易,可使用 TransactionReceipt 方法获取挖矿后的合约地址
func (ec *Client) SendTransaction(ctx context.Context, tx *Message) error {
err := ec.rpcClient.CallContext(ctx, nil, "eth_sendTransaction", tx)
return err
}使用示例:
from := common.HexToAddress("发送方地址")
to := common.HexToAddress("接收方地址")
amount := big.NewInt(1)
gasLimit := big.NewInt(90000)
gasPrice := big.NewInt(0)
data := []byte{}
message := NewMessage(from, &to, amount, gasLimit, gasPrice, data)
fmt.Println(message)
err = client.SendTransaction(context.TODO(), &message)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("交易已发送")虽然 go-ethereum 的 types 包中有 Message 结构体和 NewMessage 函数,但由于无法正确序列化为 JSON 格式,且官方注释表明未来可能会移除,因此我们需要自己实现。
获取交易哈希
为了能够追踪交易状态,我们需要获取交易哈希。修改 SendTransaction 函数以返回交易哈希:
// SendTransaction 发送交易并返回交易哈希
func (ec *Client) SendTransaction(ctx context.Context, tx *Message) (common.Hash, error) {
var txHash common.Hash
err := ec.rpcClient.CallContext(ctx, &txHash, "eth_sendTransaction", tx)
return txHash, err
}使用 common.Hash 类型而不是 string 是为了后续调用 TransactionByHash 方法时参数类型匹配。
获取交易数据:
// 获取交易详情
tx, isPending, _ := client.EthClient.TransactionByHash(context.TODO(), txHash)Transaction 类型的详细方法可参考源代码。
为了避免阻塞主程序,我们可以使用 Go 的 channel 机制来异步检查交易状态:
// 创建接收交易回执的通道
receiptChan := make(chan *types.Receipt)
// 检查交易状态
go func() {
fmt.Printf("检查交易: %s\n", txHash.String())
for {
receipt, _ := client.EthClient.TransactionReceipt(context.TODO(), txHash)
if receipt != nil {
receiptChan <- receipt
break
} else {
fmt.Println("1秒后重试...")
time.Sleep(1 * time.Second)
}
}
}()
receipt := <-receiptChan
fmt.Printf("交易状态: %v\n", receipt.Status)总结
本文介绍了使用 Go 语言通过 go-ethereum 库与以太坊区块链交互的基础方法,包括建立连接、扩展 JSON RPC API、获取区块高度以及发送交易等操作。这只是众多交互方式中的一种,实际开发中可根据具体需求选择最合适的方案。
常见问题
Q: 为什么要使用 Go 而不是 JavaScript 与以太坊交互?
A: Go 语言在性能、并发处理和系统资源管理方面具有优势,特别适合需要高性能和高并发的区块链应用场景。
Q: go-ethereum 和其他 Go 语言以太坊库相比有什么优势?
A: go-ethereum 是以太坊官方维护的库,更新频繁、功能全面且社区支持活跃,相比其他可能已停止维护的库更加可靠。
Q: 如何选择使用 ethclient 还是直接使用 rpc 客户端?
A: ethclient 提供了高级封装和方法,使用更方便;直接使用 rpc 客户端可以访问所有 JSON RPC 接口,灵活性更高。可根据具体需求选择或结合使用。
Q: 发送交易时需要注意哪些参数?
A: 必须正确设置 GasLimit 和 GasPrice,否则交易可能失败。另外需要确保发送地址在节点中有对应的私钥。
Q: 如何监控交易状态?
A: 可以通过交易哈希定期查询交易回执,使用轮询或订阅事件的方式监控交易状态变化。
Q: 除了文中介绍的方法,还有哪些与以太坊交互的方式?
A: 还可以使用 WebSocket 订阅事件、与智能合约交互、使用过滤器监听日志等更多高级功能。