对外提供服务: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
认证端点(httpAuth、wsAuth)专门用于 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_* 方法实际上来自三个不同的结构体(EthereumAPI、BlockChainAPI、TransactionAPI),但对调用方而言,它们都统一呈现在 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 包含三类核心方法:
-
engine_forkchoiceUpdatedV*— 共识层通知执行层哪个区块是当前头部、哪个是安全区块、哪个已经最终确定。可选附带 payload 请求,指示执行层开始构建新区块。 -
engine_newPayloadV*— 共识层将新的执行 payload(区块)发送给执行层进行验证,Geth 执行后返回验证结果。 -
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 项目的实用指南。