package beacon_api import ( "context" "encoding/json" "errors" "fmt" "testing" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock" test_helpers "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/test-helpers" ) func TestGetBeaconBlock_RequestFailed(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() ctx := context.Background() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) jsonRestHandler.EXPECT().GetRestJsonResponse( ctx, gomock.Any(), gomock.Any(), ).Return( nil, errors.New("foo error"), ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} _, err := validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) assert.ErrorContains(t, "failed to query GET REST endpoint", err) assert.ErrorContains(t, "foo error", err) } func TestGetBeaconBlock_Error(t *testing.T) { phase0BeaconBlockBytes, err := json.Marshal(apimiddleware.BeaconBlockJson{}) require.NoError(t, err) altairBeaconBlockBytes, err := json.Marshal(apimiddleware.BeaconBlockAltairJson{}) require.NoError(t, err) bellatrixBeaconBlockBytes, err := json.Marshal(apimiddleware.BeaconBlockBellatrixJson{}) require.NoError(t, err) capellaBeaconBlockBytes, err := json.Marshal(apimiddleware.BeaconBlockCapellaJson{}) require.NoError(t, err) testCases := []struct { name string beaconBlock interface{} expectedErrorMessage string consensusVersion string data json.RawMessage }{ { name: "phase0 block decoding failed", expectedErrorMessage: "failed to decode phase0 block response json", consensusVersion: "phase0", data: []byte{}, }, { name: "phase0 block conversion failed", expectedErrorMessage: "failed to get phase0 block", consensusVersion: "phase0", data: phase0BeaconBlockBytes, }, { name: "altair block decoding failed", expectedErrorMessage: "failed to decode altair block response json", consensusVersion: "altair", data: []byte{}, }, { name: "altair block conversion failed", expectedErrorMessage: "failed to get altair block", consensusVersion: "altair", data: altairBeaconBlockBytes, }, { name: "bellatrix block decoding failed", expectedErrorMessage: "failed to decode bellatrix block response json", beaconBlock: "foo", consensusVersion: "bellatrix", data: []byte{}, }, { name: "bellatrix block conversion failed", expectedErrorMessage: "failed to get bellatrix block", consensusVersion: "bellatrix", data: bellatrixBeaconBlockBytes, }, { name: "capella block decoding failed", expectedErrorMessage: "failed to decode capella block response json", beaconBlock: "foo", consensusVersion: "capella", data: []byte{}, }, { name: "capella block conversion failed", expectedErrorMessage: "failed to get capella block", consensusVersion: "capella", data: capellaBeaconBlockBytes, }, { name: "unsupported consensus version", expectedErrorMessage: "unsupported consensus version `foo`", consensusVersion: "foo", }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() ctx := context.Background() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) jsonRestHandler.EXPECT().GetRestJsonResponse( ctx, gomock.Any(), &abstractProduceBlockResponseJson{}, ).SetArg( 2, abstractProduceBlockResponseJson{ Version: testCase.consensusVersion, Data: testCase.data, }, ).Return( nil, nil, ).Times(1) beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) beaconBlockConverter.EXPECT().ConvertRESTPhase0BlockToProto( gomock.Any(), ).Return( nil, errors.New(testCase.expectedErrorMessage), ).AnyTimes() beaconBlockConverter.EXPECT().ConvertRESTAltairBlockToProto( gomock.Any(), ).Return( nil, errors.New(testCase.expectedErrorMessage), ).AnyTimes() beaconBlockConverter.EXPECT().ConvertRESTBellatrixBlockToProto( gomock.Any(), ).Return( nil, errors.New(testCase.expectedErrorMessage), ).AnyTimes() beaconBlockConverter.EXPECT().ConvertRESTCapellaBlockToProto( gomock.Any(), ).Return( nil, errors.New(testCase.expectedErrorMessage), ).AnyTimes() validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} _, err := validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) assert.ErrorContains(t, testCase.expectedErrorMessage, err) }) } } func TestGetBeaconBlock_Phase0Valid(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() phase0ProtoBeaconBlock := test_helpers.GenerateProtoPhase0BeaconBlock() phase0BeaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() phase0BeaconBlockBytes, err := json.Marshal(phase0BeaconBlock) require.NoError(t, err) const slot = primitives.Slot(1) randaoReveal := []byte{2} graffiti := []byte{3} ctx := context.Background() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) jsonRestHandler.EXPECT().GetRestJsonResponse( ctx, fmt.Sprintf("/eth/v2/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &abstractProduceBlockResponseJson{}, ).SetArg( 2, abstractProduceBlockResponseJson{ Version: "phase0", Data: phase0BeaconBlockBytes, }, ).Return( nil, nil, ).Times(1) beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) beaconBlockConverter.EXPECT().ConvertRESTPhase0BlockToProto( phase0BeaconBlock, ).Return( phase0ProtoBeaconBlock, nil, ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ Block: ðpb.GenericBeaconBlock_Phase0{ Phase0: phase0ProtoBeaconBlock, }, } assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) } func TestGetBeaconBlock_AltairValid(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() altairProtoBeaconBlock := test_helpers.GenerateProtoAltairBeaconBlock() altairBeaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() altairBeaconBlockBytes, err := json.Marshal(altairBeaconBlock) require.NoError(t, err) const slot = primitives.Slot(1) randaoReveal := []byte{2} graffiti := []byte{3} ctx := context.Background() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) jsonRestHandler.EXPECT().GetRestJsonResponse( ctx, fmt.Sprintf("/eth/v2/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &abstractProduceBlockResponseJson{}, ).SetArg( 2, abstractProduceBlockResponseJson{ Version: "altair", Data: altairBeaconBlockBytes, }, ).Return( nil, nil, ).Times(1) beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) beaconBlockConverter.EXPECT().ConvertRESTAltairBlockToProto( altairBeaconBlock, ).Return( altairProtoBeaconBlock, nil, ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ Block: ðpb.GenericBeaconBlock_Altair{ Altair: altairProtoBeaconBlock, }, } assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) } func TestGetBeaconBlock_BellatrixValid(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() bellatrixProtoBeaconBlock := test_helpers.GenerateProtoBellatrixBeaconBlock() bellatrixBeaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() bellatrixBeaconBlockBytes, err := json.Marshal(bellatrixBeaconBlock) require.NoError(t, err) const slot = primitives.Slot(1) randaoReveal := []byte{2} graffiti := []byte{3} ctx := context.Background() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) jsonRestHandler.EXPECT().GetRestJsonResponse( ctx, fmt.Sprintf("/eth/v2/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &abstractProduceBlockResponseJson{}, ).SetArg( 2, abstractProduceBlockResponseJson{ Version: "bellatrix", Data: bellatrixBeaconBlockBytes, }, ).Return( nil, nil, ).Times(1) beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) beaconBlockConverter.EXPECT().ConvertRESTBellatrixBlockToProto( bellatrixBeaconBlock, ).Return( bellatrixProtoBeaconBlock, nil, ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ Block: ðpb.GenericBeaconBlock_Bellatrix{ Bellatrix: bellatrixProtoBeaconBlock, }, } assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) } func TestGetBeaconBlock_CapellaValid(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() capellaProtoBeaconBlock := test_helpers.GenerateProtoCapellaBeaconBlock() capellaBeaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() capellaBeaconBlockBytes, err := json.Marshal(capellaBeaconBlock) require.NoError(t, err) const slot = primitives.Slot(1) randaoReveal := []byte{2} graffiti := []byte{3} ctx := context.Background() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) jsonRestHandler.EXPECT().GetRestJsonResponse( ctx, fmt.Sprintf("/eth/v2/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &abstractProduceBlockResponseJson{}, ).SetArg( 2, abstractProduceBlockResponseJson{ Version: "capella", Data: capellaBeaconBlockBytes, }, ).Return( nil, nil, ).Times(1) beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) beaconBlockConverter.EXPECT().ConvertRESTCapellaBlockToProto( capellaBeaconBlock, ).Return( capellaProtoBeaconBlock, nil, ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ Block: ðpb.GenericBeaconBlock_Capella{ Capella: capellaProtoBeaconBlock, }, } assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) }