Files
prysm/validator/client/beacon-api/propose_beacon_block.go
james-prysm eaf3aa3e8e simplify get and post block parsing for REST (#16307)
**What type of PR is this?**

 Feature


**What does this PR do? Why is it needed?**

PR is attempts to remove code duplication and process through a map of
configurations for get and post block apis. this should simplify
maintainability.

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-02-10 18:28:55 +00:00

214 lines
8.2 KiB
Go

package beacon_api
import (
"bytes"
"context"
"encoding/json"
"net/http"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/encoding/ssz"
"github.com/OffchainLabs/prysm/v7/network/httputil"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/pkg/errors"
)
type blockProcessingResult struct {
consensusVersion string
beaconBlockRoot [32]byte
marshalledSSZ []byte
blinded bool
// Function to marshal JSON on demand
marshalJSON func() ([]byte, error)
}
type sszMarshaler interface {
MarshalSSZ() ([]byte, error)
}
func buildBlockResult(
versionName string,
blinded bool,
sszObj sszMarshaler,
rootObj ssz.Hashable,
jsonFn func() ([]byte, error),
) (*blockProcessingResult, error) {
beaconBlockRoot, err := rootObj.HashTreeRoot()
if err != nil {
return nil, errors.Wrapf(err, "failed to compute block root for %s beacon block", versionName)
}
marshaledSSZ, err := sszObj.MarshalSSZ()
if err != nil {
return nil, errors.Wrapf(err, "failed to serialize %s beacon block", versionName)
}
return &blockProcessingResult{
consensusVersion: versionName,
blinded: blinded,
beaconBlockRoot: beaconBlockRoot,
marshalledSSZ: marshaledSSZ,
marshalJSON: jsonFn,
}, nil
}
func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
var res *blockProcessingResult
var err error
switch blockType := in.Block.(type) {
case *ethpb.GenericSignedBeaconBlock_Phase0:
res, err = buildBlockResult("phase0", false, blockType.Phase0, blockType.Phase0.Block, func() ([]byte, error) {
return json.Marshal(structs.SignedBeaconBlockPhase0FromConsensus(blockType.Phase0))
})
case *ethpb.GenericSignedBeaconBlock_Altair:
res, err = buildBlockResult("altair", false, blockType.Altair, blockType.Altair.Block, func() ([]byte, error) {
return json.Marshal(structs.SignedBeaconBlockAltairFromConsensus(blockType.Altair))
})
case *ethpb.GenericSignedBeaconBlock_Bellatrix:
res, err = buildBlockResult("bellatrix", false, blockType.Bellatrix, blockType.Bellatrix.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(blockType.Bellatrix)
if err != nil {
return nil, errors.Wrap(err, "failed to convert bellatrix beacon block")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_BlindedBellatrix:
res, err = buildBlockResult("bellatrix", true, blockType.BlindedBellatrix, blockType.BlindedBellatrix.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(blockType.BlindedBellatrix)
if err != nil {
return nil, errors.Wrap(err, "failed to convert blinded bellatrix beacon block")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_Capella:
res, err = buildBlockResult("capella", false, blockType.Capella, blockType.Capella.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(blockType.Capella)
if err != nil {
return nil, errors.Wrap(err, "failed to convert capella beacon block")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_BlindedCapella:
res, err = buildBlockResult("capella", true, blockType.BlindedCapella, blockType.BlindedCapella.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(blockType.BlindedCapella)
if err != nil {
return nil, errors.Wrap(err, "failed to convert blinded capella beacon block")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_Deneb:
res, err = buildBlockResult("deneb", false, blockType.Deneb, blockType.Deneb.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockContentsDenebFromConsensus(blockType.Deneb)
if err != nil {
return nil, errors.Wrap(err, "failed to convert deneb beacon block contents")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_BlindedDeneb:
res, err = buildBlockResult("deneb", true, blockType.BlindedDeneb, blockType.BlindedDeneb, func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockDenebFromConsensus(blockType.BlindedDeneb)
if err != nil {
return nil, errors.Wrap(err, "failed to convert deneb blinded beacon block")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_Electra:
res, err = buildBlockResult("electra", false, blockType.Electra, blockType.Electra.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockContentsElectraFromConsensus(blockType.Electra)
if err != nil {
return nil, errors.Wrap(err, "failed to convert electra beacon block contents")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_BlindedElectra:
res, err = buildBlockResult("electra", true, blockType.BlindedElectra, blockType.BlindedElectra, func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockElectraFromConsensus(blockType.BlindedElectra)
if err != nil {
return nil, errors.Wrap(err, "failed to convert electra blinded beacon block")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_Fulu:
res, err = buildBlockResult("fulu", false, blockType.Fulu, blockType.Fulu.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockContentsFuluFromConsensus(blockType.Fulu)
if err != nil {
return nil, errors.Wrap(err, "failed to convert fulu beacon block contents")
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_BlindedFulu:
res, err = buildBlockResult("fulu", true, blockType.BlindedFulu, blockType.BlindedFulu, func() ([]byte, error) {
signedBlock, err := structs.SignedBlindedBeaconBlockFuluFromConsensus(blockType.BlindedFulu)
if err != nil {
return nil, errors.Wrap(err, "failed to convert fulu blinded beacon block")
}
return json.Marshal(signedBlock)
})
default:
return nil, errors.Errorf("unsupported block type %T", in.Block)
}
if err != nil {
return nil, err
}
endpoint := "/eth/v2/beacon/blocks"
if res.blinded {
endpoint = "/eth/v2/beacon/blinded_blocks"
}
headers := map[string]string{"Eth-Consensus-Version": res.consensusVersion}
// Try PostSSZ first with SSZ data
if res.marshalledSSZ != nil {
_, _, err = c.handler.PostSSZ(ctx, endpoint, headers, bytes.NewBuffer(res.marshalledSSZ))
if err != nil {
errJson := &httputil.DefaultJsonError{}
// If PostSSZ fails with 406 (Not Acceptable), fall back to JSON
if !errors.As(err, &errJson) {
return nil, err
}
if errJson.Code == http.StatusNotAcceptable && res.marshalJSON != nil {
log.WithError(err).Warn("Failed to submit block ssz, falling back to JSON")
jsonData, jsonErr := res.marshalJSON()
if jsonErr != nil {
return nil, errors.Wrap(jsonErr, "failed to marshal JSON")
}
// Reset headers for JSON
err = c.handler.Post(ctx, endpoint, headers, bytes.NewBuffer(jsonData), nil)
// If JSON also fails, return that error
if err != nil {
return nil, errors.Wrap(err, "failed to submit block via JSON fallback")
}
} else {
// For non-406 errors or when no JSON fallback is available, return the SSZ error
return nil, errors.Wrap(errJson, "failed to submit block ssz")
}
}
} else if res.marshalJSON == nil {
return nil, errors.New("no marshalling functions available")
} else {
// No SSZ data available, marshal and use JSON
jsonData, jsonErr := res.marshalJSON()
if jsonErr != nil {
return nil, errors.Wrap(jsonErr, "failed to marshal JSON")
}
// Reset headers for JSON
err = c.handler.Post(ctx, endpoint, headers, bytes.NewBuffer(jsonData), nil)
errJson := &httputil.DefaultJsonError{}
if err != nil {
if !errors.As(err, &errJson) {
return nil, err
}
// Error 202 means that the block was successfully broadcast, but validation failed
if errJson.Code == http.StatusAccepted {
return nil, errors.New("block was successfully broadcast but failed validation")
}
return nil, errJson
}
}
return &ethpb.ProposeResponse{BlockRoot: res.beaconBlockRoot[:]}, nil
}