diff --git a/changelog/james-prysm_post-block-ssz.md b/changelog/james-prysm_post-block-ssz.md new file mode 100644 index 0000000000..8b79e087e2 --- /dev/null +++ b/changelog/james-prysm_post-block-ssz.md @@ -0,0 +1,3 @@ +### Changed + +- Switching default of validator client rest call for submit block from JSON to SSZ. Fallback json will be attempted. \ No newline at end of file diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index 42be838f17..ecfb847ff5 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -91,18 +91,6 @@ go_test( "index_test.go", "prepare_beacon_proposer_test.go", "propose_attestation_test.go", - "propose_beacon_block_altair_test.go", - "propose_beacon_block_bellatrix_test.go", - "propose_beacon_block_blinded_bellatrix_test.go", - "propose_beacon_block_blinded_capella_test.go", - "propose_beacon_block_blinded_deneb_test.go", - "propose_beacon_block_blinded_electra_test.go", - "propose_beacon_block_blinded_fulu_test.go", - "propose_beacon_block_capella_test.go", - "propose_beacon_block_deneb_test.go", - "propose_beacon_block_electra_test.go", - "propose_beacon_block_fulu_test.go", - "propose_beacon_block_phase0_test.go", "propose_beacon_block_test.go", "propose_exit_test.go", "prysm_beacon_chain_client_test.go", diff --git a/validator/client/beacon-api/beacon_api_validator_client_test.go b/validator/client/beacon-api/beacon_api_validator_client_test.go index 77d83fa7ad..d20e98a3cf 100644 --- a/validator/client/beacon-api/beacon_api_validator_client_test.go +++ b/validator/client/beacon-api/beacon_api_validator_client_test.go @@ -1,14 +1,18 @@ package beacon_api import ( + "encoding/json" "errors" "fmt" + "net/http" "testing" "github.com/OffchainLabs/prysm/v6/api/server/structs" + rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v6/network/httputil" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" "github.com/OffchainLabs/prysm/v6/testing/require" @@ -136,15 +140,14 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockValid(t *testing.T) { ctx := t.Context() jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( + jsonRestHandler.EXPECT().PostSSZ( gomock.Any(), "/eth/v2/beacon/blocks", - map[string]string{"Eth-Consensus-Version": "phase0"}, gomock.Any(), - nil, + gomock.Any(), ).Return( - nil, - ).Times(2) + nil, nil, nil, + ).Times(1) validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} expectedResp, expectedErr := validatorClient.proposeBeaconBlock( @@ -153,34 +156,38 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockValid(t *testing.T) { Block: generateSignedPhase0Block(), }, ) - - resp, err := validatorClient.ProposeBeaconBlock( - ctx, - ðpb.GenericSignedBeaconBlock{ - Block: generateSignedPhase0Block(), - }, - ) - - assert.DeepEqual(t, expectedErr, err) - assert.DeepEqual(t, expectedResp, resp) + require.NoError(t, expectedErr) + require.NotNil(t, expectedResp) } -func TestBeaconApiValidatorClient_ProposeBeaconBlockError(t *testing.T) { +func TestBeaconApiValidatorClient_ProposeBeaconBlockError_ThenPass(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() ctx := t.Context() jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blocks", + gomock.Any(), + gomock.Any(), + ).Return( + nil, nil, &httputil.DefaultJsonError{ + Code: http.StatusNotAcceptable, + Message: "SSZ not supported", + }, + ).Times(1) + jsonRestHandler.EXPECT().Post( gomock.Any(), "/eth/v2/beacon/blocks", - map[string]string{"Eth-Consensus-Version": "phase0"}, gomock.Any(), - nil, + gomock.Any(), + gomock.Any(), ).Return( - errors.New("foo error"), - ).Times(2) + nil, + ).Times(1) validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} expectedResp, expectedErr := validatorClient.proposeBeaconBlock( @@ -189,16 +196,351 @@ func TestBeaconApiValidatorClient_ProposeBeaconBlockError(t *testing.T) { Block: generateSignedPhase0Block(), }, ) + require.NoError(t, expectedErr) + require.NotNil(t, expectedResp) +} - resp, err := validatorClient.ProposeBeaconBlock( - ctx, - ðpb.GenericSignedBeaconBlock{ - Block: generateSignedPhase0Block(), +func TestBeaconApiValidatorClient_ProposeBeaconBlockAllTypes(t *testing.T) { + tests := []struct { + name string + block *ethpb.GenericSignedBeaconBlock + expectedPath string + wantErr bool + errorMessage string + }{ + { + name: "Phase0 block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + expectedPath: "/eth/v2/beacon/blocks", }, - ) + { + name: "Altair block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedAltairBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Bellatrix block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBellatrixBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Capella block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedCapellaBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Bellatrix block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedBellatrixBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Blinded Capella block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedCapellaBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Deneb block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedDenebBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Deneb block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedDenebBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Electra block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedElectraBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Electra block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedElectraBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Fulu block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedFuluBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Fulu block", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedFuluBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Unsupported block type", + block: ðpb.GenericSignedBeaconBlock{ + Block: nil, + }, + wantErr: true, + errorMessage: "unsupported block type", + }, + } - assert.ErrorContains(t, expectedErr.Error(), err) - assert.DeepEqual(t, expectedResp, resp) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := t.Context() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + if !tt.wantErr { + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + tt.expectedPath, + gomock.Any(), + gomock.Any(), + ).Return(nil, nil, nil).Times(1) + } + + validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + resp, err := validatorClient.proposeBeaconBlock(ctx, tt.block) + + if tt.wantErr { + require.ErrorContains(t, tt.errorMessage, err) + assert.Equal(t, (*ethpb.ProposeResponse)(nil), resp) + } else { + require.NoError(t, err) + require.NotNil(t, resp) + } + }) + } +} + +func TestBeaconApiValidatorClient_ProposeBeaconBlockHTTPErrors(t *testing.T) { + tests := []struct { + name string + sszError error + expectJSON bool + errorMessage string + }{ + { + name: "HTTP 202 Accepted - block broadcast but failed validation", + sszError: &httputil.DefaultJsonError{ + Code: http.StatusAccepted, + Message: "block broadcast but failed validation", + }, + expectJSON: false, // No fallback for non-406 errors + errorMessage: "failed to submit block ssz", + }, + { + name: "Other HTTP error", + sszError: &httputil.DefaultJsonError{ + Code: http.StatusBadRequest, + Message: "bad request", + }, + expectJSON: false, // No fallback for non-406 errors + errorMessage: "failed to submit block ssz", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := t.Context() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blocks", + gomock.Any(), + gomock.Any(), + ).Return(nil, nil, tt.sszError).Times(1) + + if tt.expectJSON { + // When SSZ fails, it falls back to JSON + jsonRestHandler.EXPECT().Post( + gomock.Any(), + "/eth/v2/beacon/blocks", + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Return(tt.sszError).Times(1) + } + + validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + _, err := validatorClient.proposeBeaconBlock( + ctx, + ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + ) + require.ErrorContains(t, tt.errorMessage, err) + }) + } +} + +func TestBeaconApiValidatorClient_ProposeBeaconBlockJSONFallback(t *testing.T) { + tests := []struct { + name string + block *ethpb.GenericSignedBeaconBlock + expectedPath string + jsonError error + wantErr bool + }{ + { + name: "Phase0 block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Altair block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedAltairBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Bellatrix block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBellatrixBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Capella block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedCapellaBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Bellatrix block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedBellatrixBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Blinded Capella block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedCapellaBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Deneb block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedDenebBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Deneb block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedDenebBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Electra block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedElectraBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Electra block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedElectraBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "Fulu block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedFuluBlock(), + }, + expectedPath: "/eth/v2/beacon/blocks", + }, + { + name: "Blinded Fulu block JSON fallback success", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedFuluBlock(), + }, + expectedPath: "/eth/v2/beacon/blinded_blocks", + }, + { + name: "JSON fallback fails", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + expectedPath: "/eth/v2/beacon/blocks", + jsonError: errors.New("json post failed"), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := t.Context() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + // SSZ call fails with 406 to trigger JSON fallback + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + tt.expectedPath, + gomock.Any(), + gomock.Any(), + ).Return(nil, nil, &httputil.DefaultJsonError{ + Code: http.StatusNotAcceptable, + Message: "SSZ not supported", + }).Times(1) + + // JSON fallback + jsonRestHandler.EXPECT().Post( + gomock.Any(), + tt.expectedPath, + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Return(tt.jsonError).Times(1) + + validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + resp, err := validatorClient.proposeBeaconBlock(ctx, tt.block) + + if tt.wantErr { + assert.NotNil(t, err) + assert.Equal(t, (*ethpb.ProposeResponse)(nil), resp) + } else { + require.NoError(t, err) + require.NotNil(t, resp) + } + }) + } } func TestBeaconApiValidatorClient_Host(t *testing.T) { @@ -229,3 +571,88 @@ func TestBeaconApiValidatorClient_Host(t *testing.T) { host = validatorClient.Host() require.Equal(t, hosts[1], host) } + +// Helper functions for generating test blocks for newer consensus versions +func generateSignedDenebBlock() *ethpb.GenericSignedBeaconBlock_Deneb { + var blockContents structs.SignedBeaconBlockContentsDeneb + if err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blockContents); err != nil { + panic(err) + } + genericBlock, err := blockContents.ToGeneric() + if err != nil { + panic(err) + } + return ðpb.GenericSignedBeaconBlock_Deneb{ + Deneb: genericBlock.GetDeneb(), + } +} + +func generateSignedBlindedDenebBlock() *ethpb.GenericSignedBeaconBlock_BlindedDeneb { + var blindedBlock structs.SignedBlindedBeaconBlockDeneb + if err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &blindedBlock); err != nil { + panic(err) + } + genericBlock, err := blindedBlock.ToGeneric() + if err != nil { + panic(err) + } + return ðpb.GenericSignedBeaconBlock_BlindedDeneb{ + BlindedDeneb: genericBlock.GetBlindedDeneb(), + } +} + +func generateSignedElectraBlock() *ethpb.GenericSignedBeaconBlock_Electra { + var blockContents structs.SignedBeaconBlockContentsElectra + if err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blockContents); err != nil { + panic(err) + } + genericBlock, err := blockContents.ToGeneric() + if err != nil { + panic(err) + } + return ðpb.GenericSignedBeaconBlock_Electra{ + Electra: genericBlock.GetElectra(), + } +} + +func generateSignedBlindedElectraBlock() *ethpb.GenericSignedBeaconBlock_BlindedElectra { + var blindedBlock structs.SignedBlindedBeaconBlockElectra + if err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &blindedBlock); err != nil { + panic(err) + } + genericBlock, err := blindedBlock.ToGeneric() + if err != nil { + panic(err) + } + return ðpb.GenericSignedBeaconBlock_BlindedElectra{ + BlindedElectra: genericBlock.GetBlindedElectra(), + } +} + +func generateSignedFuluBlock() *ethpb.GenericSignedBeaconBlock_Fulu { + var blockContents structs.SignedBeaconBlockContentsFulu + if err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blockContents); err != nil { + panic(err) + } + genericBlock, err := blockContents.ToGeneric() + if err != nil { + panic(err) + } + return ðpb.GenericSignedBeaconBlock_Fulu{ + Fulu: genericBlock.GetFulu(), + } +} + +func generateSignedBlindedFuluBlock() *ethpb.GenericSignedBeaconBlock_BlindedFulu { + var blindedBlock structs.SignedBlindedBeaconBlockFulu + if err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &blindedBlock); err != nil { + panic(err) + } + genericBlock, err := blindedBlock.ToGeneric() + if err != nil { + panic(err) + } + return ðpb.GenericSignedBeaconBlock_BlindedFulu{ + BlindedFulu: genericBlock.GetBlindedFulu(), + } +} diff --git a/validator/client/beacon-api/mock/json_rest_handler_mock.go b/validator/client/beacon-api/mock/json_rest_handler_mock.go index b414b93f28..a604da109d 100644 --- a/validator/client/beacon-api/mock/json_rest_handler_mock.go +++ b/validator/client/beacon-api/mock/json_rest_handler_mock.go @@ -114,6 +114,22 @@ func (mr *MockJsonRestHandlerMockRecorder) Post(ctx, endpoint, headers, data, re return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockJsonRestHandler)(nil).Post), ctx, endpoint, headers, data, resp) } +// Post mocks base method. +func (m *MockJsonRestHandler) PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PostSSZ", ctx, endpoint, headers, data) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(http.Header) + ret2, _ := ret[2].(error) + return ret0,ret1,ret2 +} + +// Post indicates an expected call of Post. +func (mr *MockJsonRestHandlerMockRecorder) PostSSZ(ctx, endpoint, headers, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostSSZ", reflect.TypeOf((*MockJsonRestHandler)(nil).PostSSZ), ctx, endpoint, headers, data) +} + // SetHost mocks base method. func (m *MockJsonRestHandler) SetHost(host string) { m.ctrl.T.Helper() diff --git a/validator/client/beacon-api/propose_beacon_block.go b/validator/client/beacon-api/propose_beacon_block.go index ba7ef871ce..7f2ed7f835 100644 --- a/validator/client/beacon-api/propose_beacon_block.go +++ b/validator/client/beacon-api/propose_beacon_block.go @@ -15,8 +15,10 @@ import ( type blockProcessingResult struct { consensusVersion string beaconBlockRoot [32]byte - marshalledJSON []byte + marshalledSSZ []byte blinded bool + // Function to marshal JSON on demand + marshalJSON func() ([]byte, error) } func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) { @@ -62,17 +64,54 @@ func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *e } headers := map[string]string{"Eth-Consensus-Version": res.consensusVersion} - err = c.jsonRestHandler.Post(ctx, endpoint, headers, bytes.NewBuffer(res.marshalledJSON), nil) - errJson := &httputil.DefaultJsonError{} - if err != nil { - if !errors.As(err, &errJson) { - return nil, err + + // Try PostSSZ first with SSZ data + if res.marshalledSSZ != nil { + _, _, err = c.jsonRestHandler.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.jsonRestHandler.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") + } } - // 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") + } 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.jsonRestHandler.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 nil, errJson } return ðpb.ProposeResponse{BlockRoot: res.beaconBlockRoot[:]}, nil @@ -89,11 +128,19 @@ func handlePhase0Block(block *ethpb.GenericSignedBeaconBlock_Phase0) (*blockProc } res.beaconBlockRoot = beaconBlockRoot - signedBlock := structs.SignedBeaconBlockPhase0FromConsensus(block.Phase0) - res.marshalledJSON, err = json.Marshal(signedBlock) + // Marshal SSZ + ssz, err := block.Phase0.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall phase0 beacon block to json") + return nil, errors.Wrap(err, "failed to serialize block for phase0 beacon block") } + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock := structs.SignedBeaconBlockPhase0FromConsensus(block.Phase0) + return json.Marshal(signedBlock) + } + return &res, nil } @@ -108,11 +155,19 @@ func handleAltairBlock(block *ethpb.GenericSignedBeaconBlock_Altair) (*blockProc } res.beaconBlockRoot = beaconBlockRoot - signedBlock := structs.SignedBeaconBlockAltairFromConsensus(block.Altair) - res.marshalledJSON, err = json.Marshal(signedBlock) + // Marshal SSZ + ssz, err := block.Altair.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall altair beacon block to json") + return nil, errors.Wrap(err, "failed to serialize block for altair beacon block") } + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock := structs.SignedBeaconBlockAltairFromConsensus(block.Altair) + return json.Marshal(signedBlock) + } + return &res, nil } @@ -127,13 +182,20 @@ func handleBellatrixBlock(block *ethpb.GenericSignedBeaconBlock_Bellatrix) (*blo } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(block.Bellatrix) + // Marshal SSZ + ssz, err := block.Bellatrix.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall bellatrix beacon block") + return nil, errors.Wrap(err, "failed to serialize block for bellatrix beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall bellatrix beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(block.Bellatrix) + if err != nil { + return nil, errors.Wrap(err, "failed to convert bellatrix beacon block") + } + return json.Marshal(signedBlock) } return &res, nil @@ -150,13 +212,20 @@ func handleBlindedBellatrixBlock(block *ethpb.GenericSignedBeaconBlock_BlindedBe } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(block.BlindedBellatrix) + // Marshal SSZ + ssz, err := block.BlindedBellatrix.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall blinded bellatrix beacon block") + return nil, errors.Wrap(err, "failed to serialize block for bellatrix beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall blinded bellatrix beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(block.BlindedBellatrix) + if err != nil { + return nil, errors.Wrap(err, "failed to convert blinded bellatrix beacon block") + } + return json.Marshal(signedBlock) } return &res, nil @@ -173,13 +242,20 @@ func handleCapellaBlock(block *ethpb.GenericSignedBeaconBlock_Capella) (*blockPr } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(block.Capella) + // Marshal SSZ + ssz, err := block.Capella.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall capella beacon block") + return nil, errors.Wrap(err, "failed to serialize capella beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall capella beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(block.Capella) + if err != nil { + return nil, errors.Wrap(err, "failed to convert capella beacon block") + } + return json.Marshal(signedBlock) } return &res, nil @@ -196,13 +272,20 @@ func handleBlindedCapellaBlock(block *ethpb.GenericSignedBeaconBlock_BlindedCape } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(block.BlindedCapella) + // Marshal SSZ + ssz, err := block.BlindedCapella.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall blinded capella beacon block") + return nil, errors.Wrap(err, "failed to serialize blinded capella beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall blinded capella beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(block.BlindedCapella) + if err != nil { + return nil, errors.Wrap(err, "failed to convert blinded capella beacon block") + } + return json.Marshal(signedBlock) } return &res, nil @@ -219,13 +302,20 @@ func handleDenebBlockContents(block *ethpb.GenericSignedBeaconBlock_Deneb) (*blo } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBeaconBlockContentsDenebFromConsensus(block.Deneb) + // Marshal SSZ + ssz, err := block.Deneb.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall deneb beacon block contents") + return nil, errors.Wrap(err, "failed to serialize deneb beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall deneb beacon block contents to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBeaconBlockContentsDenebFromConsensus(block.Deneb) + if err != nil { + return nil, errors.Wrap(err, "failed to convert deneb beacon block contents") + } + return json.Marshal(signedBlock) } return &res, nil @@ -242,13 +332,20 @@ func handleBlindedDenebBlock(block *ethpb.GenericSignedBeaconBlock_BlindedDeneb) } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb) + // Marshal SSZ + ssz, err := block.BlindedDeneb.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall deneb blinded beacon block ") + return nil, errors.Wrap(err, "failed to serialize blinded deneb beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall deneb blinded beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBlindedBeaconBlockDenebFromConsensus(block.BlindedDeneb) + if err != nil { + return nil, errors.Wrap(err, "failed to convert deneb blinded beacon block") + } + return json.Marshal(signedBlock) } return &res, nil @@ -265,13 +362,20 @@ func handleElectraBlockContents(block *ethpb.GenericSignedBeaconBlock_Electra) ( } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra) + // Marshal SSZ + ssz, err := block.Electra.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall electra beacon block contents") + return nil, errors.Wrap(err, "failed to serialize electra beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall electra beacon block contents to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra) + if err != nil { + return nil, errors.Wrap(err, "failed to convert electra beacon block contents") + } + return json.Marshal(signedBlock) } return &res, nil @@ -288,13 +392,20 @@ func handleBlindedElectraBlock(block *ethpb.GenericSignedBeaconBlock_BlindedElec } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBlindedBeaconBlockElectraFromConsensus(block.BlindedElectra) + // Marshal SSZ + ssz, err := block.BlindedElectra.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall electra blinded beacon block") + return nil, errors.Wrap(err, "failed to serialize blinded electra beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall electra blinded beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBlindedBeaconBlockElectraFromConsensus(block.BlindedElectra) + if err != nil { + return nil, errors.Wrap(err, "failed to convert electra blinded beacon block") + } + return json.Marshal(signedBlock) } return &res, nil @@ -311,13 +422,20 @@ func handleFuluBlockContents(block *ethpb.GenericSignedBeaconBlock_Fulu) (*block } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu) + // Marshal SSZ + ssz, err := block.Fulu.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall fulu beacon block contents") + return nil, errors.Wrap(err, "failed to serialize fulu beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall fulu beacon block contents to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBeaconBlockContentsFuluFromConsensus(block.Fulu) + if err != nil { + return nil, errors.Wrap(err, "failed to convert fulu beacon block contents") + } + return json.Marshal(signedBlock) } return &res, nil @@ -334,13 +452,20 @@ func handleBlindedFuluBlock(block *ethpb.GenericSignedBeaconBlock_BlindedFulu) ( } res.beaconBlockRoot = beaconBlockRoot - signedBlock, err := structs.SignedBlindedBeaconBlockFuluFromConsensus(block.BlindedFulu) + // Marshal SSZ + ssz, err := block.BlindedFulu.MarshalSSZ() if err != nil { - return nil, errors.Wrap(err, "failed to marshall fulu blinded beacon block") + return nil, errors.Wrap(err, "failed to serialize blinded fulu beacon block") } - res.marshalledJSON, err = json.Marshal(signedBlock) - if err != nil { - return nil, errors.Wrap(err, "failed to marshall fulu blinded beacon block to json") + res.marshalledSSZ = ssz + + // Set up JSON marshalling function for fallback + res.marshalJSON = func() ([]byte, error) { + signedBlock, err := structs.SignedBlindedBeaconBlockFuluFromConsensus(block.BlindedFulu) + if err != nil { + return nil, errors.Wrap(err, "failed to convert fulu blinded beacon block") + } + return json.Marshal(signedBlock) } return &res, nil diff --git a/validator/client/beacon-api/propose_beacon_block_altair_test.go b/validator/client/beacon-api/propose_beacon_block_altair_test.go deleted file mode 100644 index fe7b354525..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_altair_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Altair(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - altairBlock := generateSignedAltairBlock() - - genericSignedBlock := ðpb.GenericSignedBeaconBlock{} - genericSignedBlock.Block = altairBlock - - jsonAltairBlock := structs.SignedBeaconBlockAltairFromConsensus(altairBlock.Altair) - - marshalledBlock, err := json.Marshal(jsonAltairBlock) - require.NoError(t, err) - - ctx := t.Context() - - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "altair"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(marshalledBlock), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := altairBlock.Altair.Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} - -func generateSignedAltairBlock() *ethpb.GenericSignedBeaconBlock_Altair { - return ðpb.GenericSignedBeaconBlock_Altair{ - Altair: ðpb.SignedBeaconBlockAltair{ - Block: testhelpers.GenerateProtoAltairBeaconBlock(), - Signature: testhelpers.FillByteSlice(96, 112), - }, - } -} diff --git a/validator/client/beacon-api/propose_beacon_block_bellatrix_test.go b/validator/client/beacon-api/propose_beacon_block_bellatrix_test.go deleted file mode 100644 index bdef5767cd..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_bellatrix_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Bellatrix(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - bellatrixBlock := generateSignedBellatrixBlock() - - genericSignedBlock := ðpb.GenericSignedBeaconBlock{} - genericSignedBlock.Block = bellatrixBlock - - jsonBellatrixBlock, err := structs.SignedBeaconBlockBellatrixFromConsensus(bellatrixBlock.Bellatrix) - require.NoError(t, err) - - marshalledBlock, err := json.Marshal(jsonBellatrixBlock) - require.NoError(t, err) - - ctx := t.Context() - - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "bellatrix"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(marshalledBlock), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := bellatrixBlock.Bellatrix.Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} - -func generateSignedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_Bellatrix { - return ðpb.GenericSignedBeaconBlock_Bellatrix{ - Bellatrix: ðpb.SignedBeaconBlockBellatrix{ - Block: testhelpers.GenerateProtoBellatrixBeaconBlock(), - Signature: testhelpers.FillByteSlice(96, 127), - }, - } -} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_bellatrix_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_bellatrix_test.go deleted file mode 100644 index 40a7d24bf4..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_blinded_bellatrix_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_BlindedBellatrix(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - blindedBellatrixBlock := generateSignedBlindedBellatrixBlock() - - genericSignedBlock := ðpb.GenericSignedBeaconBlock{} - genericSignedBlock.Block = blindedBellatrixBlock - - jsonBlindedBellatrixBlock, err := structs.SignedBlindedBeaconBlockBellatrixFromConsensus(blindedBellatrixBlock.BlindedBellatrix) - require.NoError(t, err) - - marshalledBlock, err := json.Marshal(jsonBlindedBellatrixBlock) - require.NoError(t, err) - - ctx := t.Context() - - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "bellatrix"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blinded_blocks", - headers, - bytes.NewBuffer(marshalledBlock), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := blindedBellatrixBlock.BlindedBellatrix.Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} - -func generateSignedBlindedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_BlindedBellatrix { - return ðpb.GenericSignedBeaconBlock_BlindedBellatrix{ - BlindedBellatrix: ðpb.SignedBlindedBeaconBlockBellatrix{ - Block: ðpb.BlindedBeaconBlockBellatrix{ - Slot: 1, - ProposerIndex: 2, - ParentRoot: testhelpers.FillByteSlice(32, 3), - StateRoot: testhelpers.FillByteSlice(32, 4), - Body: ðpb.BlindedBeaconBlockBodyBellatrix{ - RandaoReveal: testhelpers.FillByteSlice(96, 5), - Eth1Data: ðpb.Eth1Data{ - DepositRoot: testhelpers.FillByteSlice(32, 6), - DepositCount: 7, - BlockHash: testhelpers.FillByteSlice(32, 8), - }, - Graffiti: testhelpers.FillByteSlice(32, 9), - ProposerSlashings: []*ethpb.ProposerSlashing{ - { - Header_1: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 11, - ParentRoot: testhelpers.FillByteSlice(32, 12), - StateRoot: testhelpers.FillByteSlice(32, 13), - BodyRoot: testhelpers.FillByteSlice(32, 14), - }, - Signature: testhelpers.FillByteSlice(96, 15), - }, - Header_2: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 16, - ProposerIndex: 17, - ParentRoot: testhelpers.FillByteSlice(32, 18), - StateRoot: testhelpers.FillByteSlice(32, 19), - BodyRoot: testhelpers.FillByteSlice(32, 20), - }, - Signature: testhelpers.FillByteSlice(96, 21), - }, - }, - { - Header_1: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 22, - ProposerIndex: 23, - ParentRoot: testhelpers.FillByteSlice(32, 24), - StateRoot: testhelpers.FillByteSlice(32, 25), - BodyRoot: testhelpers.FillByteSlice(32, 26), - }, - Signature: testhelpers.FillByteSlice(96, 27), - }, - Header_2: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 28, - ProposerIndex: 29, - ParentRoot: testhelpers.FillByteSlice(32, 30), - StateRoot: testhelpers.FillByteSlice(32, 31), - BodyRoot: testhelpers.FillByteSlice(32, 32), - }, - Signature: testhelpers.FillByteSlice(96, 33), - }, - }, - }, - AttesterSlashings: []*ethpb.AttesterSlashing{ - { - Attestation_1: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{34, 35}, - Data: ðpb.AttestationData{ - Slot: 36, - CommitteeIndex: 37, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 39, - Root: testhelpers.FillByteSlice(32, 40), - }, - Target: ðpb.Checkpoint{ - Epoch: 41, - Root: testhelpers.FillByteSlice(32, 42), - }, - }, - Signature: testhelpers.FillByteSlice(96, 43), - }, - Attestation_2: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{44, 45}, - Data: ðpb.AttestationData{ - Slot: 46, - CommitteeIndex: 47, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 49, - Root: testhelpers.FillByteSlice(32, 50), - }, - Target: ðpb.Checkpoint{ - Epoch: 51, - Root: testhelpers.FillByteSlice(32, 52), - }, - }, - Signature: testhelpers.FillByteSlice(96, 53), - }, - }, - { - Attestation_1: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{54, 55}, - Data: ðpb.AttestationData{ - Slot: 56, - CommitteeIndex: 57, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 59, - Root: testhelpers.FillByteSlice(32, 60), - }, - Target: ðpb.Checkpoint{ - Epoch: 61, - Root: testhelpers.FillByteSlice(32, 62), - }, - }, - Signature: testhelpers.FillByteSlice(96, 63), - }, - Attestation_2: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{64, 65}, - Data: ðpb.AttestationData{ - Slot: 66, - CommitteeIndex: 67, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 69, - Root: testhelpers.FillByteSlice(32, 70), - }, - Target: ðpb.Checkpoint{ - Epoch: 71, - Root: testhelpers.FillByteSlice(32, 72), - }, - }, - Signature: testhelpers.FillByteSlice(96, 73), - }, - }, - }, - Attestations: []*ethpb.Attestation{ - { - AggregationBits: testhelpers.FillByteSlice(4, 74), - Data: ðpb.AttestationData{ - Slot: 75, - CommitteeIndex: 76, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 78, - Root: testhelpers.FillByteSlice(32, 79), - }, - Target: ðpb.Checkpoint{ - Epoch: 80, - Root: testhelpers.FillByteSlice(32, 81), - }, - }, - Signature: testhelpers.FillByteSlice(96, 82), - }, - { - AggregationBits: testhelpers.FillByteSlice(4, 83), - Data: ðpb.AttestationData{ - Slot: 84, - CommitteeIndex: 85, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 87, - Root: testhelpers.FillByteSlice(32, 88), - }, - Target: ðpb.Checkpoint{ - Epoch: 89, - Root: testhelpers.FillByteSlice(32, 90), - }, - }, - Signature: testhelpers.FillByteSlice(96, 91), - }, - }, - Deposits: []*ethpb.Deposit{ - { - Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 92)), - Data: ðpb.Deposit_Data{ - PublicKey: testhelpers.FillByteSlice(48, 94), - WithdrawalCredentials: testhelpers.FillByteSlice(32, 95), - Amount: 96, - Signature: testhelpers.FillByteSlice(96, 97), - }, - }, - { - Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 98)), - Data: ðpb.Deposit_Data{ - PublicKey: testhelpers.FillByteSlice(48, 100), - WithdrawalCredentials: testhelpers.FillByteSlice(32, 101), - Amount: 102, - Signature: testhelpers.FillByteSlice(96, 103), - }, - }, - }, - VoluntaryExits: []*ethpb.SignedVoluntaryExit{ - { - Exit: ðpb.VoluntaryExit{ - Epoch: 104, - ValidatorIndex: 105, - }, - Signature: testhelpers.FillByteSlice(96, 106), - }, - { - Exit: ðpb.VoluntaryExit{ - Epoch: 107, - ValidatorIndex: 108, - }, - Signature: testhelpers.FillByteSlice(96, 109), - }, - }, - SyncAggregate: ðpb.SyncAggregate{ - SyncCommitteeBits: testhelpers.FillByteSlice(64, 110), - SyncCommitteeSignature: testhelpers.FillByteSlice(96, 111), - }, - ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeader{ - ParentHash: testhelpers.FillByteSlice(32, 112), - FeeRecipient: testhelpers.FillByteSlice(20, 113), - StateRoot: testhelpers.FillByteSlice(32, 114), - ReceiptsRoot: testhelpers.FillByteSlice(32, 115), - LogsBloom: testhelpers.FillByteSlice(256, 116), - PrevRandao: testhelpers.FillByteSlice(32, 117), - BlockNumber: 118, - GasLimit: 119, - GasUsed: 120, - Timestamp: 121, - ExtraData: testhelpers.FillByteSlice(32, 122), - BaseFeePerGas: testhelpers.FillByteSlice(32, 123), - BlockHash: testhelpers.FillByteSlice(32, 124), - TransactionsRoot: testhelpers.FillByteSlice(32, 125), - }, - }, - }, - Signature: testhelpers.FillByteSlice(96, 126), - }, - } -} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_capella_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_capella_test.go deleted file mode 100644 index f092f96784..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_blinded_capella_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_BlindedCapella(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - blindedCapellaBlock := generateSignedBlindedCapellaBlock() - - genericSignedBlock := ðpb.GenericSignedBeaconBlock{} - genericSignedBlock.Block = blindedCapellaBlock - - jsonBlindedCapellaBlock, err := structs.SignedBlindedBeaconBlockCapellaFromConsensus(blindedCapellaBlock.BlindedCapella) - require.NoError(t, err) - - marshalledBlock, err := json.Marshal(jsonBlindedCapellaBlock) - require.NoError(t, err) - - ctx := t.Context() - - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "capella"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blinded_blocks", - headers, - bytes.NewBuffer(marshalledBlock), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := blindedCapellaBlock.BlindedCapella.Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} - -func generateSignedBlindedCapellaBlock() *ethpb.GenericSignedBeaconBlock_BlindedCapella { - return ðpb.GenericSignedBeaconBlock_BlindedCapella{ - BlindedCapella: ðpb.SignedBlindedBeaconBlockCapella{ - Block: ðpb.BlindedBeaconBlockCapella{ - Slot: 1, - ProposerIndex: 2, - ParentRoot: testhelpers.FillByteSlice(32, 3), - StateRoot: testhelpers.FillByteSlice(32, 4), - Body: ðpb.BlindedBeaconBlockBodyCapella{ - RandaoReveal: testhelpers.FillByteSlice(96, 5), - Eth1Data: ðpb.Eth1Data{ - DepositRoot: testhelpers.FillByteSlice(32, 6), - DepositCount: 7, - BlockHash: testhelpers.FillByteSlice(32, 8), - }, - Graffiti: testhelpers.FillByteSlice(32, 9), - ProposerSlashings: []*ethpb.ProposerSlashing{ - { - Header_1: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 11, - ParentRoot: testhelpers.FillByteSlice(32, 12), - StateRoot: testhelpers.FillByteSlice(32, 13), - BodyRoot: testhelpers.FillByteSlice(32, 14), - }, - Signature: testhelpers.FillByteSlice(96, 15), - }, - Header_2: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 16, - ProposerIndex: 17, - ParentRoot: testhelpers.FillByteSlice(32, 18), - StateRoot: testhelpers.FillByteSlice(32, 19), - BodyRoot: testhelpers.FillByteSlice(32, 20), - }, - Signature: testhelpers.FillByteSlice(96, 21), - }, - }, - { - Header_1: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 22, - ProposerIndex: 23, - ParentRoot: testhelpers.FillByteSlice(32, 24), - StateRoot: testhelpers.FillByteSlice(32, 25), - BodyRoot: testhelpers.FillByteSlice(32, 26), - }, - Signature: testhelpers.FillByteSlice(96, 27), - }, - Header_2: ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: 28, - ProposerIndex: 29, - ParentRoot: testhelpers.FillByteSlice(32, 30), - StateRoot: testhelpers.FillByteSlice(32, 31), - BodyRoot: testhelpers.FillByteSlice(32, 32), - }, - Signature: testhelpers.FillByteSlice(96, 33), - }, - }, - }, - AttesterSlashings: []*ethpb.AttesterSlashing{ - { - Attestation_1: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{34, 35}, - Data: ðpb.AttestationData{ - Slot: 36, - CommitteeIndex: 37, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 39, - Root: testhelpers.FillByteSlice(32, 40), - }, - Target: ðpb.Checkpoint{ - Epoch: 41, - Root: testhelpers.FillByteSlice(32, 42), - }, - }, - Signature: testhelpers.FillByteSlice(96, 43), - }, - Attestation_2: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{44, 45}, - Data: ðpb.AttestationData{ - Slot: 46, - CommitteeIndex: 47, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 49, - Root: testhelpers.FillByteSlice(32, 50), - }, - Target: ðpb.Checkpoint{ - Epoch: 51, - Root: testhelpers.FillByteSlice(32, 52), - }, - }, - Signature: testhelpers.FillByteSlice(96, 53), - }, - }, - { - Attestation_1: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{54, 55}, - Data: ðpb.AttestationData{ - Slot: 56, - CommitteeIndex: 57, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 59, - Root: testhelpers.FillByteSlice(32, 60), - }, - Target: ðpb.Checkpoint{ - Epoch: 61, - Root: testhelpers.FillByteSlice(32, 62), - }, - }, - Signature: testhelpers.FillByteSlice(96, 63), - }, - Attestation_2: ðpb.IndexedAttestation{ - AttestingIndices: []uint64{64, 65}, - Data: ðpb.AttestationData{ - Slot: 66, - CommitteeIndex: 67, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 69, - Root: testhelpers.FillByteSlice(32, 70), - }, - Target: ðpb.Checkpoint{ - Epoch: 71, - Root: testhelpers.FillByteSlice(32, 72), - }, - }, - Signature: testhelpers.FillByteSlice(96, 73), - }, - }, - }, - Attestations: []*ethpb.Attestation{ - { - AggregationBits: testhelpers.FillByteSlice(4, 74), - Data: ðpb.AttestationData{ - Slot: 75, - CommitteeIndex: 76, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 78, - Root: testhelpers.FillByteSlice(32, 79), - }, - Target: ðpb.Checkpoint{ - Epoch: 80, - Root: testhelpers.FillByteSlice(32, 81), - }, - }, - Signature: testhelpers.FillByteSlice(96, 82), - }, - { - AggregationBits: testhelpers.FillByteSlice(4, 83), - Data: ðpb.AttestationData{ - Slot: 84, - CommitteeIndex: 85, - BeaconBlockRoot: testhelpers.FillByteSlice(32, 38), - Source: ðpb.Checkpoint{ - Epoch: 87, - Root: testhelpers.FillByteSlice(32, 88), - }, - Target: ðpb.Checkpoint{ - Epoch: 89, - Root: testhelpers.FillByteSlice(32, 90), - }, - }, - Signature: testhelpers.FillByteSlice(96, 91), - }, - }, - Deposits: []*ethpb.Deposit{ - { - Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 92)), - Data: ðpb.Deposit_Data{ - PublicKey: testhelpers.FillByteSlice(48, 94), - WithdrawalCredentials: testhelpers.FillByteSlice(32, 95), - Amount: 96, - Signature: testhelpers.FillByteSlice(96, 97), - }, - }, - { - Proof: testhelpers.FillByteArraySlice(33, testhelpers.FillByteSlice(32, 98)), - Data: ðpb.Deposit_Data{ - PublicKey: testhelpers.FillByteSlice(48, 100), - WithdrawalCredentials: testhelpers.FillByteSlice(32, 101), - Amount: 102, - Signature: testhelpers.FillByteSlice(96, 103), - }, - }, - }, - VoluntaryExits: []*ethpb.SignedVoluntaryExit{ - { - Exit: ðpb.VoluntaryExit{ - Epoch: 104, - ValidatorIndex: 105, - }, - Signature: testhelpers.FillByteSlice(96, 106), - }, - { - Exit: ðpb.VoluntaryExit{ - Epoch: 107, - ValidatorIndex: 108, - }, - Signature: testhelpers.FillByteSlice(96, 109), - }, - }, - SyncAggregate: ðpb.SyncAggregate{ - SyncCommitteeBits: testhelpers.FillByteSlice(64, 110), - SyncCommitteeSignature: testhelpers.FillByteSlice(96, 111), - }, - ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderCapella{ - ParentHash: testhelpers.FillByteSlice(32, 112), - FeeRecipient: testhelpers.FillByteSlice(20, 113), - StateRoot: testhelpers.FillByteSlice(32, 114), - ReceiptsRoot: testhelpers.FillByteSlice(32, 115), - LogsBloom: testhelpers.FillByteSlice(256, 116), - PrevRandao: testhelpers.FillByteSlice(32, 117), - BlockNumber: 118, - GasLimit: 119, - GasUsed: 120, - Timestamp: 121, - ExtraData: testhelpers.FillByteSlice(32, 122), - BaseFeePerGas: testhelpers.FillByteSlice(32, 123), - BlockHash: testhelpers.FillByteSlice(32, 124), - TransactionsRoot: testhelpers.FillByteSlice(32, 125), - WithdrawalsRoot: testhelpers.FillByteSlice(32, 126), - }, - BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{ - { - Message: ðpb.BLSToExecutionChange{ - ValidatorIndex: 127, - FromBlsPubkey: testhelpers.FillByteSlice(48, 128), - ToExecutionAddress: testhelpers.FillByteSlice(20, 129), - }, - Signature: testhelpers.FillByteSlice(96, 130), - }, - { - Message: ðpb.BLSToExecutionChange{ - ValidatorIndex: 131, - FromBlsPubkey: testhelpers.FillByteSlice(48, 132), - ToExecutionAddress: testhelpers.FillByteSlice(20, 133), - }, - Signature: testhelpers.FillByteSlice(96, 134), - }, - }, - }, - }, - Signature: testhelpers.FillByteSlice(96, 135), - }, - } -} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_deneb_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_deneb_test.go deleted file mode 100644 index 8bb44a0bec..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_blinded_deneb_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_BlindedDeneb(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - var block structs.SignedBlindedBeaconBlockDeneb - err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &block) - require.NoError(t, err) - genericSignedBlock, err := block.ToGeneric() - require.NoError(t, err) - - denebBytes, err := json.Marshal(block) - require.NoError(t, err) - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "deneb"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blinded_blocks", - headers, - bytes.NewBuffer(denebBytes), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := genericSignedBlock.GetBlindedDeneb().HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_electra_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_electra_test.go deleted file mode 100644 index a77abac346..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_blinded_electra_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_BlindedElectra(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - var block structs.SignedBlindedBeaconBlockElectra - err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block) - require.NoError(t, err) - genericSignedBlock, err := block.ToGeneric() - require.NoError(t, err) - - electraBytes, err := json.Marshal(block) - require.NoError(t, err) - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "electra"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blinded_blocks", - headers, - bytes.NewBuffer(electraBytes), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := genericSignedBlock.GetBlindedElectra().HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_fulu_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_fulu_test.go deleted file mode 100644 index 18fad855a7..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_blinded_fulu_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_BlindedFulu(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - var block structs.SignedBlindedBeaconBlockFulu - err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &block) - require.NoError(t, err) - genericSignedBlock, err := block.ToGeneric() - require.NoError(t, err) - - fuluBytes, err := json.Marshal(block) - require.NoError(t, err) - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "fulu"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blinded_blocks", - headers, - bytes.NewBuffer(fuluBytes), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := genericSignedBlock.GetBlindedFulu().HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} diff --git a/validator/client/beacon-api/propose_beacon_block_capella_test.go b/validator/client/beacon-api/propose_beacon_block_capella_test.go deleted file mode 100644 index 076055219d..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_capella_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Capella(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - capellaBlock := generateSignedCapellaBlock() - - genericSignedBlock := ðpb.GenericSignedBeaconBlock{} - genericSignedBlock.Block = capellaBlock - - jsonCapellaBlock, err := structs.SignedBeaconBlockCapellaFromConsensus(capellaBlock.Capella) - require.NoError(t, err) - - marshalledBlock, err := json.Marshal(jsonCapellaBlock) - require.NoError(t, err) - - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "capella"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(marshalledBlock), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := capellaBlock.Capella.Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} - -func generateSignedCapellaBlock() *ethpb.GenericSignedBeaconBlock_Capella { - return ðpb.GenericSignedBeaconBlock_Capella{ - Capella: ðpb.SignedBeaconBlockCapella{ - Block: testhelpers.GenerateProtoCapellaBeaconBlock(), - Signature: testhelpers.FillByteSlice(96, 127), - }, - } -} diff --git a/validator/client/beacon-api/propose_beacon_block_deneb_test.go b/validator/client/beacon-api/propose_beacon_block_deneb_test.go deleted file mode 100644 index b8267b52db..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_deneb_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Deneb(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - var blockContents structs.SignedBeaconBlockContentsDeneb - err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blockContents) - require.NoError(t, err) - genericSignedBlock, err := blockContents.ToGeneric() - require.NoError(t, err) - - denebBytes, err := json.Marshal(blockContents) - require.NoError(t, err) - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "deneb"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(denebBytes), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := genericSignedBlock.GetDeneb().Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} diff --git a/validator/client/beacon-api/propose_beacon_block_electra_test.go b/validator/client/beacon-api/propose_beacon_block_electra_test.go deleted file mode 100644 index ccd3ff5884..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_electra_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Electra(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - var blockContents structs.SignedBeaconBlockContentsElectra - err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blockContents) - require.NoError(t, err) - genericSignedBlock, err := blockContents.ToGeneric() - require.NoError(t, err) - - electraBytes, err := json.Marshal(blockContents) - require.NoError(t, err) - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "electra"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(electraBytes), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := genericSignedBlock.GetElectra().Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} diff --git a/validator/client/beacon-api/propose_beacon_block_fulu_test.go b/validator/client/beacon-api/propose_beacon_block_fulu_test.go deleted file mode 100644 index 49cbf55a8e..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_fulu_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Fulu(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - var blockContents structs.SignedBeaconBlockContentsFulu - err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blockContents) - require.NoError(t, err) - genericSignedBlock, err := blockContents.ToGeneric() - require.NoError(t, err) - - fuluBytes, err := json.Marshal(blockContents) - require.NoError(t, err) - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "fulu"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(fuluBytes), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := genericSignedBlock.GetFulu().Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} diff --git a/validator/client/beacon-api/propose_beacon_block_phase0_test.go b/validator/client/beacon-api/propose_beacon_block_phase0_test.go deleted file mode 100644 index 187a06f336..0000000000 --- a/validator/client/beacon-api/propose_beacon_block_phase0_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package beacon_api - -import ( - "bytes" - "encoding/json" - "testing" - - "github.com/OffchainLabs/prysm/v6/api/server/structs" - ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" - "github.com/OffchainLabs/prysm/v6/testing/assert" - "github.com/OffchainLabs/prysm/v6/testing/require" - "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" - testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" - "go.uber.org/mock/gomock" -) - -func TestProposeBeaconBlock_Phase0(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - phase0Block := generateSignedPhase0Block() - - genericSignedBlock := ðpb.GenericSignedBeaconBlock{} - genericSignedBlock.Block = phase0Block - - jsonPhase0Block := structs.SignedBeaconBlockPhase0FromConsensus(phase0Block.Phase0) - - marshalledBlock, err := json.Marshal(jsonPhase0Block) - require.NoError(t, err) - - ctx := t.Context() - - // Make sure that what we send in the POST body is the marshalled version of the protobuf block - headers := map[string]string{"Eth-Consensus-Version": "phase0"} - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v2/beacon/blocks", - headers, - bytes.NewBuffer(marshalledBlock), - nil, - ) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - proposeResponse, err := validatorClient.proposeBeaconBlock(ctx, genericSignedBlock) - assert.NoError(t, err) - require.NotNil(t, proposeResponse) - - expectedBlockRoot, err := phase0Block.Phase0.Block.HashTreeRoot() - require.NoError(t, err) - - // Make sure that the block root is set - assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) -} - -func generateSignedPhase0Block() *ethpb.GenericSignedBeaconBlock_Phase0 { - return ðpb.GenericSignedBeaconBlock_Phase0{ - Phase0: ðpb.SignedBeaconBlock{ - Block: testhelpers.GenerateProtoPhase0BeaconBlock(), - Signature: testhelpers.FillByteSlice(96, 110), - }, - } -} diff --git a/validator/client/beacon-api/propose_beacon_block_test.go b/validator/client/beacon-api/propose_beacon_block_test.go index 72528631a6..8383efe058 100644 --- a/validator/client/beacon-api/propose_beacon_block_test.go +++ b/validator/client/beacon-api/propose_beacon_block_test.go @@ -1,43 +1,42 @@ package beacon_api import ( + "bytes" + "encoding/json" "errors" "net/http" "testing" + "github.com/OffchainLabs/prysm/v6/api/server/structs" + rpctesting "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared/testing" "github.com/OffchainLabs/prysm/v6/network/httputil" + engine "github.com/OffchainLabs/prysm/v6/proto/engine/v1" ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v6/testing/assert" + "github.com/OffchainLabs/prysm/v6/testing/require" "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/mock" + testhelpers "github.com/OffchainLabs/prysm/v6/validator/client/beacon-api/test-helpers" "go.uber.org/mock/gomock" ) -func TestProposeBeaconBlock_Error(t *testing.T) { +func TestProposeBeaconBlock_SSZ_Error(t *testing.T) { testSuites := []struct { name string returnedError error expectedErrorMessage string }{ - { - name: "error 202", - expectedErrorMessage: "block was successfully broadcast but failed validation", - returnedError: &httputil.DefaultJsonError{ - Code: http.StatusAccepted, - Message: "202 error", - }, - }, { name: "error 500", - expectedErrorMessage: "HTTP request unsuccessful (500: foo error)", + expectedErrorMessage: "failed to submit block ssz", returnedError: &httputil.DefaultJsonError{ Code: http.StatusInternalServerError, - Message: "foo error", + Message: "failed to submit block ssz", }, }, { name: "other error", - expectedErrorMessage: "foo error", - returnedError: errors.New("foo error"), + expectedErrorMessage: "failed to submit block ssz", + returnedError: errors.New("failed to submit block ssz"), }, } @@ -106,17 +105,21 @@ func TestProposeBeaconBlock_Error(t *testing.T) { ctx := t.Context() jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - headers := map[string]string{"Eth-Consensus-Version": testCase.consensusVersion} - jsonRestHandler.EXPECT().Post( + // Expect PostSSZ to be called first with SSZ data + headers := map[string]string{ + "Eth-Consensus-Version": testCase.consensusVersion, + } + jsonRestHandler.EXPECT().PostSSZ( gomock.Any(), testCase.endpoint, headers, gomock.Any(), - nil, ).Return( - testSuite.returnedError, + nil, nil, testSuite.returnedError, ).Times(1) + // No JSON fallback expected for non-406 errors + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} _, err := validatorClient.proposeBeaconBlock(ctx, testCase.block) assert.ErrorContains(t, testSuite.expectedErrorMessage, err) @@ -130,3 +133,546 @@ func TestProposeBeaconBlock_UnsupportedBlockType(t *testing.T) { _, err := validatorClient.proposeBeaconBlock(t.Context(), ðpb.GenericSignedBeaconBlock{}) assert.ErrorContains(t, "unsupported block type", err) } + +func TestProposeBeaconBlock_SSZSuccess_NoFallback(t *testing.T) { + testCases := []struct { + name string + consensusVersion string + endpoint string + block *ethpb.GenericSignedBeaconBlock + }{ + { + name: "phase0", + consensusVersion: "phase0", + endpoint: "/eth/v2/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + }, + { + name: "altair", + consensusVersion: "altair", + endpoint: "/eth/v2/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedAltairBlock(), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := t.Context() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + // Expect PostSSZ to be called and succeed + headers := map[string]string{ + "Eth-Consensus-Version": testCase.consensusVersion, + } + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + testCase.endpoint, + headers, + gomock.Any(), + ).Return( + nil, nil, nil, + ).Times(1) + + // Post should NOT be called when PostSSZ succeeds + jsonRestHandler.EXPECT().Post( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Times(0) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + _, err := validatorClient.proposeBeaconBlock(ctx, testCase.block) + assert.NoError(t, err) + }) + } +} + +func TestProposeBeaconBlock_NewerTypes_SSZMarshal(t *testing.T) { + t.Run("deneb", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + var blockContents structs.SignedBeaconBlockContentsDeneb + err := json.Unmarshal([]byte(rpctesting.DenebBlockContents), &blockContents) + require.NoError(t, err) + genericSignedBlock, err := blockContents.ToGeneric() + require.NoError(t, err) + + denebBytes, err := genericSignedBlock.GetDeneb().MarshalSSZ() + require.NoError(t, err) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blocks", + gomock.Any(), + bytes.NewBuffer(denebBytes), + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := genericSignedBlock.GetDeneb().Block.HashTreeRoot() + require.NoError(t, err) + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) + }) + + t.Run("blinded_deneb", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + var blindedBlock structs.SignedBlindedBeaconBlockDeneb + err := json.Unmarshal([]byte(rpctesting.BlindedDenebBlock), &blindedBlock) + require.NoError(t, err) + genericSignedBlock, err := blindedBlock.ToGeneric() + require.NoError(t, err) + + blindedDenebBytes, err := genericSignedBlock.GetBlindedDeneb().MarshalSSZ() + require.NoError(t, err) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blinded_blocks", + gomock.Any(), + bytes.NewBuffer(blindedDenebBytes), + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := genericSignedBlock.GetBlindedDeneb().HashTreeRoot() + require.NoError(t, err) + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) + }) + + t.Run("electra", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + var blockContents structs.SignedBeaconBlockContentsElectra + err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &blockContents) + require.NoError(t, err) + genericSignedBlock, err := blockContents.ToGeneric() + require.NoError(t, err) + + electraBytes, err := genericSignedBlock.GetElectra().MarshalSSZ() + require.NoError(t, err) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blocks", + gomock.Any(), + bytes.NewBuffer(electraBytes), + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := genericSignedBlock.GetElectra().Block.HashTreeRoot() + require.NoError(t, err) + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) + }) + + t.Run("blinded_electra", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + var blindedBlock structs.SignedBlindedBeaconBlockElectra + err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &blindedBlock) + require.NoError(t, err) + genericSignedBlock, err := blindedBlock.ToGeneric() + require.NoError(t, err) + + blindedElectraBytes, err := genericSignedBlock.GetBlindedElectra().MarshalSSZ() + require.NoError(t, err) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blinded_blocks", + gomock.Any(), + bytes.NewBuffer(blindedElectraBytes), + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := genericSignedBlock.GetBlindedElectra().HashTreeRoot() + require.NoError(t, err) + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) + }) + + t.Run("fulu", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + var blockContents structs.SignedBeaconBlockContentsFulu + err := json.Unmarshal([]byte(rpctesting.FuluBlockContents), &blockContents) + require.NoError(t, err) + genericSignedBlock, err := blockContents.ToGeneric() + require.NoError(t, err) + + fuluBytes, err := genericSignedBlock.GetFulu().MarshalSSZ() + require.NoError(t, err) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blocks", + gomock.Any(), + bytes.NewBuffer(fuluBytes), + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := genericSignedBlock.GetFulu().Block.HashTreeRoot() + require.NoError(t, err) + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) + }) + + t.Run("blinded_fulu", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + var blindedBlock structs.SignedBlindedBeaconBlockFulu + err := json.Unmarshal([]byte(rpctesting.BlindedFuluBlock), &blindedBlock) + require.NoError(t, err) + genericSignedBlock, err := blindedBlock.ToGeneric() + require.NoError(t, err) + + blindedFuluBytes, err := genericSignedBlock.GetBlindedFulu().MarshalSSZ() + require.NoError(t, err) + + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + "/eth/v2/beacon/blinded_blocks", + gomock.Any(), + bytes.NewBuffer(blindedFuluBytes), + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(t.Context(), genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := genericSignedBlock.GetBlindedFulu().HashTreeRoot() + require.NoError(t, err) + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) + }) +} + +// Generator functions for test blocks +func generateSignedPhase0Block() *ethpb.GenericSignedBeaconBlock_Phase0 { + return ðpb.GenericSignedBeaconBlock_Phase0{ + Phase0: ðpb.SignedBeaconBlock{ + Block: testhelpers.GenerateProtoPhase0BeaconBlock(), + Signature: testhelpers.FillByteSlice(96, 110), + }, + } +} + +func generateSignedAltairBlock() *ethpb.GenericSignedBeaconBlock_Altair { + return ðpb.GenericSignedBeaconBlock_Altair{ + Altair: ðpb.SignedBeaconBlockAltair{ + Block: testhelpers.GenerateProtoAltairBeaconBlock(), + Signature: testhelpers.FillByteSlice(96, 112), + }, + } +} + +func generateSignedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_Bellatrix { + return ðpb.GenericSignedBeaconBlock_Bellatrix{ + Bellatrix: ðpb.SignedBeaconBlockBellatrix{ + Block: testhelpers.GenerateProtoBellatrixBeaconBlock(), + Signature: testhelpers.FillByteSlice(96, 127), + }, + } +} + +func generateSignedBlindedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_BlindedBellatrix { + return ðpb.GenericSignedBeaconBlock_BlindedBellatrix{ + BlindedBellatrix: ðpb.SignedBlindedBeaconBlockBellatrix{ + Block: ðpb.BlindedBeaconBlockBellatrix{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: testhelpers.FillByteSlice(32, 3), + StateRoot: testhelpers.FillByteSlice(32, 4), + Body: ðpb.BlindedBeaconBlockBodyBellatrix{ + RandaoReveal: testhelpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: testhelpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: testhelpers.FillByteSlice(32, 8), + }, + Graffiti: testhelpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: testhelpers.FillByteSlice(32, 12), + StateRoot: testhelpers.FillByteSlice(32, 13), + BodyRoot: testhelpers.FillByteSlice(32, 14), + }, + Signature: testhelpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: testhelpers.FillByteSlice(32, 18), + StateRoot: testhelpers.FillByteSlice(32, 19), + BodyRoot: testhelpers.FillByteSlice(32, 20), + }, + Signature: testhelpers.FillByteSlice(96, 21), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{}, + Attestations: []*ethpb.Attestation{}, + Deposits: []*ethpb.Deposit{}, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{}, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: testhelpers.FillByteSlice(64, 100), + SyncCommitteeSignature: testhelpers.FillByteSlice(96, 101), + }, + ExecutionPayloadHeader: &engine.ExecutionPayloadHeader{ + ParentHash: testhelpers.FillByteSlice(32, 102), + FeeRecipient: testhelpers.FillByteSlice(20, 103), + StateRoot: testhelpers.FillByteSlice(32, 104), + ReceiptsRoot: testhelpers.FillByteSlice(32, 105), + LogsBloom: testhelpers.FillByteSlice(256, 106), + PrevRandao: testhelpers.FillByteSlice(32, 107), + BlockNumber: 108, + GasLimit: 109, + GasUsed: 110, + Timestamp: 111, + ExtraData: testhelpers.FillByteSlice(32, 112), + BaseFeePerGas: testhelpers.FillByteSlice(32, 113), + BlockHash: testhelpers.FillByteSlice(32, 114), + TransactionsRoot: testhelpers.FillByteSlice(32, 115), + }, + }, + }, + Signature: testhelpers.FillByteSlice(96, 116), + }, + } +} + +func generateSignedCapellaBlock() *ethpb.GenericSignedBeaconBlock_Capella { + return ðpb.GenericSignedBeaconBlock_Capella{ + Capella: ðpb.SignedBeaconBlockCapella{ + Block: testhelpers.GenerateProtoCapellaBeaconBlock(), + Signature: testhelpers.FillByteSlice(96, 127), + }, + } +} + +func generateSignedBlindedCapellaBlock() *ethpb.GenericSignedBeaconBlock_BlindedCapella { + return ðpb.GenericSignedBeaconBlock_BlindedCapella{ + BlindedCapella: ðpb.SignedBlindedBeaconBlockCapella{ + Block: ðpb.BlindedBeaconBlockCapella{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: testhelpers.FillByteSlice(32, 3), + StateRoot: testhelpers.FillByteSlice(32, 4), + Body: ðpb.BlindedBeaconBlockBodyCapella{ + RandaoReveal: testhelpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: testhelpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: testhelpers.FillByteSlice(32, 8), + }, + Graffiti: testhelpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: testhelpers.FillByteSlice(32, 12), + StateRoot: testhelpers.FillByteSlice(32, 13), + BodyRoot: testhelpers.FillByteSlice(32, 14), + }, + Signature: testhelpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: testhelpers.FillByteSlice(32, 18), + StateRoot: testhelpers.FillByteSlice(32, 19), + BodyRoot: testhelpers.FillByteSlice(32, 20), + }, + Signature: testhelpers.FillByteSlice(96, 21), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{}, + Attestations: []*ethpb.Attestation{}, + Deposits: []*ethpb.Deposit{}, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{}, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: testhelpers.FillByteSlice(64, 37), + SyncCommitteeSignature: testhelpers.FillByteSlice(96, 38), + }, + ExecutionPayloadHeader: &engine.ExecutionPayloadHeaderCapella{ + ParentHash: testhelpers.FillByteSlice(32, 39), + FeeRecipient: testhelpers.FillByteSlice(20, 40), + StateRoot: testhelpers.FillByteSlice(32, 41), + ReceiptsRoot: testhelpers.FillByteSlice(32, 42), + LogsBloom: testhelpers.FillByteSlice(256, 43), + PrevRandao: testhelpers.FillByteSlice(32, 44), + BlockNumber: 45, + GasLimit: 46, + GasUsed: 47, + Timestamp: 48, + ExtraData: testhelpers.FillByteSlice(32, 49), + BaseFeePerGas: testhelpers.FillByteSlice(32, 50), + BlockHash: testhelpers.FillByteSlice(32, 51), + TransactionsRoot: testhelpers.FillByteSlice(32, 52), + WithdrawalsRoot: testhelpers.FillByteSlice(32, 53), + }, + BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{}, + }, + }, + Signature: testhelpers.FillByteSlice(96, 54), + }, + } +} + +func TestProposeBeaconBlock_SSZFails_406_FallbackToJSON(t *testing.T) { + testCases := []struct { + name string + consensusVersion string + endpoint string + block *ethpb.GenericSignedBeaconBlock + }{ + { + name: "phase0", + consensusVersion: "phase0", + endpoint: "/eth/v2/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := t.Context() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + // Expect PostSSZ to be called first and fail + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + testCase.endpoint, + gomock.Any(), + gomock.Any(), + ).Return( + nil, nil, &httputil.DefaultJsonError{ + Code: http.StatusNotAcceptable, + Message: "SSZ not supported", + }, + ).Times(1) + + jsonRestHandler.EXPECT().Post( + gomock.Any(), + testCase.endpoint, + gomock.Any(), + gomock.Any(), + nil, + ).Return( + nil, + ).Times(1) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + _, err := validatorClient.proposeBeaconBlock(ctx, testCase.block) + assert.NoError(t, err) + }) + } +} + +func TestProposeBeaconBlock_SSZFails_Non406_NoFallback(t *testing.T) { + testCases := []struct { + name string + consensusVersion string + endpoint string + block *ethpb.GenericSignedBeaconBlock + }{ + { + name: "phase0", + consensusVersion: "phase0", + endpoint: "/eth/v2/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := t.Context() + jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) + + // Expect PostSSZ to be called first and fail with non-406 error + sszHeaders := map[string]string{ + "Eth-Consensus-Version": testCase.consensusVersion, + } + jsonRestHandler.EXPECT().PostSSZ( + gomock.Any(), + testCase.endpoint, + sszHeaders, + gomock.Any(), + ).Return( + nil, nil, &httputil.DefaultJsonError{ + Code: http.StatusInternalServerError, + Message: "Internal server error", + }, + ).Times(1) + + // Post should NOT be called for non-406 errors + jsonRestHandler.EXPECT().Post( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ).Times(0) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + _, err := validatorClient.proposeBeaconBlock(ctx, testCase.block) + require.ErrorContains(t, "Internal server error", err) + }) + } +} diff --git a/validator/client/beacon-api/rest_handler_client.go b/validator/client/beacon-api/rest_handler_client.go index 7928bd4abf..ab8378d875 100644 --- a/validator/client/beacon-api/rest_handler_client.go +++ b/validator/client/beacon-api/rest_handler_client.go @@ -25,6 +25,7 @@ type RestHandler interface { Get(ctx context.Context, endpoint string, resp interface{}) error GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp interface{}) error + PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error) HttpClient() *http.Client Host() string SetHost(host string) @@ -179,6 +180,75 @@ func (c *BeaconApiRestHandler) Post( return decodeResp(httpResp, resp) } +// PostSSZ sends a POST request and prefers an SSZ (application/octet-stream) response body. +func (c *BeaconApiRestHandler) PostSSZ( + ctx context.Context, + apiEndpoint string, + headers map[string]string, + data *bytes.Buffer, +) ([]byte, http.Header, error) { + if data == nil { + return nil, nil, errors.New("data is nil") + } + url := c.host + apiEndpoint + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, data) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to create request for endpoint %s", url) + } + + // Accept header: prefer octet-stream (SSZ), fall back to JSON + primaryAcceptType := fmt.Sprintf("%s;q=%s", api.OctetStreamMediaType, "0.95") + secondaryAcceptType := fmt.Sprintf("%s;q=%s", api.JsonMediaType, "0.9") + acceptHeaderString := fmt.Sprintf("%s,%s", primaryAcceptType, secondaryAcceptType) + req.Header.Set("Accept", acceptHeaderString) + + // User-supplied headers + for headerKey, headerValue := range headers { + req.Header.Set(headerKey, headerValue) + } + + for _, o := range c.reqOverrides { + o(req) + } + req.Header.Set("Content-Type", api.OctetStreamMediaType) + req.Header.Set("User-Agent", version.BuildData()) + httpResp, err := c.client.Do(req) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to perform request for endpoint %s", url) + } + defer func() { + if err := httpResp.Body.Close(); err != nil { + return + } + }() + + accept := req.Header.Get("Accept") + contentType := httpResp.Header.Get("Content-Type") + body, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to read response body for %s", httpResp.Request.URL) + } + + if !apiutil.PrimaryAcceptMatches(accept, contentType) { + log.WithFields(logrus.Fields{ + "Accept": accept, + "Content-Type": contentType, + }).Debug("Server responded with non primary accept type") + } + + // non-2XX codes are a failure + if !strings.HasPrefix(httpResp.Status, "2") { + decoder := json.NewDecoder(bytes.NewBuffer(body)) + errorJson := &httputil.DefaultJsonError{} + if err = decoder.Decode(errorJson); err != nil { + return nil, nil, errors.Wrapf(err, "failed to decode response body into error json for %s", httpResp.Request.URL) + } + return nil, nil, errorJson + } + + return body, httpResp.Header, nil +} + func decodeResp(httpResp *http.Response, resp interface{}) error { body, err := io.ReadAll(httpResp.Body) if err != nil {