使用 Go 构建区块链 API - 从 JSON-RPC 到 GraphQL

本文介绍了如何使用 Go 语言构建区块链 API,特别是将底层的 JSON-RPC 接口转换成更易于前端使用的 GraphQL API。文章详细讲解了使用 JSON-RPC 的不足之处,以及 GraphQL 的优点,并提供了使用 Go 语言实现 JSON-RPC 调用和 GraphQL API 封装的具体步骤和代码示例,最后还提到了安全性和生产环境下的注意事项。

使用 Go 构建区块链 API — 从 JSON-RPC 到 GraphQL

如果你曾经构建过 Web3 前端——比如 DeFi 仪表盘、钱包应用或 NFT 工具——你就会知道前端不会直接与区块链通信。你需要一个后端来处理与节点的全部“对话”。这个后端通常是某种 API 层。

现在,区块链(尤其是以太坊和 EVM 网络)默认公开一个 JSON-RPC 接口。它能用,也还行。但它也……有点烦人。它的层级非常低,所以如果你的前端需要任何稍微复杂的东西,你最终会进行一堆单独的调用,格式化奇怪的十六进制字符串,并手动将它们拼凑在一起。

这就是 GraphQL 可以提供帮助的地方。与其在三个单独的 RPC 调用中调用 eth_getBalanceeth_calleth_blockNumber 仅仅是为了渲染一个页面,不如将它们包装在 GraphQL endpoint 中,一次性获得你所需要的。

本指南将逐步介绍如何使用 Go 来做到这一点。我们将从原始的 JSON-RPC 开始,然后展示如何构建一个轻量级的 GraphQL API,让你的前端生活轻松 10 倍。

为什么 JSON-RPC 仍然存在(但并不总是足够)

它是什么

如果你处理过以太坊节点,你可能已经见过 JSON-RPC。这只是一种远程告诉节点做某事的方式——比如返回地址余额、获取区块、发送交易——使用 HTTP 和一个简单的 JSON 结构。

你发送一个这样的请求:

{
  "jsonrpc": "2.0",
  "method": "eth_getBalance",
  "params": ["0xYourAddress", "latest"],
  "id": 1
}

节点会发回一个 JSON 响应。

问题

它是裸金属。

你想显示用户的 ETH 余额和 token 余额吗?那需要两到三个不同的 RPC 调用。还想显示合约元数据吗?那就再加。你很快就会忙于处理 RPC 响应、十六进制解码和业务逻辑——仅仅是为了给前端提供一个干净的 JSON 对象。

GraphQL 来救援

GraphQL 颠覆了这一局面。你的前端说:“给我这种形状的这些数据,” 后端负责将它们拉到一起。

你可以:

  • 组合来自多个来源(或多个 RPC 调用)的数据
  • 精确控制返回的内容
  • 减少前端的往返次数
  • 减少你的开发体验的痛苦

因此,与其每次都重写相同的 RPC 粘合逻辑,不如将其一次性公开为 GraphQL API,你的前端只需请求它所需的内容即可。

准备工作

以下是我们将会用到的:

  • Go(你应该有 Go 1.18+)
  • 一个兼容以太坊的 JSON-RPC endpoint(Infura、Alchemy 或你自己的节点)
  • 一个 GraphQL 库——我们将使用 gqlgen
  • Go 中的基本 HTTP 设置

将你的架构想象成这样:

[ 前端应用 ] ⇄ GraphQL ⇄ [ Go API 服务器 ] ⇄ JSON-RPC ⇄ [ 以太坊节点 ]

你的前端永远不会直接接触区块链。一切都通过你的 API 层。

步骤 1:在 Go 中进行 JSON-RPC 调用

这是一个获取地址 ETH 余额的基本函数。这是一个原始的 RPC 调用——以后包装很有用。

package main
import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)
type RPCRequest struct {
    Jsonrpc string        `json:"jsonrpc"`
    Method  string        `json:"method"`
    Params  []interface{} `json:"params"`
    ID      int           `json:"id"`
}
type RPCResponse struct {
    Jsonrpc string          `json:"jsonrpc"`
    ID      int             `json:"id"`
    Result  json.RawMessage `json:"result"`
    Error   *struct {
        Code    int    `json:"code"`
        Message string `json:"message"`
    } `json:"error,omitempty"`
}
func callRPC(rpcURL, method string, params []interface{}) (json.RawMessage, error) {
    request := RPCRequest{
        Jsonrpc: "2.0",
        Method:  method,
        Params:  params,
        ID:      1,
    }
    payload, _ := json.Marshal(request)
    resp, err := http.Post(rpcURL, "application/json", bytes.NewBuffer(payload))
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    var response RPCResponse
    if err := json.Unmarshal(body, &response); err != nil {
        return nil, err
    }
    if response.Error != nil {
        return nil, fmt.Errorf("RPC error: %s", response.Error.Message)
    }
    return response.Result, nil
}
func main() {
    url := "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
    address := "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
    result, err := callRPC(url, "eth_getBalance", []interface{}{address, "latest"})
    if err != nil {
        panic(err)
    }
    fmt.Println("Balance (in hex):", string(result))
}

这只是以十六进制(以 Wei 为单位)获取余额。你可能希望稍后将其转换为 ETH 或更好地格式化它——我们将在 GraphQL 层中执行此操作。

步骤 2:在 Go 中构建 GraphQL API

我们将使用 gqlgen——它有完善的文档,并且与 Go 的类型系统配合良好。

1. 定义你的 Schema

创建一个 schema.graphql 文件:

type Query {
    getBalance(address: String!): String!
}

2. 添加一个 Resolver

这是一个包装你的 JSON-RPC 逻辑的基本 resolver:

package graph
import (
    "context"
    "yourapp/rpc"
)
type queryResolver struct{}
func (r *queryResolver) GetBalance(ctx context.Context, address string) (string, error) {
    result, err := rpc.Call("eth_getBalance", []interface{}{address, "latest"})
    if err != nil {
        return "", err
    }
    return string(result), nil
}

你可以稍后在此基础上构建——将余额转换为 ETH,处理边缘情况,验证地址等。

3. 启动服务器

package main
import (
    "log"
    "net/http"
    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
    "yourapp/graph"
    "yourapp/graph/generated"
)
func main() {
    srv := handler.NewDefaultServer(
        generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}),
    )
    http.Handle("/", playground.Handler("GraphQL Playground", "/query"))
    http.Handle("/query", srv)
    log.Println("GraphQL API running at http://localhost:8080/")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

就这样——你现在已经有了一个可工作的 Go 中的 GraphQL API,它可以包装一个区块链节点。

安全性:不要跳过这一步

JSON-RPC 风险

  • 永远不要将原始节点暴露给公众。 不要让用户直接访问你的节点。包装它。
  • 锁定敏感方法,如 eth_sendTransactionpersonal_之类的东西——这些可能很危险。
  • 始终使用 TLS/HTTPS
  • 如果你运行自己的节点,请将其限制为仅允许来自你的后端的流量。

GraphQL 特有的东西

  • 验证地址和输入——永远不要信任前端数据。
  • 限制查询深度和复杂性。如果不对深度查询进行检查,它们可能会使你的服务器崩溃。
  • 限制速率请求,尤其是当你的 API 是公开的时。
  • 不要返回 GraphQL 响应中的内部错误或堆栈跟踪——清理错误消息。

额外内容:上线前应该添加的内容

想要使这个生产就绪吗?添加:

  • 缓存:不要每次都访问 RPC 节点。将余额数据缓存几秒钟或几分钟。
  • 批处理:如果每个查询需要多次调用,请在后台对它们进行批处理。
  • 分页:对于列表(例如,交易),始终进行分页。
  • 速率限制:防止滥用。
  • 多链支持:想要支持多个网络吗?设计你的 schema 以采用 chainId 或动态切换 RPC 提供程序。

总结

如果你正在为 Web3 构建并使用 Go,这种模式——JSON-RPC 到 GraphQL——为你提供了一种将前端连接到区块链的干净方式,而无需暴露丑陋的细节。你的 UI 获得了一个不错的、结构化的 API,你的后端处理所有奇怪的 JSON 怪癖、重试和验证。

前期需要做更多的工作,但回报是值得的:更简单的前端,更少的错误,以及你可以根据需要扩展或保护的后端。

只是不要跳过安全部分。真的。

  • 原文链接: medium.com/@ancilartech/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ancilartech
ancilartech
江湖只有他的大名,没有他的介绍。