Read OSS

对外提供服务:JSON-RPC、Engine API 与 API 架构

中级

前置知识

  • 第 1-2 篇:架构与启动流程
  • JSON-RPC 基础知识
  • 了解以太坊执行层/共识层的分离设计

对外提供服务:JSON-RPC、Engine API 与 API 架构

每一个 dApp、钱包、区块浏览器和开发工具,都是通过 JSON-RPC 接口与 Geth 进行交互的。Geth 在多个命名空间下提供数千个 RPC 方法,支持 HTTP、WebSocket 和 IPC 三种传输方式,同时还为共识层单独提供了一个需要身份认证的端点。本文将全面介绍 Geth 的 RPC 架构,包括基于反射的注册框架、传输层、用于解耦 API 处理器与内部实现的 Backend 抽象、命名空间注册机制,以及 Engine API。

RPC 框架:基于反射的注册机制

Geth 的 RPC 框架实现于 rpc/ 包中,利用 Go 的反射机制将结构体方法自动映射为 JSON-RPC 端点。注册的核心逻辑位于 service.go

type serviceRegistry struct {
    mu       sync.Mutex
    services map[string]service
}

type service struct {
    name          string
    callbacks     map[string]*callback
    subscriptions map[string]*callback
}

type callback struct {
    fn          reflect.Value
    rcvr        reflect.Value
    argTypes    []reflect.Type
    hasCtx      bool
    errPos      int
    isSubscribe bool
}

当你调用 server.RegisterName("eth", myService) 注册一个结构体时,框架会通过反射自动发现所有导出方法,识别其参数类型,并创建可调用的包装器。命名规则是自动处理的:注册在 "eth" 命名空间下的服务,其 GetBalance 方法会自动映射为 eth_getBalance(首字母小写的驼峰命名)。

flowchart LR
    REG["RegisterName('eth', EthereumAPI{})"] --> REFLECT["Reflect on EthereumAPI"]
    REFLECT --> DISCOVER["Find exported methods"]
    DISCOVER --> MAP["GetBalance → eth_getBalance<br/>BlockNumber → eth_blockNumber<br/>Subscribe → eth_subscribe"]
    MAP --> STORE["Store in serviceRegistry"]

    REQ["JSON-RPC Request<br/>{method: 'eth_getBalance'}"] --> LOOKUP["Lookup service + method"]
    LOOKUP --> INVOKE["Reflect.Call(args...)"]
    INVOKE --> RESP["JSON-RPC Response"]

如果方法的第一个参数是 context.Context,框架会自动注入请求上下文。返回值为 (Subscription, error) 的方法则会被识别为订阅端点。所有 JSON 序列化和错误封装均由框架统一处理。

提示: 添加新的 RPC 方法时,只需在对应的 API 结构体上新增一个导出方法即可,框架会自动发现它——无需手动注册,也无需代码生成。结构体名称决定命名空间前缀,方法名称决定 RPC 方法后缀。

传输层:HTTP、WebSocket、IPC

正如第 2 篇所介绍的,Node 在初始化过程中会创建并管理多个传输端点。RPC 框架支持以下四种传输方式:

flowchart TD
    subgraph "Unauthenticated"
        HTTP["HTTP Server<br/>:8545<br/>Stateless requests"]
        WS["WebSocket Server<br/>:8546<br/>Subscriptions"]
        IPC["IPC Server<br/>Unix socket<br/>Local access only"]
    end
    subgraph "Authenticated (JWT)"
        HTTP_AUTH["Auth HTTP<br/>:8551<br/>Engine API"]
        WS_AUTH["Auth WebSocket<br/>:8551<br/>Engine API subscriptions"]
    end
    subgraph "In-Process"
        INPROC["InProc Handler<br/>Direct function calls<br/>No serialization"]
    end
    HTTP --> RPC_SERVER["rpc.Server<br/>serviceRegistry"]
    WS --> RPC_SERVER
    IPC --> RPC_SERVER
    HTTP_AUTH --> RPC_SERVER
    WS_AUTH --> RPC_SERVER
    INPROC --> RPC_SERVER

认证端点(httpAuthwsAuth)专门用于 Engine API,采用 JWT 认证机制——共识层和执行层共享一个密钥,每个请求都必须携带有效的 JWT token。Node 的 startRPC() 方法负责初始化所有传输层,认证端点仅在存在需要认证的 API 方法时才会被创建。

进程内处理器(inprocHandler)值得单独说明。它由 node.Attach() 使用,用于创建一个无需经过网络序列化、直接调用方法的 RPC 客户端。geth console 命令正是通过这种方式连接到自身节点的。

Backend 接口与 EthAPIBackend

ethapi.Backend 接口是将所有 RPC 处理器与区块链内部实现解耦的核心抽象,涵盖链访问、交易池操作、配置以及日志过滤等超过 40 个方法:

type Backend interface {
    // General Ethereum API
    SyncProgress(ctx context.Context) ethereum.SyncProgress
    SuggestGasTipCap(ctx context.Context) (*big.Int, error)
    ChainDb() ethdb.Database
    AccountManager() *accounts.Manager
    RPCGasCap() uint64
    RPCEVMTimeout() time.Duration

    // Blockchain API
    HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)

    // Transaction pool API
    SendTx(ctx context.Context, signedTx *types.Transaction) error
    GetPoolTransaction(txHash common.Hash) *types.Transaction
    // ... many more
}
classDiagram
    class Backend {
        <<interface>>
        +SyncProgress() SyncProgress
        +HeaderByNumber(number) Header
        +BlockByNumber(number) Block
        +StateAndHeaderByNumber(number) StateDB, Header
        +SendTx(tx) error
        +GetPoolTransaction(hash) Transaction
        +ChainConfig() ChainConfig
        +Engine() Engine
    }
    class EthAPIBackend {
        -eth *Ethereum
        -gpo *OracleBackend
        +HeaderByNumber() Header
        +SendTx() error
    }
    class EthereumAPI {
        -b Backend
        +GetBalance(addr, block)
        +GetTransactionByHash(hash)
    }
    EthAPIBackend ..|> Backend
    EthereumAPI --> Backend : uses

EthAPIBackend 的具体实现位于 eth/api_backend.go,它将每个方法简单地委托给 Ethereum 结构体的对应组件——链查询走 eth.blockchain,交易池操作走 eth.txPool,共识引擎访问走 eth.engine

这层间接调用是有意为之的。RPC 处理器永远不会直接操作区块链或交易池,始终通过 Backend 接口进行访问。这样一来,处理器可以配合 mock backend 进行单元测试,理论上也允许不同类型的节点(全节点、轻节点)复用同一套处理器代码。

API 命名空间与注册

Ethereum 服务通过 APIs() 方法注册其 API 命名空间:

func (s *Ethereum) APIs() []rpc.API {
    apis := ethapi.GetAPIs(s.APIBackend)  // eth, txpool, debug namespaces
    return append(apis, []rpc.API{
        {Namespace: "miner",  Service: NewMinerAPI(s)},
        {Namespace: "eth",    Service: downloader.NewDownloaderAPI(...)},
        {Namespace: "admin",  Service: NewAdminAPI(s)},
        {Namespace: "debug",  Service: NewDebugAPI(s)},
        {Namespace: "net",    Service: s.netRPCService},
    }...)
}

ethapi 中的 GetAPIs() 函数负责创建核心 API 结构体:

func GetAPIs(apiBackend Backend) []rpc.API {
    return []rpc.API{
        {Namespace: "eth",    Service: NewEthereumAPI(apiBackend)},
        {Namespace: "eth",    Service: NewBlockChainAPI(apiBackend)},
        {Namespace: "eth",    Service: NewTransactionAPI(apiBackend, nonceLock)},
        {Namespace: "txpool", Service: NewTxPoolAPI(apiBackend)},
        // ...
    }
}

多个服务可以注册在同一个命名空间下——所有 eth_* 方法实际上来自三个不同的结构体(EthereumAPIBlockChainAPITransactionAPI),但对调用方而言,它们都统一呈现在 eth 命名空间下。

flowchart TD
    subgraph "eth namespace"
        EA["EthereumAPI<br/>eth_chainId, eth_syncing,<br/>eth_gasPrice"]
        BCA["BlockChainAPI<br/>eth_getBalance, eth_getCode,<br/>eth_call, eth_estimateGas"]
        TXA["TransactionAPI<br/>eth_sendRawTransaction,<br/>eth_getTransactionByHash"]
    end
    subgraph "Other namespaces"
        MINER["MinerAPI<br/>miner_setGasPrice,<br/>miner_setExtra"]
        ADMIN["AdminAPI<br/>admin_peers,<br/>admin_nodeInfo"]
        DEBUG["DebugAPI<br/>debug_traceTransaction,<br/>debug_dumpBlock"]
        NET["NetAPI<br/>net_version,<br/>net_peerCount"]
    end

Engine API:由共识层驱动执行层

在合并(Merge)之后的以太坊中,最关键的 RPC 接口并非面向公众的 eth_* API,而是 Engine API。这个需要身份认证的 JSON-RPC 接口,是共识层客户端向 Geth 发出指令的通道。

Engine API 包含三类核心方法:

  1. engine_forkchoiceUpdatedV* — 共识层通知执行层哪个区块是当前头部、哪个是安全区块、哪个已经最终确定。可选附带 payload 请求,指示执行层开始构建新区块。

  2. engine_newPayloadV* — 共识层将新的执行 payload(区块)发送给执行层进行验证,Geth 执行后返回验证结果。

  3. engine_getPayloadV* — 共识层获取此前请求执行层构建的 payload。

sequenceDiagram
    participant CL as Consensus Layer
    participant Engine as Engine API (authenticated)
    participant Miner as Miner
    participant BC as BlockChain

    CL->>Engine: engine_forkchoiceUpdatedV3<br/>(headHash, safeHash, finalHash, payloadAttrs)
    Engine->>BC: Update fork choice head
    Engine->>Miner: BuildPayload(payloadAttrs)
    Miner-->>Engine: payloadId

    Note over Miner: Miner builds block in background...

    CL->>Engine: engine_getPayloadV4(payloadId)
    Engine->>Miner: GetPayload(payloadId)
    Miner-->>Engine: ExecutionPayload + BlobsBundle
    Engine-->>CL: Return payload

    Note over CL: CL proposes block to network

    CL->>Engine: engine_newPayloadV4(payload)
    Engine->>BC: InsertBlockWithoutSealVerification
    BC-->>Engine: valid/invalid/syncing
    Engine-->>CL: PayloadStatus

Miner 结构体虽然沿用了工作量证明时代的命名,但如今它的实际职责是 payload 构建器。当共识层通过 forkchoiceUpdated 请求 payload 时,Miner 开始组装区块——从交易池中筛选交易、执行交易、计算状态根,并准备好执行 payload。共识层随后可通过 getPayload 来获取结果。

type Miner struct {
    confMu      sync.RWMutex
    config      *Config
    chainConfig *params.ChainConfig
    engine      consensus.Engine
    txpool      *txpool.TxPool
    prio        []common.Address  // Prioritized senders
    chain       *core.BlockChain
    pending     *pending
}

Engine API 端点被注册为需要认证的 API,只在受 JWT 保护的传输层上可用。Node 的 getAPIs() 方法会将需要认证的 API 与不需要认证的 API 分离,确保 Engine API 方法永远不会在公开的 HTTP/WS 端点上被调用。

提示: Engine API 的具体实现位于 catalyst 包(eth/catalyst/)。如果你想深入了解共识层与执行层的交互细节,从 catalyst/api.go 入手是最好的选择——ForkchoiceUpdatedV*NewPayloadV*GetPayloadV* 的实现都在这里。

至此,我们已经完整介绍了 RPC 与 API 层,也走完了 Geth 每个主要子系统的旅程——从启动流程、区块执行、数据存储、网络通信,到对外提供的 API 接口。最后一篇文章将把这一切融会贯通,为你提供构建、测试和贡献 Geth 项目的实用指南。