eip-8161: remove EIP-8160 discovery, use flag-based SSZ-REST configuration

Remove getClientCommunicationChannels, exchangeCapabilitiesV2, and
getSupportedProtocols. The SSZ-REST endpoint is now enabled solely via
the --authrpc.ssz-rest flag. CLs discover it via their own --ssz-rest-url flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Giulio 2026-03-05 16:57:13 +01:00
parent 650273aa6c
commit 8851d6a5be
6 changed files with 0 additions and 287 deletions

View file

@ -229,84 +229,6 @@ func DecodeForkChoiceResponseSSZ(buf []byte) (*ForkChoiceResponse, error) {
return resp, nil
}
// --- CommunicationChannel SSZ ---
// EncodeCommunicationChannelsSSZ encodes communication channels to SSZ.
func EncodeCommunicationChannelsSSZ(channels []CommunicationChannel) []byte {
if len(channels) == 0 {
return []byte{}
}
var totalSize int
for _, ch := range channels {
totalSize += 4 + len(ch.Protocol) + 4 + len(ch.URL)
}
buf := make([]byte, 4+totalSize)
binary.LittleEndian.PutUint32(buf[0:4], uint32(len(channels)))
offset := 4
for _, ch := range channels {
protBytes := []byte(ch.Protocol)
urlBytes := []byte(ch.URL)
binary.LittleEndian.PutUint32(buf[offset:offset+4], uint32(len(protBytes)))
offset += 4
copy(buf[offset:], protBytes)
offset += len(protBytes)
binary.LittleEndian.PutUint32(buf[offset:offset+4], uint32(len(urlBytes)))
offset += 4
copy(buf[offset:], urlBytes)
offset += len(urlBytes)
}
return buf
}
// DecodeCommunicationChannelsSSZ decodes communication channels from SSZ bytes.
func DecodeCommunicationChannelsSSZ(buf []byte) ([]CommunicationChannel, error) {
if len(buf) < 4 {
return nil, fmt.Errorf("CommunicationChannels: buffer too short")
}
count := binary.LittleEndian.Uint32(buf[0:4])
if count > 16 {
return nil, fmt.Errorf("CommunicationChannels: too many channels (%d > 16)", count)
}
channels := make([]CommunicationChannel, 0, count)
offset := uint32(4)
for i := uint32(0); i < count; i++ {
if offset+4 > uint32(len(buf)) {
return nil, fmt.Errorf("CommunicationChannels: unexpected end of buffer")
}
protLen := binary.LittleEndian.Uint32(buf[offset : offset+4])
offset += 4
if protLen > 32 || offset+protLen > uint32(len(buf)) {
return nil, fmt.Errorf("CommunicationChannels: protocol too long or truncated")
}
protocol := string(buf[offset : offset+protLen])
offset += protLen
if offset+4 > uint32(len(buf)) {
return nil, fmt.Errorf("CommunicationChannels: unexpected end of buffer")
}
urlLen := binary.LittleEndian.Uint32(buf[offset : offset+4])
offset += 4
if urlLen > 256 || offset+urlLen > uint32(len(buf)) {
return nil, fmt.Errorf("CommunicationChannels: URL too long or truncated")
}
url := string(buf[offset : offset+urlLen])
offset += urlLen
channels = append(channels, CommunicationChannel{Protocol: protocol, URL: url})
}
return channels, nil
}
// --- Capabilities SSZ ---
// EncodeCapabilitiesSSZ encodes a list of capability strings to SSZ.

View file

@ -180,29 +180,6 @@ func TestCapabilitiesSSZRoundTrip(t *testing.T) {
}
}
func TestCommunicationChannelsSSZRoundTrip(t *testing.T) {
channels := []CommunicationChannel{
{Protocol: "json_rpc", URL: "localhost:8551"},
{Protocol: "ssz_rest", URL: "http://localhost:8552"},
}
encoded := EncodeCommunicationChannelsSSZ(channels)
decoded, err := DecodeCommunicationChannelsSSZ(encoded)
if err != nil {
t.Fatalf("decode error: %v", err)
}
if len(decoded) != len(channels) {
t.Fatalf("length mismatch: got %d, want %d", len(decoded), len(channels))
}
for i, ch := range channels {
if decoded[i].Protocol != ch.Protocol {
t.Errorf("channel[%d].Protocol mismatch: got %s, want %s", i, decoded[i].Protocol, ch.Protocol)
}
if decoded[i].URL != ch.URL {
t.Errorf("channel[%d].URL mismatch: got %s, want %s", i, decoded[i].URL, ch.URL)
}
}
}
func TestClientVersionSSZRoundTrip(t *testing.T) {
cv := &ClientVersionV1{
Code: "GE",

View file

@ -413,14 +413,3 @@ func (v *ClientVersionV1) String() string {
return fmt.Sprintf("%s-%s-%s-%s", v.Code, v.Name, v.Version, v.Commit)
}
// CommunicationChannel represents a communication protocol supported by the EL (EIP-8160).
type CommunicationChannel struct {
Protocol string `json:"protocol"`
URL string `json:"url"`
}
// ExchangeCapabilitiesV2Response is the response to engine_exchangeCapabilitiesV2 (EIP-8160).
type ExchangeCapabilitiesV2Response struct {
Capabilities []string `json:"capabilities"`
SupportedProtocols []CommunicationChannel `json:"supportedProtocols"`
}

View file

@ -52,12 +52,7 @@ import (
func Register(stack *node.Node, backend *eth.Ethereum) error {
api := NewConsensusAPI(backend)
// Configure SSZ-REST fields from the node config
cfg := stack.Config()
api.authAddr = cfg.AuthAddr
api.authPort = cfg.AuthPort
api.sszRestEnabled = cfg.SszRestEnabled
api.sszRestPort = cfg.SszRestPort
stack.RegisterAPIs([]rpc.API{
newTestingAPI(backend),
@ -145,11 +140,6 @@ type ConsensusAPI struct {
forkchoiceLock sync.Mutex // Lock for the forkChoiceUpdated method
newPayloadLock sync.Mutex // Lock for the NewPayload method
// SSZ-REST server config (EIP-8161)
sszRestEnabled bool
sszRestPort int
authAddr string
authPort int
}
// NewConsensusAPI creates a new consensus api for the given backend.
@ -1109,8 +1099,6 @@ func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
// Methods that should not be advertised via V1 capabilities
skip := map[string]bool{
"ExchangeCapabilities": true,
"ExchangeCapabilitiesV2": true,
"GetClientCommunicationChannelsV1": true,
}
valueT := reflect.TypeOf(api)
caps := make([]string, 0, valueT.NumMethod())
@ -1142,37 +1130,6 @@ func (api *ConsensusAPI) GetClientVersionV1(info engine.ClientVersionV1) []engin
}
}
// ExchangeCapabilitiesV2 extends ExchangeCapabilities with supported protocols (EIP-8160).
func (api *ConsensusAPI) ExchangeCapabilitiesV2(fromCl []string) engine.ExchangeCapabilitiesV2Response {
capabilities := api.ExchangeCapabilities(fromCl)
return engine.ExchangeCapabilitiesV2Response{
Capabilities: capabilities,
SupportedProtocols: api.getSupportedProtocols(),
}
}
// GetClientCommunicationChannelsV1 returns the communication protocols supported by this EL (EIP-8160).
func (api *ConsensusAPI) GetClientCommunicationChannelsV1() []engine.CommunicationChannel {
return api.getSupportedProtocols()
}
// getSupportedProtocols returns the list of communication protocols supported by this EL.
func (api *ConsensusAPI) getSupportedProtocols() []engine.CommunicationChannel {
channels := []engine.CommunicationChannel{
{
Protocol: "json_rpc",
URL: fmt.Sprintf("%s:%d", api.authAddr, api.authPort),
},
}
if api.sszRestEnabled && api.sszRestPort > 0 {
channels = append(channels, engine.CommunicationChannel{
Protocol: "ssz_rest",
URL: fmt.Sprintf("http://%s:%d", api.authAddr, api.sszRestPort),
})
}
return channels
}
// GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
// of block bodies by the engine api.
func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBody {

View file

@ -153,11 +153,6 @@ func (s *SszRestServer) registerRoutes(mux *http.ServeMux) {
// getClientVersion
mux.HandleFunc("POST /engine/v1/get_client_version", s.handleGetClientVersion)
// getClientCommunicationChannels (deprecated, kept for backward compat)
mux.HandleFunc("POST /engine/v1/get_client_communication_channels", s.handleGetClientCommunicationChannels)
// exchangeCapabilitiesV2 (EIP-8160)
mux.HandleFunc("POST /engine/v2/exchange_capabilities", s.handleExchangeCapabilitiesV2)
}
// --- newPayload handlers ---
@ -499,48 +494,6 @@ func (s *SszRestServer) handleGetClientVersion(w http.ResponseWriter, r *http.Re
sszResponse(w, engine.EncodeClientVersionsSSZ(result))
}
// --- exchangeCapabilitiesV2 handler (EIP-8160) ---
func (s *SszRestServer) handleExchangeCapabilitiesV2(w http.ResponseWriter, r *http.Request) {
log.Info("[SSZ-REST] Received ExchangeCapabilitiesV2")
body, err := readBody(r, 1024*1024)
if err != nil {
sszErrorResponse(w, http.StatusBadRequest, -32602, "failed to read request body")
return
}
capabilities, err := engine.DecodeCapabilitiesSSZ(body)
if err != nil {
sszErrorResponse(w, http.StatusBadRequest, -32602, err.Error())
return
}
result := s.api.ExchangeCapabilitiesV2(capabilities)
capBuf := engine.EncodeCapabilitiesSSZ(result.Capabilities)
chanBuf := engine.EncodeCommunicationChannelsSSZ(result.SupportedProtocols)
// SSZ Container: capabilities_offset(4) + channels_offset(4) + data
fixedSize := uint32(8)
buf := make([]byte, 8+len(capBuf)+len(chanBuf))
binary.LittleEndian.PutUint32(buf[0:4], fixedSize)
binary.LittleEndian.PutUint32(buf[4:8], fixedSize+uint32(len(capBuf)))
copy(buf[8:], capBuf)
copy(buf[8+len(capBuf):], chanBuf)
sszResponse(w, buf)
}
// --- getClientCommunicationChannels handler ---
func (s *SszRestServer) handleGetClientCommunicationChannels(w http.ResponseWriter, r *http.Request) {
log.Info("[SSZ-REST] Received GetClientCommunicationChannels")
result := s.api.GetClientCommunicationChannelsV1()
sszResponse(w, engine.EncodeCommunicationChannelsSSZ(result))
}
// handleEngineError converts engine errors to appropriate HTTP error responses.
func (s *SszRestServer) handleEngineError(w http.ResponseWriter, err error) {
log.Warn("[SSZ-REST] Engine error", "err", err)

View file

@ -140,88 +140,3 @@ func TestSszRestSuccessFormat(t *testing.T) {
}
}
// TestSszRestExchangeCapabilitiesV2Format tests the V2 response container format.
func TestSszRestExchangeCapabilitiesV2Format(t *testing.T) {
caps := []string{"engine_newPayloadV4", "engine_getPayloadV4"}
channels := []engine.CommunicationChannel{
{Protocol: "json_rpc", URL: "localhost:8551"},
{Protocol: "ssz_rest", URL: "http://localhost:8552"},
}
capBuf := engine.EncodeCapabilitiesSSZ(caps)
chanBuf := engine.EncodeCommunicationChannelsSSZ(channels)
// Build the V2 response container: offset(4) + offset(4) + data
fixedSize := uint32(8)
buf := make([]byte, 8+len(capBuf)+len(chanBuf))
le32(buf[0:4], fixedSize)
le32(buf[4:8], fixedSize+uint32(len(capBuf)))
copy(buf[8:], capBuf)
copy(buf[8+len(capBuf):], chanBuf)
// Decode
capOffset := rd32(buf[0:4])
chanOffset := rd32(buf[4:8])
decodedCaps, err := engine.DecodeCapabilitiesSSZ(buf[capOffset:chanOffset])
if err != nil {
t.Fatal(err)
}
decodedChannels, err := engine.DecodeCommunicationChannelsSSZ(buf[chanOffset:])
if err != nil {
t.Fatal(err)
}
if len(decodedCaps) != 2 || decodedCaps[0] != "engine_newPayloadV4" {
t.Errorf("caps mismatch: %v", decodedCaps)
}
if len(decodedChannels) != 2 || decodedChannels[1].Protocol != "ssz_rest" {
t.Errorf("channels mismatch: %v", decodedChannels)
}
}
// TestSszRestGetSupportedProtocols tests the getSupportedProtocols helper.
func TestSszRestGetSupportedProtocols(t *testing.T) {
api := &ConsensusAPI{
sszRestEnabled: true,
sszRestPort: 8552,
authAddr: "127.0.0.1",
authPort: 8551,
}
channels := api.getSupportedProtocols()
if len(channels) != 2 {
t.Fatalf("expected 2 channels, got %d", len(channels))
}
if channels[0].Protocol != "json_rpc" {
t.Errorf("first channel should be json_rpc, got %s", channels[0].Protocol)
}
if channels[1].Protocol != "ssz_rest" {
t.Errorf("second channel should be ssz_rest, got %s", channels[1].Protocol)
}
if channels[1].URL != "http://127.0.0.1:8552" {
t.Errorf("unexpected URL: %s", channels[1].URL)
}
// Without SSZ-REST
api2 := &ConsensusAPI{
sszRestEnabled: false,
authAddr: "127.0.0.1",
authPort: 8551,
}
channels2 := api2.getSupportedProtocols()
if len(channels2) != 1 {
t.Fatalf("expected 1 channel without SSZ-REST, got %d", len(channels2))
}
}
func le32(buf []byte, v uint32) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
buf[2] = byte(v >> 16)
buf[3] = byte(v >> 24)
}
func rd32(buf []byte) uint32 {
return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
}