feat: update SSZ engine and REST transport

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Giulio 2026-03-06 18:10:39 +01:00
parent 959dd13761
commit df8fab51f0
2 changed files with 38 additions and 42 deletions

View file

@ -76,15 +76,11 @@ func SSZToEngineStatus(status uint8) string {
const payloadStatusFixedSize = 9 // status(1) + hash_offset(4) + err_offset(4) const payloadStatusFixedSize = 9 // status(1) + hash_offset(4) + err_offset(4)
// EncodePayloadStatusSSZ encodes a PayloadStatusV1 to SSZ bytes per EIP-8161. // EncodePayloadStatusSSZ encodes a PayloadStatusV1 to SSZ bytes per EIP-8161.
// Uses List[Hash32, 1] encoding: 0 bytes if nil, 32 bytes if present.
func EncodePayloadStatusSSZ(ps *PayloadStatusV1) []byte { func EncodePayloadStatusSSZ(ps *PayloadStatusV1) []byte {
// Build Union[None, Hash32] for latest_valid_hash var hashData []byte
var hashUnion []byte
if ps.LatestValidHash != nil { if ps.LatestValidHash != nil {
hashUnion = make([]byte, 33) // selector(1) + hash(32) hashData = ps.LatestValidHash[:]
hashUnion[0] = 1
copy(hashUnion[1:33], ps.LatestValidHash[:])
} else {
hashUnion = []byte{0}
} }
var errorBytes []byte var errorBytes []byte
@ -92,13 +88,13 @@ func EncodePayloadStatusSSZ(ps *PayloadStatusV1) []byte {
errorBytes = []byte(*ps.ValidationError) errorBytes = []byte(*ps.ValidationError)
} }
buf := make([]byte, payloadStatusFixedSize+len(hashUnion)+len(errorBytes)) buf := make([]byte, payloadStatusFixedSize+len(hashData)+len(errorBytes))
buf[0] = EngineStatusToSSZ(ps.Status) buf[0] = EngineStatusToSSZ(ps.Status)
binary.LittleEndian.PutUint32(buf[1:5], uint32(payloadStatusFixedSize)) binary.LittleEndian.PutUint32(buf[1:5], uint32(payloadStatusFixedSize))
binary.LittleEndian.PutUint32(buf[5:9], uint32(payloadStatusFixedSize+len(hashUnion))) binary.LittleEndian.PutUint32(buf[5:9], uint32(payloadStatusFixedSize+len(hashData)))
copy(buf[payloadStatusFixedSize:], hashUnion) copy(buf[payloadStatusFixedSize:], hashData)
copy(buf[payloadStatusFixedSize+len(hashUnion):], errorBytes) copy(buf[payloadStatusFixedSize+len(hashData):], errorBytes)
return buf return buf
} }
@ -119,16 +115,16 @@ func DecodePayloadStatusSSZ(buf []byte) (*PayloadStatusV1, error) {
return nil, fmt.Errorf("PayloadStatusSSZ: offsets out of bounds") return nil, fmt.Errorf("PayloadStatusSSZ: offsets out of bounds")
} }
// Decode Union[None, Hash32] // Decode List[Hash32, 1]: 0 bytes = nil, 32 bytes = hash present
unionData := buf[hashOffset:errOffset] hashData := buf[hashOffset:errOffset]
if len(unionData) > 0 { switch len(hashData) {
if unionData[0] == 1 { case 0:
if len(unionData) < 33 { ps.LatestValidHash = nil
return nil, fmt.Errorf("PayloadStatusSSZ: Union hash data too short") case 32:
} hash := common.BytesToHash(hashData)
hash := common.BytesToHash(unionData[1:33]) ps.LatestValidHash = &hash
ps.LatestValidHash = &hash default:
} return nil, fmt.Errorf("PayloadStatusSSZ: invalid hash list length %d (expected 0 or 32)", len(hashData))
} }
// Decode validation_error // Decode validation_error
@ -175,22 +171,18 @@ const forkchoiceUpdatedResponseFixedSize = 8
func EncodeForkChoiceResponseSSZ(resp *ForkChoiceResponse) []byte { func EncodeForkChoiceResponseSSZ(resp *ForkChoiceResponse) []byte {
psBytes := EncodePayloadStatusSSZ(&resp.PayloadStatus) psBytes := EncodePayloadStatusSSZ(&resp.PayloadStatus)
// Build Union[None, uint64] for payload ID // Build List[Bytes8, 1] for payload ID: 0 bytes if nil, 8 bytes if present
var pidUnion []byte var pidData []byte
if resp.PayloadID != nil { if resp.PayloadID != nil {
pidUnion = make([]byte, 9) // selector(1) + 8 bytes pidData = resp.PayloadID[:]
pidUnion[0] = 1
copy(pidUnion[1:9], resp.PayloadID[:])
} else {
pidUnion = []byte{0}
} }
buf := make([]byte, forkchoiceUpdatedResponseFixedSize+len(psBytes)+len(pidUnion)) buf := make([]byte, forkchoiceUpdatedResponseFixedSize+len(psBytes)+len(pidData))
binary.LittleEndian.PutUint32(buf[0:4], uint32(forkchoiceUpdatedResponseFixedSize)) binary.LittleEndian.PutUint32(buf[0:4], uint32(forkchoiceUpdatedResponseFixedSize))
binary.LittleEndian.PutUint32(buf[4:8], uint32(forkchoiceUpdatedResponseFixedSize+len(psBytes))) binary.LittleEndian.PutUint32(buf[4:8], uint32(forkchoiceUpdatedResponseFixedSize+len(psBytes)))
copy(buf[forkchoiceUpdatedResponseFixedSize:], psBytes) copy(buf[forkchoiceUpdatedResponseFixedSize:], psBytes)
copy(buf[forkchoiceUpdatedResponseFixedSize+len(psBytes):], pidUnion) copy(buf[forkchoiceUpdatedResponseFixedSize+len(psBytes):], pidData)
return buf return buf
} }
@ -215,15 +207,17 @@ func DecodeForkChoiceResponseSSZ(buf []byte) (*ForkChoiceResponse, error) {
} }
resp.PayloadStatus = *ps resp.PayloadStatus = *ps
// Decode Union[None, PayloadID] // Decode List[Bytes8, 1]: 0 bytes = nil, 8 bytes = payload ID present
pidData := buf[pidOffset:] pidData := buf[pidOffset:]
if len(pidData) > 0 && pidData[0] == 1 { switch len(pidData) {
if len(pidData) < 9 { case 0:
return nil, fmt.Errorf("ForkChoiceResponseSSZ: Union payload_id data too short") resp.PayloadID = nil
} case 8:
var pid PayloadID var pid PayloadID
copy(pid[:], pidData[1:9]) copy(pid[:], pidData)
resp.PayloadID = &pid resp.PayloadID = &pid
default:
return nil, fmt.Errorf("ForkChoiceResponseSSZ: invalid payload_id list length %d (expected 0 or 8)", len(pidData))
} }
return resp, nil return resp, nil

View file

@ -270,12 +270,14 @@ func (s *SszRestServer) handleForkchoiceUpdated(w http.ResponseWriter, r *http.R
if len(body) >= fixedSize { if len(body) >= fixedSize {
attrOffset := binary.LittleEndian.Uint32(body[96:100]) attrOffset := binary.LittleEndian.Uint32(body[96:100])
if attrOffset <= uint32(len(body)) && attrOffset < uint32(len(body)) { if attrOffset <= uint32(len(body)) {
unionData := body[attrOffset:] // List[PayloadAttributesV3SSZ, 1]: for variable-size containers,
if len(unionData) > 0 { // the list data starts with a 4-byte offset to the first element.
selector := unionData[0] listData := body[attrOffset:]
if selector == 1 && len(unionData) > 1 { if len(listData) > 4 {
pa, err := engine.DecodePayloadAttributesSSZ(unionData[1:], version) elemOffset := binary.LittleEndian.Uint32(listData[0:4])
if elemOffset <= uint32(len(listData)) {
pa, err := engine.DecodePayloadAttributesSSZ(listData[elemOffset:], version)
if err != nil { if err != nil {
sszErrorResponse(w, http.StatusBadRequest, -32602, err.Error()) sszErrorResponse(w, http.StatusBadRequest, -32602, err.Error())
return return