diff --git a/beacon-chain/rpc/eth/beacon/handlers.go b/beacon-chain/rpc/eth/beacon/handlers.go index 298a5ea7f33c..ca13044ff1b9 100644 --- a/beacon-chain/rpc/eth/beacon/handlers.go +++ b/beacon-chain/rpc/eth/beacon/handlers.go @@ -49,6 +49,102 @@ var ( errMarshalSSZ = errors.New("could not marshal block into SSZ") ) +type blockDecoder func([]byte) (*eth.GenericSignedBeaconBlock, error) + +func decodingError(v string, err error) error { + return fmt.Errorf("could not decode request body into %s consensus block: %w", v, err) +} + +type signedBlockContentPeeker struct { + Block json.RawMessage `json:"signed_block"` +} +type slotPeeker struct { + Block struct { + Slot primitives.Slot `json:"slot,string"` + } `json:"message"` +} + +func versionHeaderFromRequest(body []byte) (string, error) { + // check is required for post deneb fork blocks contents + p := &signedBlockContentPeeker{} + if err := json.Unmarshal(body, p); err != nil { + return "", errors.Wrap(err, "unable to peek slot from block contents") + } + data := body + if len(p.Block) > 0 { + data = p.Block + } + sp := &slotPeeker{} + if err := json.Unmarshal(data, sp); err != nil { + return "", errors.Wrap(err, "unable to peek slot from block") + } + ce := slots.ToEpoch(sp.Block.Slot) + if ce >= params.BeaconConfig().FuluForkEpoch { + return version.String(version.Fulu), nil + } else if ce >= params.BeaconConfig().ElectraForkEpoch { + return version.String(version.Electra), nil + } else if ce >= params.BeaconConfig().DenebForkEpoch { + return version.String(version.Deneb), nil + } else if ce >= params.BeaconConfig().CapellaForkEpoch { + return version.String(version.Capella), nil + } else if ce >= params.BeaconConfig().BellatrixForkEpoch { + return version.String(version.Bellatrix), nil + } else if ce >= params.BeaconConfig().AltairForkEpoch { + return version.String(version.Altair), nil + } else { + return version.String(version.Phase0), nil + } +} + +// validateVersionHeader checks if the version header is required and retrieves it +// from the request. If the version header is not provided and not required, it attempts +// to derive it from the request body. +func validateVersionHeader(r *http.Request, body []byte, versionRequired bool) (string, error) { + versionHeader := r.Header.Get(api.VersionHeader) + if versionRequired && versionHeader == "" { + return "", fmt.Errorf("%s header is required", api.VersionHeader) + } + + if !versionRequired && versionHeader == "" { + var err error + versionHeader, err = versionHeaderFromRequest(body) + if err != nil { + return "", errors.Wrap(err, "could not decode request body for version header") + } + } + + return versionHeader, nil +} + +func readRequestBody(r *http.Request) ([]byte, error) { + return io.ReadAll(r.Body) +} + +// GenericConverter is an example interface that your block structs could implement. +type GenericConverter interface { + ToGeneric() (*eth.GenericSignedBeaconBlock, error) +} + +// decodeGenericJSON uses generics to unmarshal JSON into a type T that also +// provides a ToGeneric() method to produce a *eth.GenericSignedBeaconBlock. +func decodeGenericJSON[T GenericConverter](body []byte, forkVersion string) (*eth.GenericSignedBeaconBlock, error) { + // Create a pointer to the zero value of T. + blockPtr := new(T) + + // Unmarshal JSON into blockPtr. + if err := unmarshalStrict(body, blockPtr); err != nil { + return nil, decodingError(forkVersion, err) + } + + // Call the ToGeneric method on the underlying value. + consensusBlock, err := (*blockPtr).ToGeneric() + if err != nil { + return nil, decodingError(forkVersion, err) + } + + return consensusBlock, nil +} + // GetBlockV2 retrieves block details for given block ID. func (s *Server) GetBlockV2(w http.ResponseWriter, r *http.Request) { ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockV2") @@ -343,342 +439,189 @@ func (s *Server) PublishBlindedBlockV2(w http.ResponseWriter, r *http.Request) { } } -func (s *Server) publishBlindedBlockSSZ(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { // nolint:gocognit - body, err := io.ReadAll(r.Body) +// publishBlindedBlockSSZ reads SSZ-encoded data and publishes a blinded block. +func (s *Server) publishBlindedBlockSSZ(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { + body, err := readRequestBody(r) if err != nil { httputil.HandleError(w, "Could not read request body: "+err.Error(), http.StatusInternalServerError) return } - versionHeader := r.Header.Get(api.VersionHeader) - if versionRequired && versionHeader == "" { - httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest) - } - fuluBlock := ð.SignedBlindedBeaconBlockFulu{} - if err = fuluBlock.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_BlindedFulu{ - BlindedFulu: fuluBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return - } - if versionHeader == version.String(version.Fulu) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Fulu), err.Error()), - http.StatusBadRequest, - ) + versionHeader, err := validateVersionHeader(r, body, versionRequired) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - electraBlock := ð.SignedBlindedBeaconBlockElectra{} - if err = electraBlock.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_BlindedElectra{ - BlindedElectra: electraBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return - } - if versionHeader == version.String(version.Electra) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Electra), err.Error()), - http.StatusBadRequest, - ) + genericBlock, err := decodeBlindedBlockSSZ(versionHeader, body) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - denebBlock := ð.SignedBlindedBeaconBlockDeneb{} - if err = denebBlock.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_BlindedDeneb{ - BlindedDeneb: denebBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return - } - if versionHeader == version.String(version.Deneb) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Deneb), err.Error()), - http.StatusBadRequest, - ) + if err := s.validateBroadcast(ctx, r, genericBlock); err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } + s.proposeBlock(ctx, w, genericBlock) +} - capellaBlock := ð.SignedBlindedBeaconBlockCapella{} - if err = capellaBlock.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_BlindedCapella{ - BlindedCapella: capellaBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return - } - if versionHeader == version.String(version.Capella) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Capella), err.Error()), - http.StatusBadRequest, - ) - return +// decodeBlindedBlockSSZ dispatches to the correct SSZ decoder based on versionHeader. +func decodeBlindedBlockSSZ(versionHeader string, body []byte) (*eth.GenericSignedBeaconBlock, error) { + if decoder, exists := blindedSSZDecoders[versionHeader]; exists { + return decoder(body) } + return nil, fmt.Errorf("body does not represent a valid blinded block type") +} - bellatrixBlock := ð.SignedBlindedBeaconBlockBellatrix{} - if err = bellatrixBlock.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_BlindedBellatrix{ - BlindedBellatrix: bellatrixBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return - } - if versionHeader == version.String(version.Bellatrix) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Bellatrix), err.Error()), - http.StatusBadRequest, - ) - return - } +var blindedSSZDecoders = map[string]blockDecoder{ + version.String(version.Fulu): decodeBlindedFuluSSZ, + version.String(version.Electra): decodeBlindedElectraSSZ, + version.String(version.Deneb): decodeBlindedDenebSSZ, + version.String(version.Capella): decodeBlindedCapellaSSZ, + version.String(version.Bellatrix): decodeBlindedBellatrixSSZ, + version.String(version.Altair): decodeAltairSSZ, + version.String(version.Phase0): decodePhase0SSZ, +} - altairBlock := ð.SignedBeaconBlockAltair{} - if err = altairBlock.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Altair{ - Altair: altairBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return +func decodeBlindedFuluSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + fuluBlock := ð.SignedBlindedBeaconBlockFulu{} + if err := fuluBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError(version.String(version.Fulu), err) } - if versionHeader == version.String(version.Altair) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Altair), err.Error()), - http.StatusBadRequest, - ) - return + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_BlindedFulu{ + BlindedFulu: fuluBlock, + }, + }, nil +} + +func decodeBlindedElectraSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + electraBlock := ð.SignedBlindedBeaconBlockElectra{} + if err := electraBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError(version.String(version.Electra), err) } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_BlindedElectra{ + BlindedElectra: electraBlock, + }, + }, nil +} - phase0Block := ð.SignedBeaconBlock{} - if err = phase0Block.UnmarshalSSZ(body); err == nil { - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Phase0{ - Phase0: phase0Block, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return +func decodeBlindedDenebSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + denebBlock := ð.SignedBlindedBeaconBlockDeneb{} + if err := denebBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError(version.String(version.Deneb), err) } - if versionHeader == version.String(version.Phase0) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Phase0), err.Error()), - http.StatusBadRequest, - ) - return + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_BlindedDeneb{ + BlindedDeneb: denebBlock, + }, + }, nil +} + +func decodeBlindedCapellaSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + capellaBlock := ð.SignedBlindedBeaconBlockCapella{} + if err := capellaBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError(version.String(version.Capella), err) } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_BlindedCapella{ + BlindedCapella: capellaBlock, + }, + }, nil +} - httputil.HandleError(w, "Body does not represent a valid block type", http.StatusBadRequest) +func decodeBlindedBellatrixSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + bellatrixBlock := ð.SignedBlindedBeaconBlockBellatrix{} + if err := bellatrixBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError(version.String(version.Bellatrix), err) + } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_BlindedBellatrix{ + BlindedBellatrix: bellatrixBlock, + }, + }, nil } -func (s *Server) publishBlindedBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { // nolint:gocognit - body, err := io.ReadAll(r.Body) +// publishBlindedBlock reads JSON-encoded data and publishes a blinded block. +func (s *Server) publishBlindedBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { + body, err := readRequestBody(r) if err != nil { httputil.HandleError(w, "Could not read request body", http.StatusInternalServerError) return } - versionHeader := r.Header.Get(api.VersionHeader) - if versionRequired && versionHeader == "" { - httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest) - } - var consensusBlock *eth.GenericSignedBeaconBlock - - var fuluBlock *structs.SignedBlindedBeaconBlockFulu - if err = unmarshalStrict(body, &fuluBlock); err == nil { - consensusBlock, err = fuluBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Fulu) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Fulu), err.Error()), - http.StatusBadRequest, - ) + versionHeader, err := validateVersionHeader(r, body, versionRequired) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - var electraBlock *structs.SignedBlindedBeaconBlockElectra - if err = unmarshalStrict(body, &electraBlock); err == nil { - consensusBlock, err = electraBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Electra) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Electra), err.Error()), - http.StatusBadRequest, - ) + genericBlock, err := decodeBlindedBlockJSON(versionHeader, body) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - var denebBlock *structs.SignedBlindedBeaconBlockDeneb - if err = unmarshalStrict(body, &denebBlock); err == nil { - consensusBlock, err = denebBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Deneb) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Deneb), err.Error()), - http.StatusBadRequest, - ) + if err := s.validateBroadcast(ctx, r, genericBlock); err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } + s.proposeBlock(ctx, w, genericBlock) +} - var capellaBlock *structs.SignedBlindedBeaconBlockCapella - if err = unmarshalStrict(body, &capellaBlock); err == nil { - consensusBlock, err = capellaBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Capella) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Capella), err.Error()), - http.StatusBadRequest, - ) - return +// decodeBlindedBlockJSON dispatches to the correct JSON decoder based on versionHeader. +func decodeBlindedBlockJSON(versionHeader string, body []byte) (*eth.GenericSignedBeaconBlock, error) { + if decoder, exists := blindedJSONDecoders[versionHeader]; exists { + return decoder(body) } + return nil, fmt.Errorf("body does not represent a valid blinded block type") +} - var bellatrixBlock *structs.SignedBlindedBeaconBlockBellatrix - if err = unmarshalStrict(body, &bellatrixBlock); err == nil { - consensusBlock, err = bellatrixBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Bellatrix) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Bellatrix), err.Error()), - http.StatusBadRequest, - ) - return - } +var blindedJSONDecoders = map[string]blockDecoder{ + version.String(version.Fulu): decodeBlindedFuluJSON, + version.String(version.Electra): decodeBlindedElectraJSON, + version.String(version.Deneb): decodeBlindedDenebJSON, + version.String(version.Capella): decodeBlindedCapellaJSON, + version.String(version.Bellatrix): decodeBlindedBellatrixJSON, + version.String(version.Altair): decodeAltairJSON, + version.String(version.Phase0): decodePhase0JSON, +} - var altairBlock *structs.SignedBeaconBlockAltair - if err = unmarshalStrict(body, &altairBlock); err == nil { - consensusBlock, err = altairBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Altair) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Altair), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeBlindedFuluJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBlindedBeaconBlockFulu]( + body, + version.String(version.Fulu), + ) +} - var phase0Block *structs.SignedBeaconBlock - if err = unmarshalStrict(body, &phase0Block); err == nil { - consensusBlock, err = phase0Block.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - if versionHeader == version.String(version.Phase0) { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Phase0), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeBlindedElectraJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBlindedBeaconBlockElectra]( + body, + version.String(version.Electra), + ) +} - httputil.HandleError(w, "Body does not represent a valid block type", http.StatusBadRequest) +func decodeBlindedDenebJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBlindedBeaconBlockDeneb]( + body, + version.String(version.Deneb), + ) +} + +func decodeBlindedCapellaJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBlindedBeaconBlockCapella]( + body, + version.String(version.Capella), + ) +} + +func decodeBlindedBellatrixJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBlindedBeaconBlockBellatrix]( + body, + version.String(version.Bellatrix), + ) } // PublishBlock instructs the beacon node to broadcast a newly signed beacon block to the beacon network, @@ -724,473 +667,281 @@ func (s *Server) PublishBlockV2(w http.ResponseWriter, r *http.Request) { } } -type signedBlockContentPeeker struct { - Block json.RawMessage `json:"signed_block"` -} -type slotPeeker struct { - Block struct { - Slot primitives.Slot `json:"slot,string"` - } `json:"message"` -} - -func versionHeaderFromRequest(body []byte) (string, error) { - // check is required for post deneb fork blocks contents - p := &signedBlockContentPeeker{} - if err := json.Unmarshal(body, p); err != nil { - return "", errors.Wrap(err, "unable to peek slot from block contents") - } - data := body - if len(p.Block) > 0 { - data = p.Block - } - sp := &slotPeeker{} - if err := json.Unmarshal(data, sp); err != nil { - return "", errors.Wrap(err, "unable to peek slot from block") - } - ce := slots.ToEpoch(sp.Block.Slot) - if ce >= params.BeaconConfig().FuluForkEpoch { - return version.String(version.Fulu), nil - } else if ce >= params.BeaconConfig().ElectraForkEpoch { - return version.String(version.Electra), nil - } else if ce >= params.BeaconConfig().DenebForkEpoch { - return version.String(version.Deneb), nil - } else if ce >= params.BeaconConfig().CapellaForkEpoch { - return version.String(version.Capella), nil - } else if ce >= params.BeaconConfig().BellatrixForkEpoch { - return version.String(version.Bellatrix), nil - } else if ce >= params.BeaconConfig().AltairForkEpoch { - return version.String(version.Altair), nil - } else { - return version.String(version.Phase0), nil - } -} - -// nolint:gocognit -func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { // nolint:gocognit - body, err := io.ReadAll(r.Body) +// publishBlockSSZ handles publishing an SSZ-encoded block to the beacon node. +func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { + body, err := readRequestBody(r) if err != nil { httputil.HandleError(w, "Could not read request body", http.StatusInternalServerError) return } - versionHeader := r.Header.Get(api.VersionHeader) - if versionRequired && versionHeader == "" { - httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest) + + versionHeader, err := validateVersionHeader(r, body, versionRequired) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - if !versionRequired && versionHeader == "" { - versionHeader, err = versionHeaderFromRequest(body) - if err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body for version header: %s", err.Error()), - http.StatusBadRequest, - ) - } - } - if versionHeader == version.String(version.Fulu) { - fuluBlock := ð.SignedBeaconBlockContentsFulu{} - if err = fuluBlock.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Fulu), err.Error()), - http.StatusBadRequest, - ) - return - } - - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Fulu{ - Fulu: fuluBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - if errors.Is(err, errEquivocatedBlock) { - b, err := blocks.NewSignedBeaconBlock(genericBlock) - if err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - if err := s.broadcastSeenBlockSidecars(ctx, b, genericBlock.GetFulu().Blobs, genericBlock.GetFulu().KzgProofs); err != nil { - log.WithError(err).Error("Failed to broadcast blob sidecars") - } - } - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) + // Decode SSZ into a generic block. + genericBlock, err := decodeSSZToGenericBlock(versionHeader, body) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - if versionHeader == version.String(version.Electra) { - electraBlock := ð.SignedBeaconBlockContentsElectra{} - if err = electraBlock.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Electra), err.Error()), - http.StatusBadRequest, - ) - return - } - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Electra{ - Electra: electraBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - if errors.Is(err, errEquivocatedBlock) { - b, err := blocks.NewSignedBeaconBlock(genericBlock) - if err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - if err := s.broadcastSeenBlockSidecars(ctx, b, genericBlock.GetElectra().Blobs, genericBlock.GetElectra().KzgProofs); err != nil { - log.WithError(err).Error("Failed to broadcast blob sidecars") - } + // Validate and optionally broadcast sidecars on equivocation. + if err := s.validateBroadcast(ctx, r, genericBlock); err != nil { + if errors.Is(err, errEquivocatedBlock) { + b, err := blocks.NewSignedBeaconBlock(genericBlock) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) + return + } + if err = broadcastSidecarsIfSupported(ctx, s, b, genericBlock, versionHeader); err != nil { + log.WithError(err).Error("Failed to broadcast blob sidecars") } - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return } - s.proposeBlock(ctx, w, genericBlock) + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - if versionHeader == version.String(version.Deneb) { - denebBlock := ð.SignedBeaconBlockContentsDeneb{} - if err = denebBlock.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Deneb), err.Error()), - http.StatusBadRequest, - ) - return - } - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Deneb{ - Deneb: denebBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - if errors.Is(err, errEquivocatedBlock) { - b, err := blocks.NewSignedBeaconBlock(genericBlock) - if err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - if err := s.broadcastSeenBlockSidecars(ctx, b, genericBlock.GetDeneb().Blobs, genericBlock.GetDeneb().KzgProofs); err != nil { - log.WithError(err).Error("Failed to broadcast blob sidecars") - } - } - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return + s.proposeBlock(ctx, w, genericBlock) +} + +var sszDecoders = map[string]blockDecoder{ + version.String(version.Fulu): decodeFuluSSZ, + version.String(version.Electra): decodeElectraSSZ, + version.String(version.Deneb): decodeDenebSSZ, + version.String(version.Capella): decodeCapellaSSZ, + version.String(version.Bellatrix): decodeBellatrixSSZ, + version.String(version.Altair): decodeAltairSSZ, + version.String(version.Phase0): decodePhase0SSZ, +} + +// decodeSSZToGenericBlock uses a lookup table to map a version string to the proper decoder. +func decodeSSZToGenericBlock(versionHeader string, body []byte) (*eth.GenericSignedBeaconBlock, error) { + if decoder, found := sszDecoders[versionHeader]; found { + return decoder(body) } + return nil, errors.New("body does not represent a valid block type") +} - if versionHeader == version.String(version.Capella) { - capellaBlock := ð.SignedBeaconBlockCapella{} - if err = capellaBlock.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Capella), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeFuluSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + fuluBlock := ð.SignedBeaconBlockContentsFulu{} + if err := fuluBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Fulu), err, + ) + } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Fulu{Fulu: fuluBlock}, + }, nil +} - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Capella{ - Capella: capellaBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return +func decodeElectraSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + electraBlock := ð.SignedBeaconBlockContentsElectra{} + if err := electraBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Electra), err, + ) } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Electra{Electra: electraBlock}, + }, nil +} - if versionHeader == version.String(version.Bellatrix) { - bellatrixBlock := ð.SignedBeaconBlockBellatrix{} - if err = bellatrixBlock.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Bellatrix), err.Error()), - http.StatusBadRequest, - ) - return - } - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Bellatrix{ - Bellatrix: bellatrixBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return +func decodeDenebSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + denebBlock := ð.SignedBeaconBlockContentsDeneb{} + if err := denebBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Deneb), + err, + ) } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Deneb{ + Deneb: denebBlock, + }, + }, nil +} - if versionHeader == version.String(version.Altair) { - altairBlock := ð.SignedBeaconBlockAltair{} - if err = altairBlock.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Altair), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeCapellaSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + capellaBlock := ð.SignedBeaconBlockCapella{} + if err := capellaBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Capella), + err, + ) + } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Capella{ + Capella: capellaBlock, + }, + }, nil +} - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Altair{ - Altair: altairBlock, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return +func decodeBellatrixSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + bellatrixBlock := ð.SignedBeaconBlockBellatrix{} + if err := bellatrixBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Bellatrix), + err, + ) } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Bellatrix{ + Bellatrix: bellatrixBlock, + }, + }, nil +} - if versionHeader == version.String(version.Phase0) { - phase0Block := ð.SignedBeaconBlock{} - if err = phase0Block.UnmarshalSSZ(body); err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Phase0), err.Error()), - http.StatusBadRequest, - ) - return - } - genericBlock := ð.GenericSignedBeaconBlock{ - Block: ð.GenericSignedBeaconBlock_Phase0{ - Phase0: phase0Block, - }, - } - if err = s.validateBroadcast(ctx, r, genericBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, genericBlock) - return +func decodeAltairSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + altairBlock := ð.SignedBeaconBlockAltair{} + if err := altairBlock.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Altair), + err, + ) } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Altair{ + Altair: altairBlock, + }, + }, nil +} - httputil.HandleError(w, "Body does not represent a valid block type", http.StatusBadRequest) +func decodePhase0SSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) { + phase0Block := ð.SignedBeaconBlock{} + if err := phase0Block.UnmarshalSSZ(body); err != nil { + return nil, decodingError( + version.String(version.Phase0), err, + ) + } + return ð.GenericSignedBeaconBlock{ + Block: ð.GenericSignedBeaconBlock_Phase0{Phase0: phase0Block}, + }, nil } -// nolint:gocognit -func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { // nolint:gocognit - body, err := io.ReadAll(r.Body) +// publishBlock handles publishing a JSON-encoded block to the beacon node. +func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, versionRequired bool) { + body, err := readRequestBody(r) if err != nil { httputil.HandleError(w, "Could not read request body", http.StatusInternalServerError) return } - versionHeader := r.Header.Get(api.VersionHeader) - if versionRequired && versionHeader == "" { - httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest) + + versionHeader, err := validateVersionHeader(r, body, versionRequired) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - if !versionRequired && versionHeader == "" { - versionHeader, err = versionHeaderFromRequest(body) - if err != nil { - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body for version header: %s", err.Error()), - http.StatusBadRequest, - ) - } - } - var consensusBlock *eth.GenericSignedBeaconBlock - if versionHeader == version.String(version.Fulu) { - var fuluBlockContents *structs.SignedBeaconBlockContentsFulu - if err = unmarshalStrict(body, &fuluBlockContents); err == nil { - consensusBlock, err = fuluBlockContents.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - if errors.Is(err, errEquivocatedBlock) { - b, err := blocks.NewSignedBeaconBlock(consensusBlock) - if err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - if err := s.broadcastSeenBlockSidecars(ctx, b, consensusBlock.GetFulu().Blobs, consensusBlock.GetFulu().KzgProofs); err != nil { - log.WithError(err).Error("Failed to broadcast blob sidecars") - } - } - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Fulu), err.Error()), - http.StatusBadRequest, - ) + // Decode JSON into a generic block. + genericBlock, decodeErr := decodeJSONToGenericBlock(versionHeader, body) + if decodeErr != nil { + httputil.HandleError(w, decodeErr.Error(), http.StatusBadRequest) return } - if versionHeader == version.String(version.Electra) { - var electraBlockContents *structs.SignedBeaconBlockContentsElectra - if err = unmarshalStrict(body, &electraBlockContents); err == nil { - consensusBlock, err = electraBlockContents.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - if errors.Is(err, errEquivocatedBlock) { - b, err := blocks.NewSignedBeaconBlock(consensusBlock) - if err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - if err := s.broadcastSeenBlockSidecars(ctx, b, consensusBlock.GetElectra().Blobs, consensusBlock.GetElectra().KzgProofs); err != nil { - log.WithError(err).Error("Failed to broadcast blob sidecars") - } - } - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) + // Validate and optionally broadcast sidecars on equivocation. + if err := s.validateBroadcast(ctx, r, genericBlock); err != nil { + if errors.Is(err, errEquivocatedBlock) { + b, err := blocks.NewSignedBeaconBlock(genericBlock) + if err != nil { + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - } - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Electra), err.Error()), - http.StatusBadRequest, - ) - return - } - if versionHeader == version.String(version.Deneb) { - var denebBlockContents *structs.SignedBeaconBlockContentsDeneb - if err = unmarshalStrict(body, &denebBlockContents); err == nil { - consensusBlock, err = denebBlockContents.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - if errors.Is(err, errEquivocatedBlock) { - b, err := blocks.NewSignedBeaconBlock(consensusBlock) - if err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - if err := s.broadcastSeenBlockSidecars(ctx, b, consensusBlock.GetDeneb().Blobs, consensusBlock.GetDeneb().KzgProofs); err != nil { - log.WithError(err).Error("Failed to broadcast blob sidecars") - } - } - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return + if err := broadcastSidecarsIfSupported(ctx, s, b, genericBlock, versionHeader); err != nil { + log.WithError(err).Error("Failed to broadcast blob sidecars") } } - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Deneb), err.Error()), - http.StatusBadRequest, - ) + httputil.HandleError(w, err.Error(), http.StatusBadRequest) return } - if versionHeader == version.String(version.Capella) { - var capellaBlock *structs.SignedBeaconBlockCapella - if err = unmarshalStrict(body, &capellaBlock); err == nil { - consensusBlock, err = capellaBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } + s.proposeBlock(ctx, w, genericBlock) +} - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Capella), err.Error()), - http.StatusBadRequest, - ) - return +var jsonDecoders = map[string]blockDecoder{ + version.String(version.Fulu): decodeFuluJSON, + version.String(version.Electra): decodeElectraJSON, + version.String(version.Deneb): decodeDenebJSON, + version.String(version.Capella): decodeCapellaJSON, + version.String(version.Bellatrix): decodeBellatrixJSON, + version.String(version.Altair): decodeAltairJSON, + version.String(version.Phase0): decodePhase0JSON, +} + +// decodeJSONToGenericBlock uses a lookup table to map a version string to the proper decoder. +func decodeJSONToGenericBlock(versionHeader string, body []byte) (*eth.GenericSignedBeaconBlock, error) { + if decoder, found := jsonDecoders[versionHeader]; found { + return decoder(body) } + return nil, fmt.Errorf("body does not represent a valid block type") +} - if versionHeader == version.String(version.Bellatrix) { - var bellatrixBlock *structs.SignedBeaconBlockBellatrix - if err = unmarshalStrict(body, &bellatrixBlock); err == nil { - consensusBlock, err = bellatrixBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } +func decodeFuluJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlockContentsFulu]( + body, + version.String(version.Fulu), + ) +} - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Bellatrix), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeElectraJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlockContentsElectra]( + body, + version.String(version.Electra), + ) +} - if versionHeader == version.String(version.Altair) { - var altairBlock *structs.SignedBeaconBlockAltair - if err = unmarshalStrict(body, &altairBlock); err == nil { - consensusBlock, err = altairBlock.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } +func decodeDenebJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlockContentsDeneb]( + body, + version.String(version.Deneb), + ) +} - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Altair), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeCapellaJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlockCapella]( + body, + version.String(version.Capella), + ) +} - if versionHeader == version.String(version.Phase0) { - var phase0Block *structs.SignedBeaconBlock - if err = unmarshalStrict(body, &phase0Block); err == nil { - consensusBlock, err = phase0Block.ToGeneric() - if err == nil { - if err = s.validateBroadcast(ctx, r, consensusBlock); err != nil { - httputil.HandleError(w, err.Error(), http.StatusBadRequest) - return - } - s.proposeBlock(ctx, w, consensusBlock) - return - } - } +func decodeBellatrixJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlockBellatrix]( + body, + version.String(version.Bellatrix), + ) +} - httputil.HandleError( - w, - fmt.Sprintf("Could not decode request body into %s consensus block: %v", version.String(version.Phase0), err.Error()), - http.StatusBadRequest, - ) - return - } +func decodeAltairJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlockAltair]( + body, + version.String(version.Altair), + ) +} + +func decodePhase0JSON(body []byte) (*eth.GenericSignedBeaconBlock, error) { + return decodeGenericJSON[*structs.SignedBeaconBlock]( + body, + version.String(version.Phase0), + ) +} - httputil.HandleError(w, "Body does not represent a valid block type", http.StatusBadRequest) +// broadcastSidecarsIfSupported broadcasts blob sidecars when an equivocated block occurs. +func broadcastSidecarsIfSupported(ctx context.Context, s *Server, b interfaces.SignedBeaconBlock, gb *eth.GenericSignedBeaconBlock, versionHeader string) error { + switch versionHeader { + case version.String(version.Fulu): + return s.broadcastSeenBlockSidecars(ctx, b, gb.GetFulu().Blobs, gb.GetFulu().KzgProofs) + case version.String(version.Electra): + return s.broadcastSeenBlockSidecars(ctx, b, gb.GetElectra().Blobs, gb.GetElectra().KzgProofs) + case version.String(version.Deneb): + return s.broadcastSeenBlockSidecars(ctx, b, gb.GetDeneb().Blobs, gb.GetDeneb().KzgProofs) + default: + // other forks before Deneb do not support blob sidecars + return nil + } } func (s *Server) proposeBlock(ctx context.Context, w http.ResponseWriter, blk *eth.GenericSignedBeaconBlock) { diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go index 6219f8d8ba7f..6f19483a12d4 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_test.go @@ -1528,7 +1528,7 @@ func TestPublishBlock(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Phase0)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Phase0)), writer.Body.String()) }) t.Run("Fulu", func(t *testing.T) { v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) @@ -1564,7 +1564,7 @@ func TestPublishBlock(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -1577,7 +1577,7 @@ func TestPublishBlock(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("syncing", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -1612,6 +1612,20 @@ func TestVersionHeaderFromRequest(t *testing.T) { require.NoError(t, err) require.Equal(t, version.String(version.Fulu), versionHead) }) + t.Run("Blinded Fulu block returns fulu header", func(t *testing.T) { + cfg := params.BeaconConfig().Copy() + cfg.FuluForkEpoch = 7 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + var signedblock *structs.SignedBlindedBeaconBlockFulu + require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &signedblock)) + signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().FuluForkEpoch)) + newBlock, err := json.Marshal(signedblock) + require.NoError(t, err) + versionHead, err := versionHeaderFromRequest(newBlock) + require.NoError(t, err) + require.Equal(t, version.String(version.Fulu), versionHead) + }) t.Run("Electra block contents returns electra header", func(t *testing.T) { cfg := params.BeaconConfig().Copy() cfg.ElectraForkEpoch = 6 @@ -1626,6 +1640,20 @@ func TestVersionHeaderFromRequest(t *testing.T) { require.NoError(t, err) require.Equal(t, version.String(version.Electra), versionHead) }) + t.Run("Blinded Electra block returns electra header", func(t *testing.T) { + cfg := params.BeaconConfig().Copy() + cfg.ElectraForkEpoch = 6 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + var signedblock *structs.SignedBlindedBeaconBlockElectra + require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &signedblock)) + signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().ElectraForkEpoch)) + newBlock, err := json.Marshal(signedblock) + require.NoError(t, err) + versionHead, err := versionHeaderFromRequest(newBlock) + require.NoError(t, err) + require.Equal(t, version.String(version.Electra), versionHead) + }) t.Run("Deneb block contents returns deneb header", func(t *testing.T) { cfg := params.BeaconConfig().Copy() cfg.DenebForkEpoch = 5 @@ -1640,7 +1668,21 @@ func TestVersionHeaderFromRequest(t *testing.T) { require.NoError(t, err) require.Equal(t, version.String(version.Deneb), versionHead) }) - t.Run("Capella block returns capella header", func(t *testing.T) { + t.Run("Blinded Deneb block returns Deneb header", func(t *testing.T) { + cfg := params.BeaconConfig().Copy() + cfg.DenebForkEpoch = 5 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + var signedblock *structs.SignedBlindedBeaconBlockDeneb + require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &signedblock)) + signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().DenebForkEpoch)) + newBlock, err := json.Marshal(signedblock) + require.NoError(t, err) + versionHead, err := versionHeaderFromRequest(newBlock) + require.NoError(t, err) + require.Equal(t, version.String(version.Deneb), versionHead) + }) + t.Run("Capella block returns Capella header", func(t *testing.T) { cfg := params.BeaconConfig().Copy() cfg.CapellaForkEpoch = 4 params.OverrideBeaconConfig(cfg) @@ -1654,6 +1696,20 @@ func TestVersionHeaderFromRequest(t *testing.T) { require.NoError(t, err) require.Equal(t, version.String(version.Capella), versionHead) }) + t.Run("Blinded Capella block returns Capella header", func(t *testing.T) { + cfg := params.BeaconConfig().Copy() + cfg.CapellaForkEpoch = 4 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + var signedblock *structs.SignedBlindedBeaconBlockCapella + require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedCapellaBlock), &signedblock)) + signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().CapellaForkEpoch)) + newBlock, err := json.Marshal(signedblock) + require.NoError(t, err) + versionHead, err := versionHeaderFromRequest(newBlock) + require.NoError(t, err) + require.Equal(t, version.String(version.Capella), versionHead) + }) t.Run("Bellatrix block returns capella header", func(t *testing.T) { cfg := params.BeaconConfig().Copy() cfg.BellatrixForkEpoch = 3 @@ -1668,6 +1724,20 @@ func TestVersionHeaderFromRequest(t *testing.T) { require.NoError(t, err) require.Equal(t, version.String(version.Bellatrix), versionHead) }) + t.Run("Blinded Capella block returns Capella header", func(t *testing.T) { + cfg := params.BeaconConfig().Copy() + cfg.BellatrixForkEpoch = 3 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + var signedblock *structs.SignedBlindedBeaconBlockBellatrix + require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedBellatrixBlock), &signedblock)) + signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().BellatrixForkEpoch)) + newBlock, err := json.Marshal(signedblock) + require.NoError(t, err) + versionHead, err := versionHeaderFromRequest(newBlock) + require.NoError(t, err) + require.Equal(t, version.String(version.Bellatrix), versionHead) + }) t.Run("Altair block returns capella header", func(t *testing.T) { cfg := params.BeaconConfig().Copy() cfg.AltairForkEpoch = 2 @@ -1909,7 +1979,7 @@ func TestPublishBlockSSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -1930,7 +2000,7 @@ func TestPublishBlockSSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("syncing", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -2072,12 +2142,11 @@ func TestPublishBlindedBlock(t *testing.T) { t.Run("Blinded Electra", func(t *testing.T) { v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool { - // Convert back Fulu to Electra when there is at least one difference between Electra and Fulu blocks. - block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedFulu) - converted, err := structs.BlindedBeaconBlockFuluFromConsensus(block.BlindedFulu.Message) + block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedElectra) + converted, err := structs.BlindedBeaconBlockElectraFromConsensus(block.BlindedElectra.Message) require.NoError(t, err) - var signedblock *structs.SignedBlindedBeaconBlockFulu - err = json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &signedblock) + var signedblock *structs.SignedBlindedBeaconBlockElectra + err = json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &signedblock) require.NoError(t, err) require.DeepEqual(t, converted, signedblock.Message) return ok @@ -2094,6 +2163,52 @@ func TestPublishBlindedBlock(t *testing.T) { server.PublishBlindedBlock(writer, request) assert.Equal(t, http.StatusOK, writer.Code) }) + t.Run("Blinded Electra block without version header succeeds", func(t *testing.T) { + cfg := params.BeaconConfig().Copy() + cfg.ElectraForkEpoch = 6 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + var signedblock *structs.SignedBlindedBeaconBlockElectra + require.NoError(t, json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &signedblock)) + signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().ElectraForkEpoch)) + newBlock, err := json.Marshal(signedblock) + require.NoError(t, err) + v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) + v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool { + block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedElectra) + converted, err := structs.BlindedBeaconBlockElectraFromConsensus(block.BlindedElectra.Message) + require.NoError(t, err) + var signedblock *structs.SignedBlindedBeaconBlockElectra + err = json.Unmarshal(newBlock, &signedblock) + require.NoError(t, err) + require.DeepEqual(t, converted, signedblock.Message) + return ok + })) + server := &Server{ + V1Alpha1ValidatorServer: v1alpha1Server, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + } + + request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(newBlock)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + server.PublishBlindedBlock(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + }) + t.Run("Blinded Electra block without version header on wrong fork", func(t *testing.T) { + v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) + server := &Server{ + V1Alpha1ValidatorServer: v1alpha1Server, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + } + request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.BlindedElectraBlock))) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + server.PublishBlindedBlock(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + // block is sent with slot == 1 which means it's in the phase0 fork + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Phase0)), writer.Body.String()) + }) t.Run("Blinded Fulu", func(t *testing.T) { v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool { @@ -2129,7 +2244,7 @@ func TestPublishBlindedBlock(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -2142,7 +2257,7 @@ func TestPublishBlindedBlock(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("syncing", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -2366,7 +2481,7 @@ func TestPublishBlindedBlockSSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -2387,7 +2502,7 @@ func TestPublishBlindedBlockSSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlock(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("syncing", func(t *testing.T) { chainService := &chainMock.ChainService{} @@ -2585,7 +2700,7 @@ func TestPublishBlockV2(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block:", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block:", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -2598,7 +2713,7 @@ func TestPublishBlockV2(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block:", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block:", version.String(version.Capella)), writer.Body.String()) }) t.Run("missing version header", func(t *testing.T) { server := &Server{ @@ -2842,7 +2957,7 @@ func TestPublishBlockV2SSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -2863,7 +2978,7 @@ func TestPublishBlockV2SSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("missing version header", func(t *testing.T) { server := &Server{ @@ -3018,12 +3133,11 @@ func TestPublishBlindedBlockV2(t *testing.T) { t.Run("Blinded Electra", func(t *testing.T) { v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl) v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool { - // Convert back Fulu to Electra when there is at least one difference between Electra and Fulu blocks. - block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedFulu) - converted, err := structs.BlindedBeaconBlockFuluFromConsensus(block.BlindedFulu.Message) + block, ok := req.Block.(*eth.GenericSignedBeaconBlock_BlindedElectra) + converted, err := structs.BlindedBeaconBlockElectraFromConsensus(block.BlindedElectra.Message) require.NoError(t, err) - var signedblock *structs.SignedBlindedBeaconBlockFulu - err = json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &signedblock) + var signedblock *structs.SignedBlindedBeaconBlockElectra + err = json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &signedblock) require.NoError(t, err) require.DeepEqual(t, converted, signedblock.Message) return ok @@ -3075,7 +3189,7 @@ func TestPublishBlindedBlockV2(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block:", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block:", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -3088,7 +3202,7 @@ func TestPublishBlindedBlockV2(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("missing version header", func(t *testing.T) { server := &Server{ @@ -3324,7 +3438,7 @@ func TestPublishBlindedBlockV2SSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Bellatrix)), writer.Body.String()) }) t.Run("wrong version header", func(t *testing.T) { server := &Server{ @@ -3345,7 +3459,7 @@ func TestPublishBlindedBlockV2SSZ(t *testing.T) { writer.Body = &bytes.Buffer{} server.PublishBlindedBlockV2(writer, request) assert.Equal(t, http.StatusBadRequest, writer.Code) - assert.StringContains(t, fmt.Sprintf("Could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) + assert.StringContains(t, fmt.Sprintf("could not decode request body into %s consensus block", version.String(version.Capella)), writer.Body.String()) }) t.Run("missing version header", func(t *testing.T) { server := &Server{ diff --git a/changelog/james-prysm_blockv2-cognit.md b/changelog/james-prysm_blockv2-cognit.md new file mode 100644 index 000000000000..7f57635c20e3 --- /dev/null +++ b/changelog/james-prysm_blockv2-cognit.md @@ -0,0 +1,4 @@ +### Fixed + +- refactored publish block and block ssz functions to fix gocognit +- refactored publish blinded block and blinded block ssz to correctly deal with version headers and sent blocks \ No newline at end of file