Skip to content

Commit 9f42f01

Browse files
Giulio2002CodexGiulio Rebuffo
authored
Added SSZ REST for Engine API (#21203)
## Summary Adds an authenticated SSZ-REST transport for the Engine API alongside the existing JSON-RPC path. - Adds `/engine/v*/payloads`, `/engine/v*/forkchoice`, `/engine/v*/blobs`, `/engine/v1/client/version`, and `/engine/v1/capabilities` handlers. - Advertises SSZ endpoint capabilities through `engine_exchangeCapabilities` while preserving JSON-RPC fallback. - Implements Erigon-native SSZ wire types using `cl/ssz` encode/decode patterns. - Adds focused SSZ-REST handler/wire tests. - Adds Kurtosis devnet material for Prysm + Erigon + Spamoor with 4s slots. ## Verification - Agent verifier completed all checks under `/root/gevm-erigon/agi/sszrest_verifier`. - Live Kurtosis interop run completed with Prysm + Erigon + Spamoor. - Devnet used 4 second slots (`seconds_per_slot = 4`, `slot_duration_ms = 4000`). - Erigon logs showed SSZ-REST `getPayload`, `newPayload`, and `forkchoice` traffic. - Prysm synced blocks and issued forkchoice updates. - Spamoor confirmed transactions. - Final verifier scan found no fatal Engine API errors. --------- Co-authored-by: Codex <codex@example.com> Co-authored-by: Giulio Rebuffo <giulio.rebuffio.rebuffoo²²²²§§@gmail.com>
1 parent 958b2fb commit 9f42f01

8 files changed

Lines changed: 1687 additions & 30 deletions

File tree

cmd/rpcdaemon/cli/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,12 +962,21 @@ func createHandler(cfg *httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Han
962962
return
963963
}
964964

965+
if jwtSecret != nil && AuthenticatedEngineRESTHandler != nil && strings.HasPrefix(r.URL.Path, "/engine/") {
966+
AuthenticatedEngineRESTHandler.ServeHTTP(w, r)
967+
return
968+
}
969+
965970
httpHandler.ServeHTTP(w, r)
966971
})
967972

968973
return handler, nil
969974
}
970975

976+
// AuthenticatedEngineRESTHandler is installed by the in-process Engine API
977+
// server and is invoked only after the same JWT check as authenticated JSON-RPC.
978+
var AuthenticatedEngineRESTHandler http.Handler
979+
971980
func createEngineListener(cfg *httpcfg.HttpCfg, engineApi []rpc.API, logger log.Logger) (*http.Server, *rpc.Server, string, error) {
972981
engineHttpEndpoint := fmt.Sprintf("tcp://%s:%d", cfg.AuthRpcHTTPListenAddress, cfg.AuthRpcPort)
973982

execution/engineapi/engine_api_methods.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ var ourCapabilities = []string{
6464
"engine_getBlobsV1",
6565
"engine_getBlobsV2",
6666
"engine_getBlobsV3",
67+
"POST /engine/v1/payloads",
68+
"POST /engine/v2/payloads",
69+
"POST /engine/v3/payloads",
70+
"POST /engine/v4/payloads",
71+
"POST /engine/v5/payloads",
72+
"GET /engine/v1/payloads/{payload_id}",
73+
"GET /engine/v2/payloads/{payload_id}",
74+
"GET /engine/v3/payloads/{payload_id}",
75+
"GET /engine/v4/payloads/{payload_id}",
76+
"GET /engine/v5/payloads/{payload_id}",
77+
"GET /engine/v6/payloads/{payload_id}",
78+
"POST /engine/v1/forkchoice",
79+
"POST /engine/v2/forkchoice",
80+
"POST /engine/v3/forkchoice",
81+
"POST /engine/v4/forkchoice",
82+
"POST /engine/v1/blobs",
83+
"POST /engine/v2/blobs",
84+
"POST /engine/v3/blobs",
85+
"POST /engine/v1/client/version",
86+
"POST /engine/v1/capabilities",
6787
}
6888

6989
// Returns the most recent version of the payload(for the payloadID) at the time of receiving the call
@@ -193,8 +213,6 @@ func (e *EngineServer) NewPayloadV3(ctx context.Context, payload *engine_types.E
193213
// See https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#engine_newpayloadv4
194214
func (e *EngineServer) NewPayloadV4(ctx context.Context, payload *engine_types.ExecutionPayload,
195215
expectedBlobHashes []common.Hash, parentBeaconBlockRoot *common.Hash, executionRequests []hexutil.Bytes) (*engine_types.PayloadStatus, error) {
196-
// TODO(racytech): add proper version or refactor this part
197-
// add all version ralated checks here so the newpayload doesn't have to deal with checks
198216
return e.newPayload(ctx, payload, expectedBlobHashes, parentBeaconBlockRoot, executionRequests, clparams.ElectraVersion)
199217
}
200218

execution/engineapi/engine_server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ func (e *EngineServer) Start(
142142
) error {
143143
e.filters = filters
144144
e.events = events
145+
cli.AuthenticatedEngineRESTHandler = e.SSZRESTHandler()
145146

146147
var eg errgroup.Group
147148
if !e.internalCL {

execution/engineapi/engine_types/jsonrpc.go

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"errors"
2323
"fmt"
2424

25+
"github.com/erigontech/erigon/cl/clparams"
2526
"github.com/erigontech/erigon/common"
2627
"github.com/erigontech/erigon/common/hexutil"
2728
"github.com/erigontech/erigon/execution/types"
@@ -32,25 +33,26 @@ import (
3233

3334
// ExecutionPayload represents an execution payload (aka block)
3435
type ExecutionPayload struct {
35-
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
36-
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
37-
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
38-
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
39-
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
40-
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
41-
BlockNumber hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
42-
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
43-
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
44-
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
45-
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
46-
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
47-
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
48-
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
49-
Withdrawals []*types.Withdrawal `json:"withdrawals"`
50-
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
51-
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
52-
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
53-
BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"`
36+
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
37+
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
38+
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
39+
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
40+
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
41+
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
42+
BlockNumber hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
43+
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
44+
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
45+
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
46+
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
47+
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
48+
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
49+
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
50+
Withdrawals []*types.Withdrawal `json:"withdrawals"`
51+
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
52+
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
53+
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
54+
BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"`
55+
SSZVersion clparams.StateVersion `json:"-"`
5456
}
5557

5658
// PayloadAttributes represent the attributes required to start assembling a payload
@@ -62,12 +64,13 @@ type ForkChoiceState struct {
6264

6365
// PayloadAttributes represent the attributes required to start assembling a payload
6466
type PayloadAttributes struct {
65-
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
66-
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
67-
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
68-
Withdrawals []*types.Withdrawal `json:"withdrawals"`
69-
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
70-
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
67+
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
68+
PrevRandao common.Hash `json:"prevRandao" gencodec:"required"`
69+
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
70+
Withdrawals []*types.Withdrawal `json:"withdrawals"`
71+
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
72+
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
73+
SSZVersion clparams.StateVersion `json:"-"`
7174
}
7275

7376
// TransitionConfiguration represents the correct configurations of the CL and the EL
@@ -81,9 +84,10 @@ type TransitionConfiguration struct {
8184
// It covers both BlobsBundleV1 (https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#blobsbundlev1)
8285
// and BlobsBundleV2 (https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#blobsbundlev2)
8386
type BlobsBundle struct {
84-
Commitments []hexutil.Bytes `json:"commitments" gencodec:"required"`
85-
Proofs []hexutil.Bytes `json:"proofs" gencodec:"required"`
86-
Blobs []hexutil.Bytes `json:"blobs" gencodec:"required"`
87+
Commitments []hexutil.Bytes `json:"commitments" gencodec:"required"`
88+
Proofs []hexutil.Bytes `json:"proofs" gencodec:"required"`
89+
Blobs []hexutil.Bytes `json:"blobs" gencodec:"required"`
90+
SSZVersion clparams.StateVersion `json:"-"`
8791
}
8892

8993
// BlobsBundleFromTransactions builds a BlobsBundle by extracting blobs,

0 commit comments

Comments
 (0)