Skip to content

Commit 712ed9f

Browse files
Giulio2002claude
andcommitted
[WIP] EIP-8161: SSZ-REST Engine API transport (EL side)
Implements the EL side of EIP-8161, adding an SSZ-REST HTTP server alongside the existing JSON-RPC Engine API. All engine_* methods are mapped to REST endpoints with SSZ-encoded request/response bodies, cutting payload sizes ~50% and eliminating JSON encode/decode overhead. - New SSZ-REST HTTP server with JWT auth (same secret as JSON-RPC) - SSZ encode/decode for all Engine API types (PayloadStatus, ForkchoiceUpdatedResponse, NewPayloadRequest, GetPayloadResponse, GetBlobs, ExchangeCapabilities, ClientVersion, CommunicationChannels) - CLI flags: --authrpc.ssz-rest, --authrpc.ssz-rest-port - EIP-8160 integration: advertises ssz_rest channel via engine_getClientCommunicationChannelsV1 - Handles V4 (Electra) and V5 (Fulu) with correct fork version mapping - Proper SSZ Union types for optional fields (latest_valid_hash, payload_id) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fe17d1f commit 712ed9f

File tree

14 files changed

+3530
-0
lines changed

14 files changed

+3530
-0
lines changed

cmd/rpcdaemon/cli/httpcfg/http_cfg.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,8 @@ type HttpCfg struct {
111111

112112
RpcTxSyncDefaultTimeout time.Duration // Default timeout for eth_sendRawTransactionSync
113113
RpcTxSyncMaxTimeout time.Duration // Maximum timeout for eth_sendRawTransactionSync
114+
115+
// EIP-8161: SSZ-REST Engine API Transport
116+
SszRestEnabled bool // Enable SSZ-REST Engine API server alongside JSON-RPC
117+
SszRestPort int // Port for the SSZ-REST Engine API server (default: AuthRpcPort + 1)
114118
}

cmd/utils/flags.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,16 @@ var (
326326
Value: "",
327327
}
328328

329+
SszRestEnabledFlag = cli.BoolFlag{
330+
Name: "authrpc.ssz-rest",
331+
Usage: "Enable the SSZ-REST Engine API transport (EIP-8161) alongside JSON-RPC",
332+
}
333+
SszRestPortFlag = cli.UintFlag{
334+
Name: "authrpc.ssz-rest-port",
335+
Usage: "HTTP port for the SSZ-REST Engine API server (default: authrpc.port + 1)",
336+
Value: 0,
337+
}
338+
329339
HttpCompressionFlag = cli.BoolFlag{
330340
Name: "http.compression",
331341
Usage: "Enable compression over HTTP-RPC. Use --http.compression=false to disable it",

execution/engineapi/engine_api_jsonrpc_client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,17 @@ func (c *JsonRpcClient) GetClientVersionV1(ctx context.Context, callerVersion *e
396396
}, c.backOff(ctx))
397397
}
398398

399+
func (c *JsonRpcClient) GetClientCommunicationChannelsV1(ctx context.Context) ([]enginetypes.CommunicationChannel, error) {
400+
return backoff.RetryWithData(func() ([]enginetypes.CommunicationChannel, error) {
401+
var result []enginetypes.CommunicationChannel
402+
err := c.rpcClient.CallContext(ctx, &result, "engine_getClientCommunicationChannelsV1")
403+
if err != nil {
404+
return nil, c.maybeMakePermanent(err)
405+
}
406+
return result, nil
407+
}, c.backOff(ctx))
408+
}
409+
399410
func (c *JsonRpcClient) backOff(ctx context.Context) backoff.BackOff {
400411
var backOff backoff.BackOff
401412
backOff = backoff.NewConstantBackOff(c.retryBackOff)

execution/engineapi/engine_api_methods.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ var ourCapabilities = []string{
5454
"engine_getBlobsV1",
5555
"engine_getBlobsV2",
5656
"engine_getBlobsV3",
57+
"engine_getClientCommunicationChannelsV1",
5758
}
5859

5960
// Returns the most recent version of the payload(for the payloadID) at the time of receiving the call
@@ -284,3 +285,37 @@ func (e *EngineServer) GetBlobsV3(ctx context.Context, blobHashes []common.Hash)
284285
}
285286
return nil, err
286287
}
288+
289+
// GetClientCommunicationChannelsV1 returns the communication protocols and endpoints supported by the EL.
290+
// See EIP-8160 and EIP-8161
291+
func (e *EngineServer) GetClientCommunicationChannelsV1(ctx context.Context) ([]engine_types.CommunicationChannel, error) {
292+
e.engineLogSpamer.RecordRequest()
293+
294+
addr := "localhost"
295+
port := 8551
296+
if e.httpConfig != nil {
297+
if e.httpConfig.AuthRpcHTTPListenAddress != "" {
298+
addr = e.httpConfig.AuthRpcHTTPListenAddress
299+
}
300+
if e.httpConfig.AuthRpcPort != 0 {
301+
port = e.httpConfig.AuthRpcPort
302+
}
303+
}
304+
305+
channels := []engine_types.CommunicationChannel{
306+
{
307+
Protocol: "json_rpc",
308+
URL: fmt.Sprintf("%s:%d", addr, port),
309+
},
310+
}
311+
312+
// EIP-8161: Advertise the SSZ-REST channel if the server is running
313+
if e.httpConfig != nil && e.httpConfig.SszRestEnabled && e.sszRestPort > 0 {
314+
channels = append(channels, engine_types.CommunicationChannel{
315+
Protocol: "ssz_rest",
316+
URL: fmt.Sprintf("http://%s:%d", addr, e.sszRestPort),
317+
})
318+
}
319+
320+
return channels, nil
321+
}

execution/engineapi/engine_server.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ type EngineServer struct {
8787
// TODO Remove this on next release
8888
printPectraBanner bool
8989
maxReorgDepth uint64
90+
httpConfig *httpcfg.HttpCfg
91+
sszRestPort int // EIP-8161: port the SSZ-REST server is listening on
9092
}
9193

9294
func NewEngineServer(
@@ -140,6 +142,7 @@ func (e *EngineServer) Start(
140142
return nil
141143
})
142144
}
145+
e.httpConfig = httpConfig
143146
base := jsonrpc.NewBaseApi(filters, stateCache, blockReader, httpConfig.WithDatadir, httpConfig.EvmCallTimeout, engineReader, httpConfig.Dirs, nil, httpConfig.RangeLimit)
144147
ethImpl := jsonrpc.NewEthAPI(base, db, eth, e.txpool, mining, jsonrpc.NewEthApiConfig(httpConfig), e.logger)
145148

@@ -164,6 +167,39 @@ func (e *EngineServer) Start(
164167
}
165168
return err
166169
})
170+
171+
// EIP-8161: Start SSZ-REST Engine API server if enabled
172+
if httpConfig.SszRestEnabled {
173+
eg.Go(func() error {
174+
defer e.logger.Debug("[EngineServer] SSZ-REST server goroutine terminated")
175+
jwtSecret, err := cli.ObtainJWTSecret(httpConfig, e.logger)
176+
if err != nil {
177+
e.logger.Error("[EngineServer] failed to obtain JWT secret for SSZ-REST server", "err", err)
178+
return err
179+
}
180+
181+
addr := httpConfig.AuthRpcHTTPListenAddress
182+
if addr == "" {
183+
addr = "127.0.0.1"
184+
}
185+
port := httpConfig.SszRestPort
186+
if port == 0 {
187+
port = httpConfig.AuthRpcPort + 1
188+
if httpConfig.AuthRpcPort == 0 {
189+
port = 8552
190+
}
191+
}
192+
e.sszRestPort = port
193+
194+
sszServer := NewSszRestServer(e, e.logger, jwtSecret, addr, port)
195+
err = sszServer.Start(ctx)
196+
if err != nil && !errors.Is(err, context.Canceled) {
197+
e.logger.Error("[EngineServer] SSZ-REST server background goroutine failed", "err", err)
198+
}
199+
return err
200+
})
201+
}
202+
167203
return eg.Wait()
168204
}
169205

execution/engineapi/engine_server_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,35 @@ func TestGetPayloadBodiesByHashV2(t *testing.T) {
348348
req.Equal(hexutil.Bytes(balBytes), bodies[0].BlockAccessList)
349349
}
350350

351+
func TestGetClientCommunicationChannelsV1(t *testing.T) {
352+
mockSentry := execmoduletester.New(t, execmoduletester.WithTxPool(), execmoduletester.WithChainConfig(chain.AllProtocolChanges))
353+
req := require.New(t)
354+
355+
executionRpc := direct.NewExecutionClientDirect(mockSentry.ExecModule)
356+
maxReorgDepth := ethconfig.Defaults.MaxReorgDepth
357+
engineServer := NewEngineServer(mockSentry.Log, mockSentry.ChainConfig, executionRpc, nil, false, false, true, nil, ethconfig.Defaults.FcuTimeout, maxReorgDepth)
358+
359+
ctx := context.Background()
360+
361+
// Before Start (no httpConfig set) — should return defaults
362+
channels, err := engineServer.GetClientCommunicationChannelsV1(ctx)
363+
req.NoError(err)
364+
req.Len(channels, 1)
365+
req.Equal("json_rpc", channels[0].Protocol)
366+
req.Equal("localhost:8551", channels[0].URL)
367+
368+
// After setting httpConfig via Start-like initialization
369+
engineServer.httpConfig = &httpcfg.HttpCfg{
370+
AuthRpcHTTPListenAddress: "0.0.0.0",
371+
AuthRpcPort: 9551,
372+
}
373+
channels, err = engineServer.GetClientCommunicationChannelsV1(ctx)
374+
req.NoError(err)
375+
req.Len(channels, 1)
376+
req.Equal("json_rpc", channels[0].Protocol)
377+
req.Equal("0.0.0.0:9551", channels[0].URL)
378+
}
379+
351380
func TestGetPayloadBodiesByRangeV2(t *testing.T) {
352381
mockSentry := execmoduletester.New(t, execmoduletester.WithTxPool(), execmoduletester.WithChainConfig(chain.AllProtocolChanges))
353382
req := require.New(t)

0 commit comments

Comments
 (0)