Skip to content

Commit f1bba86

Browse files
Giulio2002claude
andcommitted
eip-8160: add engine_exchangeCapabilitiesV2 with supportedProtocols
Implements the updated EIP-8160 spec where communication channels are returned as part of engine_exchangeCapabilitiesV2 instead of a separate engine_getClientCommunicationChannelsV1 method. The V2 response includes both capabilities and supportedProtocols fields. The old GetClientCommunicationChannelsV1 method is kept for backward compatibility but now delegates to the shared getSupportedProtocols(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 712ed9f commit f1bba86

File tree

5 files changed

+94
-31
lines changed

5 files changed

+94
-31
lines changed

execution/engineapi/engine_api_jsonrpc_client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ func (c *JsonRpcClient) GetClientCommunicationChannelsV1(ctx context.Context) ([
407407
}, c.backOff(ctx))
408408
}
409409

410+
func (c *JsonRpcClient) ExchangeCapabilitiesV2(fromCl []string) *enginetypes.ExchangeCapabilitiesV2Response {
411+
// JSON-RPC client doesn't implement V2 directly; the server-side handles this.
412+
return nil
413+
}
414+
410415
func (c *JsonRpcClient) backOff(ctx context.Context) backoff.BackOff {
411416
var backOff backoff.BackOff
412417
backOff = backoff.NewConstantBackOff(c.retryBackOff)

execution/engineapi/engine_api_methods.go

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var ourCapabilities = []string{
5555
"engine_getBlobsV2",
5656
"engine_getBlobsV3",
5757
"engine_getClientCommunicationChannelsV1",
58+
"engine_exchangeCapabilitiesV2",
5859
}
5960

6061
// Returns the most recent version of the payload(for the payloadID) at the time of receiving the call
@@ -248,6 +249,46 @@ func (e *EngineServer) ExchangeCapabilities(fromCl []string) []string {
248249
return ourCapabilities
249250
}
250251

252+
// ExchangeCapabilitiesV2 extends ExchangeCapabilities with supportedProtocols (EIP-8160).
253+
func (e *EngineServer) ExchangeCapabilitiesV2(fromCl []string) *engine_types.ExchangeCapabilitiesV2Response {
254+
capabilities := e.ExchangeCapabilities(fromCl)
255+
return &engine_types.ExchangeCapabilitiesV2Response{
256+
Capabilities: capabilities,
257+
SupportedProtocols: e.getSupportedProtocols(),
258+
}
259+
}
260+
261+
// getSupportedProtocols returns the list of communication protocols supported by the EL.
262+
func (e *EngineServer) getSupportedProtocols() []engine_types.CommunicationChannel {
263+
addr := "localhost"
264+
port := 8551
265+
if e.httpConfig != nil {
266+
if e.httpConfig.AuthRpcHTTPListenAddress != "" {
267+
addr = e.httpConfig.AuthRpcHTTPListenAddress
268+
}
269+
if e.httpConfig.AuthRpcPort != 0 {
270+
port = e.httpConfig.AuthRpcPort
271+
}
272+
}
273+
274+
channels := []engine_types.CommunicationChannel{
275+
{
276+
Protocol: "json_rpc",
277+
URL: fmt.Sprintf("%s:%d", addr, port),
278+
},
279+
}
280+
281+
// EIP-8161: Advertise the SSZ-REST channel if the server is running
282+
if e.httpConfig != nil && e.httpConfig.SszRestEnabled && e.sszRestPort > 0 {
283+
channels = append(channels, engine_types.CommunicationChannel{
284+
Protocol: "ssz_rest",
285+
URL: fmt.Sprintf("http://%s:%d", addr, e.sszRestPort),
286+
})
287+
}
288+
289+
return channels
290+
}
291+
251292
func (e *EngineServer) GetBlobsV1(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV1, error) {
252293
e.logger.Debug("[GetBlobsV1] Received Request", "hashes", len(blobHashes))
253294
resp, err := e.getBlobs(ctx, blobHashes, clparams.DenebVersion)
@@ -287,35 +328,8 @@ func (e *EngineServer) GetBlobsV3(ctx context.Context, blobHashes []common.Hash)
287328
}
288329

289330
// GetClientCommunicationChannelsV1 returns the communication protocols and endpoints supported by the EL.
290-
// See EIP-8160 and EIP-8161
331+
// Deprecated: Use ExchangeCapabilitiesV2 instead. Kept for backward compatibility.
291332
func (e *EngineServer) GetClientCommunicationChannelsV1(ctx context.Context) ([]engine_types.CommunicationChannel, error) {
292333
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
334+
return e.getSupportedProtocols(), nil
321335
}

execution/engineapi/engine_ssz_rest_server.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,11 @@ func (s *SszRestServer) registerRoutes(mux *http.ServeMux) {
163163
// getClientVersion
164164
mux.HandleFunc("POST /engine/v1/get_client_version", s.handleGetClientVersion)
165165

166-
// getClientCommunicationChannels
166+
// getClientCommunicationChannels (deprecated, kept for backward compat)
167167
mux.HandleFunc("POST /engine/v1/get_client_communication_channels", s.handleGetClientCommunicationChannels)
168+
169+
// exchangeCapabilitiesV2 (EIP-8160)
170+
mux.HandleFunc("POST /engine/v2/exchange_capabilities", s.handleExchangeCapabilitiesV2)
168171
}
169172

170173
// readBody reads the request body with a size limit.
@@ -610,6 +613,40 @@ func (s *SszRestServer) handleGetClientVersion(w http.ResponseWriter, r *http.Re
610613
sszResponse(w, engine_types.EncodeClientVersions(result))
611614
}
612615

616+
// --- exchangeCapabilitiesV2 handler (EIP-8160) ---
617+
618+
func (s *SszRestServer) handleExchangeCapabilitiesV2(w http.ResponseWriter, r *http.Request) {
619+
s.logger.Info("[SSZ-REST] Received ExchangeCapabilitiesV2")
620+
621+
body, err := readBody(r, 1024*1024)
622+
if err != nil {
623+
sszErrorResponse(w, http.StatusBadRequest, -32602, "failed to read request body")
624+
return
625+
}
626+
627+
capabilities, err := engine_types.DecodeCapabilities(body)
628+
if err != nil {
629+
sszErrorResponse(w, http.StatusBadRequest, -32602, err.Error())
630+
return
631+
}
632+
633+
result := s.engine.ExchangeCapabilitiesV2(capabilities)
634+
// Encode as: capabilities SSZ + communication channels SSZ appended
635+
// For simplicity, use the same capabilities encoding followed by channels encoding.
636+
capBuf := engine_types.EncodeCapabilities(result.Capabilities)
637+
chanBuf := engine_types.EncodeCommunicationChannels(result.SupportedProtocols)
638+
639+
// SSZ Container: capabilities_offset(4) + channels_offset(4) + capabilities_data + channels_data
640+
fixedSize := uint32(8)
641+
buf := make([]byte, 8+len(capBuf)+len(chanBuf))
642+
binary.LittleEndian.PutUint32(buf[0:4], fixedSize)
643+
binary.LittleEndian.PutUint32(buf[4:8], fixedSize+uint32(len(capBuf)))
644+
copy(buf[8:], capBuf)
645+
copy(buf[8+len(capBuf):], chanBuf)
646+
647+
sszResponse(w, buf)
648+
}
649+
613650
// --- getClientCommunicationChannels handler ---
614651

615652
func (s *SszRestServer) handleGetClientCommunicationChannels(w http.ResponseWriter, r *http.Request) {

execution/engineapi/engine_types/jsonrpc.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,18 @@ type ClientVersionV1 struct {
137137
}
138138

139139
// CommunicationChannel describes a protocol and endpoint supported by the EL.
140-
// See EIP-8160: engine_getClientCommunicationChannelsV1
140+
// See EIP-8160: engine_exchangeCapabilitiesV2
141141
type CommunicationChannel struct {
142142
Protocol string `json:"protocol" gencodec:"required"`
143143
URL string `json:"url" gencodec:"required"`
144144
}
145145

146+
// ExchangeCapabilitiesV2Response is the response for engine_exchangeCapabilitiesV2 (EIP-8160).
147+
type ExchangeCapabilitiesV2Response struct {
148+
Capabilities []string `json:"capabilities"`
149+
SupportedProtocols []CommunicationChannel `json:"supportedProtocols"`
150+
}
151+
146152
func (c ClientVersionV1) String() string {
147153
return fmt.Sprintf("ClientCode: %s, %s-%s-%s", c.Code, c.Name, c.Version, c.Commit)
148154
}

execution/engineapi/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ type EngineAPI interface {
4747
GetBlobsV2(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV2, error)
4848
GetBlobsV3(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV2, error)
4949
GetClientCommunicationChannelsV1(ctx context.Context) ([]engine_types.CommunicationChannel, error)
50+
ExchangeCapabilitiesV2(fromCl []string) *engine_types.ExchangeCapabilitiesV2Response
5051
}

0 commit comments

Comments
 (0)