fix: /eth/v2/beacon/blocks post api to handle electra and fulu blocks correctly (#14897)

* adding fix and changelog

* adding no lint gocognit for now

* fixing linting

* Update beacon-chain/rpc/eth/beacon/handlers.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update beacon-chain/rpc/eth/beacon/handlers.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update beacon-chain/rpc/eth/beacon/handlers.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* updating based on kasey's suggestions

* preston's comments

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
james-prysm
2025-02-10 17:50:09 -06:00
committed by GitHub
parent 9fbe3564df
commit 26d35474e9
3 changed files with 426 additions and 178 deletions

View File

@@ -724,6 +724,48 @@ 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)
if err != nil {
@@ -735,9 +777,28 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
httputil.HandleError(w, api.VersionHeader+" header is required", 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 := &eth.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
}
fuluBlock := &eth.SignedBeaconBlockContentsFulu{}
if err = fuluBlock.UnmarshalSSZ(body); err == nil {
genericBlock := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Fulu{
Fulu: fuluBlock,
@@ -760,17 +821,17 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
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,
)
return
}
electraBlock := &eth.SignedBeaconBlockContentsElectra{}
if err = electraBlock.UnmarshalSSZ(body); err == nil {
if versionHeader == version.String(version.Electra) {
electraBlock := &eth.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 := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Electra{
Electra: electraBlock,
@@ -793,17 +854,17 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
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,
)
return
}
denebBlock := &eth.SignedBeaconBlockContentsDeneb{}
if err = denebBlock.UnmarshalSSZ(body); err == nil {
if versionHeader == version.String(version.Deneb) {
denebBlock := &eth.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 := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Deneb{
Deneb: denebBlock,
@@ -826,17 +887,18 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
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,
)
return
}
capellaBlock := &eth.SignedBeaconBlockCapella{}
if err = capellaBlock.UnmarshalSSZ(body); err == nil {
if versionHeader == version.String(version.Capella) {
capellaBlock := &eth.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
}
genericBlock := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Capella{
Capella: capellaBlock,
@@ -849,17 +911,17 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
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
}
bellatrixBlock := &eth.SignedBeaconBlockBellatrix{}
if err = bellatrixBlock.UnmarshalSSZ(body); err == nil {
if versionHeader == version.String(version.Bellatrix) {
bellatrixBlock := &eth.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 := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Bellatrix{
Bellatrix: bellatrixBlock,
@@ -872,17 +934,18 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
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
}
altairBlock := &eth.SignedBeaconBlockAltair{}
if err = altairBlock.UnmarshalSSZ(body); err == nil {
if versionHeader == version.String(version.Altair) {
altairBlock := &eth.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
}
genericBlock := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Altair{
Altair: altairBlock,
@@ -895,17 +958,17 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
s.proposeBlock(ctx, w, genericBlock)
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
}
phase0Block := &eth.SignedBeaconBlock{}
if err = phase0Block.UnmarshalSSZ(body); err == nil {
if versionHeader == version.String(version.Phase0) {
phase0Block := &eth.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 := &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Phase0{
Phase0: phase0Block,
@@ -918,18 +981,11 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
s.proposeBlock(ctx, w, genericBlock)
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
}
httputil.HandleError(w, "Body does not represent a valid block type", http.StatusBadRequest)
}
// 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)
if err != nil {
@@ -941,32 +997,41 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest)
return
}
var consensusBlock *eth.GenericSignedBeaconBlock
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
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()),
@@ -975,29 +1040,29 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}
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")
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
}
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
s.proposeBlock(ctx, w, consensusBlock)
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()),
@@ -1006,29 +1071,29 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}
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")
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
}
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
s.proposeBlock(ctx, w, consensusBlock)
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()),
@@ -1037,19 +1102,20 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}
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)
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, 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()),
@@ -1058,19 +1124,20 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}
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)
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
}
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()),
@@ -1079,19 +1146,20 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}
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)
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
}
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()),
@@ -1100,19 +1168,20 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
return
}
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)
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
}
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()),

View File

@@ -1407,6 +1407,38 @@ func TestPublishBlock(t *testing.T) {
server.PublishBlock(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
})
t.Run("Capella block without version header succeeds", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.CapellaForkEpoch = 4
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockCapella
require.NoError(t, json.Unmarshal([]byte(rpctesting.CapellaBlock), &signedblock))
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().CapellaForkEpoch))
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_Capella)
converted, err := structs.BeaconBlockCapellaFromConsensus(block.Capella.Block)
require.NoError(t, err)
var signedblock *structs.SignedBeaconBlockCapella
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.PublishBlock(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
})
t.Run("Deneb", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
@@ -1433,11 +1465,10 @@ func TestPublishBlock(t *testing.T) {
t.Run("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_Fulu)
converted, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu)
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Electra)
converted, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
require.NoError(t, err)
var signedblock *structs.SignedBeaconBlockContentsFulu
var signedblock *structs.SignedBeaconBlockContentsElectra
err = json.Unmarshal([]byte(rpctesting.FuluBlockContents), &signedblock)
require.NoError(t, err)
require.DeepEqual(t, converted, signedblock)
@@ -1454,6 +1485,51 @@ func TestPublishBlock(t *testing.T) {
server.PublishBlock(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
})
t.Run("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.SignedBeaconBlockContentsElectra
require.NoError(t, json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &signedblock))
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().ElectraForkEpoch))
newContents, 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_Electra)
converted, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
require.NoError(t, err)
var signedblock *structs.SignedBeaconBlockContentsElectra
err = json.Unmarshal(newContents, &signedblock)
require.NoError(t, err)
require.DeepEqual(t, converted, signedblock)
return ok
}))
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader(newContents))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.PublishBlock(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
})
t.Run("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.ElectraBlockContents)))
writer := httptest.NewRecorder()
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())
})
t.Run("Fulu", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
@@ -1521,6 +1597,107 @@ func TestPublishBlock(t *testing.T) {
})
}
func TestVersionHeaderFromRequest(t *testing.T) {
t.Run("Fulu block contents returns fulu header", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.FuluForkEpoch = 7
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockContentsFulu
require.NoError(t, json.Unmarshal([]byte(rpctesting.FuluBlockContents), &signedblock))
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().FuluForkEpoch))
newContents, err := json.Marshal(signedblock)
require.NoError(t, err)
versionHead, err := versionHeaderFromRequest(newContents)
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
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockContentsElectra
require.NoError(t, json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &signedblock))
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().ElectraForkEpoch))
newContents, err := json.Marshal(signedblock)
require.NoError(t, err)
versionHead, err := versionHeaderFromRequest(newContents)
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
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockContentsDeneb
require.NoError(t, json.Unmarshal([]byte(rpctesting.DenebBlockContents), &signedblock))
signedblock.SignedBlock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().DenebForkEpoch))
newContents, err := json.Marshal(signedblock)
require.NoError(t, err)
versionHead, err := versionHeaderFromRequest(newContents)
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)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockCapella
require.NoError(t, json.Unmarshal([]byte(rpctesting.CapellaBlock), &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
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockBellatrix
require.NoError(t, json.Unmarshal([]byte(rpctesting.BellatrixBlock), &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
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
var signedblock *structs.SignedBeaconBlockAltair
require.NoError(t, json.Unmarshal([]byte(rpctesting.AltairBlock), &signedblock))
signedblock.Message.Slot = fmt.Sprintf("%d", uint64(params.BeaconConfig().SlotsPerEpoch)*uint64(params.BeaconConfig().AltairForkEpoch))
newBlock, err := json.Marshal(signedblock)
require.NoError(t, err)
versionHead, err := versionHeaderFromRequest(newBlock)
require.NoError(t, err)
require.Equal(t, version.String(version.Altair), versionHead)
})
t.Run("Phase0 block returns capella header", func(t *testing.T) {
var signedblock *structs.SignedBeaconBlock
require.NoError(t, json.Unmarshal([]byte(rpctesting.Phase0Block), &signedblock))
newBlock, err := json.Marshal(signedblock)
require.NoError(t, err)
versionHead, err := versionHeaderFromRequest(newBlock)
require.NoError(t, err)
require.Equal(t, version.String(version.Phase0), versionHead)
})
t.Run("Malformed json returns error unable to peek slot from block contents", func(t *testing.T) {
malformedJSON := []byte(`{"age": 30,}`)
_, err := versionHeaderFromRequest(malformedJSON)
require.ErrorContains(t, "unable to peek slot", err)
})
}
func TestPublishBlockSSZ(t *testing.T) {
ctrl := gomock.NewController(t)
t.Run("Phase 0", func(t *testing.T) {
@@ -2352,12 +2529,11 @@ func TestPublishBlockV2(t *testing.T) {
t.Run("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_Fulu)
converted, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu)
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Electra)
converted, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
require.NoError(t, err)
var signedblock *structs.SignedBeaconBlockContentsFulu
err = json.Unmarshal([]byte(rpctesting.FuluBlockContents), &signedblock)
var signedblock *structs.SignedBeaconBlockContentsElectra
err = json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &signedblock)
require.NoError(t, err)
require.DeepEqual(t, converted, signedblock)
return ok

View File

@@ -0,0 +1,3 @@
### Fixed
- fix block api endpoint to handle blocks with the same structure but on different forks (i.e. fulu and electra)