From 791110f7950425ea1938dee84eaa82bee6afb6a3 Mon Sep 17 00:00:00 2001 From: Patrice Vignola Date: Mon, 13 Feb 2023 05:22:11 -0800 Subject: [PATCH] Add REST implementation for Validator's `StreamBlocksAltair` (#11974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: RadosÅ‚aw Kapka --- hack/update-mockgen.sh | 1 + testing/mock/validator_client_mock.go | 4 +- validator/client/beacon-api/BUILD.bazel | 8 +- .../beacon-api/beacon_api_validator_client.go | 9 +- .../beacon-api/beacon_block_converter.go | 423 +++++++++ .../beacon-api/beacon_block_converter_test.go | 505 +++++++++++ .../client/beacon-api/get_beacon_block.go | 432 +-------- .../get_beacon_block_altair_test.go | 151 ---- .../get_beacon_block_bellatrix_test.go | 259 ------ .../get_beacon_block_capella_test.go | 149 ---- .../get_beacon_block_phase0_test.go | 249 ------ .../beacon-api/get_beacon_block_test.go | 243 ++++- validator/client/beacon-api/mock/BUILD.bazel | 2 + .../mock/beacon_block_converter_mock.go | 96 ++ .../client/beacon-api/mock/duties_mock.go | 10 +- validator/client/beacon-api/stream_blocks.go | 196 +++++ .../client/beacon-api/stream_blocks_test.go | 832 ++++++++++++++++++ 17 files changed, 2327 insertions(+), 1242 deletions(-) create mode 100644 validator/client/beacon-api/beacon_block_converter.go create mode 100644 validator/client/beacon-api/beacon_block_converter_test.go delete mode 100644 validator/client/beacon-api/get_beacon_block_altair_test.go delete mode 100644 validator/client/beacon-api/get_beacon_block_bellatrix_test.go delete mode 100644 validator/client/beacon-api/get_beacon_block_capella_test.go delete mode 100644 validator/client/beacon-api/get_beacon_block_phase0_test.go create mode 100644 validator/client/beacon-api/mock/beacon_block_converter_mock.go create mode 100644 validator/client/beacon-api/stream_blocks.go create mode 100644 validator/client/beacon-api/stream_blocks_test.go diff --git a/hack/update-mockgen.sh b/hack/update-mockgen.sh index 734b9a959..c7a9fa3ee 100755 --- a/hack/update-mockgen.sh +++ b/hack/update-mockgen.sh @@ -76,6 +76,7 @@ beacon_api_mocks=( "$beacon_api_mock_path/duties_mock.go duties.go" "$beacon_api_mock_path/json_rest_handler_mock.go json_rest_handler.go" "$beacon_api_mock_path/state_validators_mock.go state_validators.go" + "$beacon_api_mock_path/beacon_block_converter_mock.go beacon_block_converter.go" ) for ((i = 0; i < ${#beacon_api_mocks[@]}; i++)); do diff --git a/testing/mock/validator_client_mock.go b/testing/mock/validator_client_mock.go index 6ed9600ba..0c4e7fd17 100644 --- a/testing/mock/validator_client_mock.go +++ b/testing/mock/validator_client_mock.go @@ -9,7 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" + primitives "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" eth "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -353,7 +353,7 @@ func (mr *MockValidatorClientMockRecorder) SubmitValidatorRegistrations(arg0, ar } // SubscribeCommitteeSubnets mocks base method. -func (m *MockValidatorClient) SubscribeCommitteeSubnets(arg0 context.Context, arg1 *eth.CommitteeSubnetsSubscribeRequest, arg2 []types.ValidatorIndex) (*emptypb.Empty, error) { +func (m *MockValidatorClient) SubscribeCommitteeSubnets(arg0 context.Context, arg1 *eth.CommitteeSubnetsSubscribeRequest, arg2 []primitives.ValidatorIndex) (*emptypb.Empty, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubscribeCommitteeSubnets", arg0, arg1, arg2) ret0, _ := ret[0].(*emptypb.Empty) diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index 9eab32e36..afbe1511d 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -7,6 +7,7 @@ go_library( "attestation_data.go", "beacon_api_helpers.go", "beacon_api_validator_client.go", + "beacon_block_converter.go", "beacon_block_json_helpers.go", "beacon_block_proto_helpers.go", "domain_data.go", @@ -24,6 +25,7 @@ go_library( "registration.go", "state_validators.go", "status.go", + "stream_blocks.go", "submit_signed_aggregate_proof.go", "submit_signed_contribution_and_proof.go", "subscribe_committee_subnets.go", @@ -60,16 +62,13 @@ go_test( "attestation_data_test.go", "beacon_api_helpers_test.go", "beacon_api_validator_client_test.go", + "beacon_block_converter_test.go", "beacon_block_json_helpers_test.go", "beacon_block_proto_helpers_test.go", "domain_data_test.go", "doppelganger_test.go", "duties_test.go", "genesis_test.go", - "get_beacon_block_altair_test.go", - "get_beacon_block_bellatrix_test.go", - "get_beacon_block_capella_test.go", - "get_beacon_block_phase0_test.go", "get_beacon_block_test.go", "index_test.go", "json_rest_handler_test.go", @@ -86,6 +85,7 @@ go_test( "registration_test.go", "state_validators_test.go", "status_test.go", + "stream_blocks_test.go", "submit_signed_aggregate_proof_test.go", "submit_signed_contribution_and_proof_test.go", "subscribe_committee_subnets_test.go", diff --git a/validator/client/beacon-api/beacon_api_validator_client.go b/validator/client/beacon-api/beacon_api_validator_client.go index c138f1065..57ee915f5 100644 --- a/validator/client/beacon-api/beacon_api_validator_client.go +++ b/validator/client/beacon-api/beacon_api_validator_client.go @@ -20,6 +20,7 @@ type beaconApiValidatorClient struct { stateValidatorsProvider stateValidatorsProvider jsonRestHandler jsonRestHandler fallbackClient iface.ValidatorClient + beaconBlockConverter beaconBlockConverter } func NewBeaconApiValidatorClientWithFallback(host string, timeout time.Duration, fallbackClient iface.ValidatorClient) iface.ValidatorClient { @@ -33,6 +34,7 @@ func NewBeaconApiValidatorClientWithFallback(host string, timeout time.Duration, dutiesProvider: beaconApiDutiesProvider{jsonRestHandler: jsonRestHandler}, stateValidatorsProvider: beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler}, jsonRestHandler: jsonRestHandler, + beaconBlockConverter: beaconApiBeaconBlockConverter{}, fallbackClient: fallbackClient, } } @@ -108,12 +110,7 @@ func (c *beaconApiValidatorClient) ProposeExit(ctx context.Context, in *ethpb.Si } func (c *beaconApiValidatorClient) StreamBlocksAltair(ctx context.Context, in *ethpb.StreamBlocksRequest) (ethpb.BeaconNodeValidator_StreamBlocksAltairClient, error) { - if c.fallbackClient != nil { - return c.fallbackClient.StreamBlocksAltair(ctx, in) - } - - // TODO: Implement me - panic("beaconApiValidatorClient.StreamBlocksAltair is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.") + return c.streamBlocks(ctx, in, time.Second), nil } func (c *beaconApiValidatorClient) StreamDuties(ctx context.Context, in *ethpb.DutiesRequest) (ethpb.BeaconNodeValidator_StreamDutiesClient, error) { diff --git a/validator/client/beacon-api/beacon_block_converter.go b/validator/client/beacon-api/beacon_block_converter.go new file mode 100644 index 000000000..e3d33e047 --- /dev/null +++ b/validator/client/beacon-api/beacon_block_converter.go @@ -0,0 +1,423 @@ +package beacon_api + +import ( + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" + enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" +) + +type beaconBlockConverter interface { + ConvertRESTPhase0BlockToProto(block *apimiddleware.BeaconBlockJson) (*ethpb.BeaconBlock, error) + ConvertRESTAltairBlockToProto(block *apimiddleware.BeaconBlockAltairJson) (*ethpb.BeaconBlockAltair, error) + ConvertRESTBellatrixBlockToProto(block *apimiddleware.BeaconBlockBellatrixJson) (*ethpb.BeaconBlockBellatrix, error) + ConvertRESTCapellaBlockToProto(block *apimiddleware.BeaconBlockCapellaJson) (*ethpb.BeaconBlockCapella, error) +} + +type beaconApiBeaconBlockConverter struct{} + +// ConvertRESTPhase0BlockToProto converts a Phase0 JSON beacon block to its protobuf equivalent +func (c beaconApiBeaconBlockConverter) ConvertRESTPhase0BlockToProto(block *apimiddleware.BeaconBlockJson) (*ethpb.BeaconBlock, error) { + blockSlot, err := strconv.ParseUint(block.Slot, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse slot `%s`", block.Slot) + } + + blockProposerIndex, err := strconv.ParseUint(block.ProposerIndex, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse proposer index `%s`", block.ProposerIndex) + } + + parentRoot, err := hexutil.Decode(block.ParentRoot) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode parent root `%s`", block.ParentRoot) + } + + stateRoot, err := hexutil.Decode(block.StateRoot) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode state root `%s`", block.StateRoot) + } + + if block.Body == nil { + return nil, errors.New("block body is nil") + } + + randaoReveal, err := hexutil.Decode(block.Body.RandaoReveal) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode randao reveal `%s`", block.Body.RandaoReveal) + } + + if block.Body.Eth1Data == nil { + return nil, errors.New("eth1 data is nil") + } + + depositRoot, err := hexutil.Decode(block.Body.Eth1Data.DepositRoot) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode deposit root `%s`", block.Body.Eth1Data.DepositRoot) + } + + depositCount, err := strconv.ParseUint(block.Body.Eth1Data.DepositCount, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse deposit count `%s`", block.Body.Eth1Data.DepositCount) + } + + blockHash, err := hexutil.Decode(block.Body.Eth1Data.BlockHash) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode block hash `%s`", block.Body.Eth1Data.BlockHash) + } + + graffiti, err := hexutil.Decode(block.Body.Graffiti) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode graffiti `%s`", block.Body.Graffiti) + } + + proposerSlashings, err := convertProposerSlashingsToProto(block.Body.ProposerSlashings) + if err != nil { + return nil, errors.Wrap(err, "failed to get proposer slashings") + } + + attesterSlashings, err := convertAttesterSlashingsToProto(block.Body.AttesterSlashings) + if err != nil { + return nil, errors.Wrap(err, "failed to get attester slashings") + } + + attestations, err := convertAttestationsToProto(block.Body.Attestations) + if err != nil { + return nil, errors.Wrap(err, "failed to get attestations") + } + + deposits, err := convertDepositsToProto(block.Body.Deposits) + if err != nil { + return nil, errors.Wrap(err, "failed to get deposits") + } + + voluntaryExits, err := convertVoluntaryExitsToProto(block.Body.VoluntaryExits) + if err != nil { + return nil, errors.Wrap(err, "failed to get voluntary exits") + } + + return ðpb.BeaconBlock{ + Slot: primitives.Slot(blockSlot), + ProposerIndex: primitives.ValidatorIndex(blockProposerIndex), + ParentRoot: parentRoot, + StateRoot: stateRoot, + Body: ðpb.BeaconBlockBody{ + RandaoReveal: randaoReveal, + Eth1Data: ðpb.Eth1Data{ + DepositRoot: depositRoot, + DepositCount: depositCount, + BlockHash: blockHash, + }, + Graffiti: graffiti, + ProposerSlashings: proposerSlashings, + AttesterSlashings: attesterSlashings, + Attestations: attestations, + Deposits: deposits, + VoluntaryExits: voluntaryExits, + }, + }, nil +} + +// ConvertRESTAltairBlockToProto converts an Altair JSON beacon block to its protobuf equivalent +func (c beaconApiBeaconBlockConverter) ConvertRESTAltairBlockToProto(block *apimiddleware.BeaconBlockAltairJson) (*ethpb.BeaconBlockAltair, error) { + if block.Body == nil { + return nil, errors.New("block body is nil") + } + + // Call convertRESTPhase0BlockToProto to set the phase0 fields because all the error handling and the heavy lifting + // has already been done + phase0Block, err := c.ConvertRESTPhase0BlockToProto(&apimiddleware.BeaconBlockJson{ + Slot: block.Slot, + ProposerIndex: block.ProposerIndex, + ParentRoot: block.ParentRoot, + StateRoot: block.StateRoot, + Body: &apimiddleware.BeaconBlockBodyJson{ + RandaoReveal: block.Body.RandaoReveal, + Eth1Data: block.Body.Eth1Data, + Graffiti: block.Body.Graffiti, + ProposerSlashings: block.Body.ProposerSlashings, + AttesterSlashings: block.Body.AttesterSlashings, + Attestations: block.Body.Attestations, + Deposits: block.Body.Deposits, + VoluntaryExits: block.Body.VoluntaryExits, + }, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to get the phase0 fields of the altair block") + } + + if block.Body.SyncAggregate == nil { + return nil, errors.New("sync aggregate is nil") + } + + syncCommitteeBits, err := hexutil.Decode(block.Body.SyncAggregate.SyncCommitteeBits) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode sync committee bits `%s`", block.Body.SyncAggregate.SyncCommitteeBits) + } + + syncCommitteeSignature, err := hexutil.Decode(block.Body.SyncAggregate.SyncCommitteeSignature) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode sync committee signature `%s`", block.Body.SyncAggregate.SyncCommitteeSignature) + } + + return ðpb.BeaconBlockAltair{ + Slot: phase0Block.Slot, + ProposerIndex: phase0Block.ProposerIndex, + ParentRoot: phase0Block.ParentRoot, + StateRoot: phase0Block.StateRoot, + Body: ðpb.BeaconBlockBodyAltair{ + RandaoReveal: phase0Block.Body.RandaoReveal, + Eth1Data: phase0Block.Body.Eth1Data, + Graffiti: phase0Block.Body.Graffiti, + ProposerSlashings: phase0Block.Body.ProposerSlashings, + AttesterSlashings: phase0Block.Body.AttesterSlashings, + Attestations: phase0Block.Body.Attestations, + Deposits: phase0Block.Body.Deposits, + VoluntaryExits: phase0Block.Body.VoluntaryExits, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: syncCommitteeBits, + SyncCommitteeSignature: syncCommitteeSignature, + }, + }, + }, nil +} + +// ConvertRESTBellatrixBlockToProto converts a Bellatrix JSON beacon block to its protobuf equivalent +func (c beaconApiBeaconBlockConverter) ConvertRESTBellatrixBlockToProto(block *apimiddleware.BeaconBlockBellatrixJson) (*ethpb.BeaconBlockBellatrix, error) { + if block.Body == nil { + return nil, errors.New("block body is nil") + } + + // Call convertRESTAltairBlockToProto to set the altair fields because all the error handling and the heavy lifting + // has already been done + altairBlock, err := c.ConvertRESTAltairBlockToProto(&apimiddleware.BeaconBlockAltairJson{ + Slot: block.Slot, + ProposerIndex: block.ProposerIndex, + ParentRoot: block.ParentRoot, + StateRoot: block.StateRoot, + Body: &apimiddleware.BeaconBlockBodyAltairJson{ + RandaoReveal: block.Body.RandaoReveal, + Eth1Data: block.Body.Eth1Data, + Graffiti: block.Body.Graffiti, + ProposerSlashings: block.Body.ProposerSlashings, + AttesterSlashings: block.Body.AttesterSlashings, + Attestations: block.Body.Attestations, + Deposits: block.Body.Deposits, + VoluntaryExits: block.Body.VoluntaryExits, + SyncAggregate: block.Body.SyncAggregate, + }, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to get the altair fields of the bellatrix block") + } + + if block.Body.ExecutionPayload == nil { + return nil, errors.New("execution payload is nil") + } + + parentHash, err := hexutil.Decode(block.Body.ExecutionPayload.ParentHash) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload parent hash `%s`", block.Body.ExecutionPayload.ParentHash) + } + + feeRecipient, err := hexutil.Decode(block.Body.ExecutionPayload.FeeRecipient) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload fee recipient `%s`", block.Body.ExecutionPayload.FeeRecipient) + } + + stateRoot, err := hexutil.Decode(block.Body.ExecutionPayload.StateRoot) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload state root `%s`", block.Body.ExecutionPayload.StateRoot) + } + + receiptsRoot, err := hexutil.Decode(block.Body.ExecutionPayload.ReceiptsRoot) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload receipts root `%s`", block.Body.ExecutionPayload.ReceiptsRoot) + } + + logsBloom, err := hexutil.Decode(block.Body.ExecutionPayload.LogsBloom) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload logs bloom `%s`", block.Body.ExecutionPayload.LogsBloom) + } + + prevRandao, err := hexutil.Decode(block.Body.ExecutionPayload.PrevRandao) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload prev randao `%s`", block.Body.ExecutionPayload.PrevRandao) + } + + blockNumber, err := strconv.ParseUint(block.Body.ExecutionPayload.BlockNumber, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse execution payload block number `%s`", block.Body.ExecutionPayload.BlockNumber) + } + + gasLimit, err := strconv.ParseUint(block.Body.ExecutionPayload.GasLimit, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse execution payload gas limit `%s`", block.Body.ExecutionPayload.GasLimit) + } + + gasUsed, err := strconv.ParseUint(block.Body.ExecutionPayload.GasUsed, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse execution payload gas used `%s`", block.Body.ExecutionPayload.GasUsed) + } + + timestamp, err := strconv.ParseUint(block.Body.ExecutionPayload.TimeStamp, 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse execution payload timestamp `%s`", block.Body.ExecutionPayload.TimeStamp) + } + + extraData, err := hexutil.Decode(block.Body.ExecutionPayload.ExtraData) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload extra data `%s`", block.Body.ExecutionPayload.ExtraData) + } + + baseFeePerGas := new(big.Int) + if _, ok := baseFeePerGas.SetString(block.Body.ExecutionPayload.BaseFeePerGas, 10); !ok { + return nil, errors.Errorf("failed to parse execution payload base fee per gas `%s`", block.Body.ExecutionPayload.BaseFeePerGas) + } + + blockHash, err := hexutil.Decode(block.Body.ExecutionPayload.BlockHash) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode execution payload block hash `%s`", block.Body.ExecutionPayload.BlockHash) + } + + transactions, err := convertTransactionsToProto(block.Body.ExecutionPayload.Transactions) + if err != nil { + return nil, errors.Wrap(err, "failed to get execution payload transactions") + } + + return ðpb.BeaconBlockBellatrix{ + Slot: altairBlock.Slot, + ProposerIndex: altairBlock.ProposerIndex, + ParentRoot: altairBlock.ParentRoot, + StateRoot: altairBlock.StateRoot, + Body: ðpb.BeaconBlockBodyBellatrix{ + RandaoReveal: altairBlock.Body.RandaoReveal, + Eth1Data: altairBlock.Body.Eth1Data, + Graffiti: altairBlock.Body.Graffiti, + ProposerSlashings: altairBlock.Body.ProposerSlashings, + AttesterSlashings: altairBlock.Body.AttesterSlashings, + Attestations: altairBlock.Body.Attestations, + Deposits: altairBlock.Body.Deposits, + VoluntaryExits: altairBlock.Body.VoluntaryExits, + SyncAggregate: altairBlock.Body.SyncAggregate, + ExecutionPayload: &enginev1.ExecutionPayload{ + ParentHash: parentHash, + FeeRecipient: feeRecipient, + StateRoot: stateRoot, + ReceiptsRoot: receiptsRoot, + LogsBloom: logsBloom, + PrevRandao: prevRandao, + BlockNumber: blockNumber, + GasLimit: gasLimit, + GasUsed: gasUsed, + Timestamp: timestamp, + ExtraData: extraData, + BaseFeePerGas: bytesutil.PadTo(bytesutil.BigIntToLittleEndianBytes(baseFeePerGas), 32), + BlockHash: blockHash, + Transactions: transactions, + }, + }, + }, nil +} + +// ConvertRESTCapellaBlockToProto converts a Capella JSON beacon block to its protobuf equivalent +func (c beaconApiBeaconBlockConverter) ConvertRESTCapellaBlockToProto(block *apimiddleware.BeaconBlockCapellaJson) (*ethpb.BeaconBlockCapella, error) { + if block.Body == nil { + return nil, errors.New("block body is nil") + } + + if block.Body.ExecutionPayload == nil { + return nil, errors.New("execution payload is nil") + } + + // Call convertRESTBellatrixBlockToProto to set the bellatrix fields because all the error handling and the heavy + // lifting has already been done + bellatrixBlock, err := c.ConvertRESTBellatrixBlockToProto(&apimiddleware.BeaconBlockBellatrixJson{ + Slot: block.Slot, + ProposerIndex: block.ProposerIndex, + ParentRoot: block.ParentRoot, + StateRoot: block.StateRoot, + Body: &apimiddleware.BeaconBlockBodyBellatrixJson{ + RandaoReveal: block.Body.RandaoReveal, + Eth1Data: block.Body.Eth1Data, + Graffiti: block.Body.Graffiti, + ProposerSlashings: block.Body.ProposerSlashings, + AttesterSlashings: block.Body.AttesterSlashings, + Attestations: block.Body.Attestations, + Deposits: block.Body.Deposits, + VoluntaryExits: block.Body.VoluntaryExits, + SyncAggregate: block.Body.SyncAggregate, + ExecutionPayload: &apimiddleware.ExecutionPayloadJson{ + ParentHash: block.Body.ExecutionPayload.ParentHash, + FeeRecipient: block.Body.ExecutionPayload.FeeRecipient, + StateRoot: block.Body.ExecutionPayload.StateRoot, + ReceiptsRoot: block.Body.ExecutionPayload.ReceiptsRoot, + LogsBloom: block.Body.ExecutionPayload.LogsBloom, + PrevRandao: block.Body.ExecutionPayload.PrevRandao, + BlockNumber: block.Body.ExecutionPayload.BlockNumber, + GasLimit: block.Body.ExecutionPayload.GasLimit, + GasUsed: block.Body.ExecutionPayload.GasUsed, + TimeStamp: block.Body.ExecutionPayload.TimeStamp, + ExtraData: block.Body.ExecutionPayload.ExtraData, + BaseFeePerGas: block.Body.ExecutionPayload.BaseFeePerGas, + BlockHash: block.Body.ExecutionPayload.BlockHash, + Transactions: block.Body.ExecutionPayload.Transactions, + }, + }, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to get the bellatrix fields of the capella block") + } + + withdrawals, err := convertWithdrawalsToProto(block.Body.ExecutionPayload.Withdrawals) + if err != nil { + return nil, errors.Wrap(err, "failed to get withdrawals") + } + + blsToExecutionChanges, err := convertBlsToExecutionChangesToProto(block.Body.BLSToExecutionChanges) + if err != nil { + return nil, errors.Wrap(err, "failed to get bls to execution changes") + } + + return ðpb.BeaconBlockCapella{ + Slot: bellatrixBlock.Slot, + ProposerIndex: bellatrixBlock.ProposerIndex, + ParentRoot: bellatrixBlock.ParentRoot, + StateRoot: bellatrixBlock.StateRoot, + Body: ðpb.BeaconBlockBodyCapella{ + RandaoReveal: bellatrixBlock.Body.RandaoReveal, + Eth1Data: bellatrixBlock.Body.Eth1Data, + Graffiti: bellatrixBlock.Body.Graffiti, + ProposerSlashings: bellatrixBlock.Body.ProposerSlashings, + AttesterSlashings: bellatrixBlock.Body.AttesterSlashings, + Attestations: bellatrixBlock.Body.Attestations, + Deposits: bellatrixBlock.Body.Deposits, + VoluntaryExits: bellatrixBlock.Body.VoluntaryExits, + SyncAggregate: bellatrixBlock.Body.SyncAggregate, + ExecutionPayload: &enginev1.ExecutionPayloadCapella{ + ParentHash: bellatrixBlock.Body.ExecutionPayload.ParentHash, + FeeRecipient: bellatrixBlock.Body.ExecutionPayload.FeeRecipient, + StateRoot: bellatrixBlock.Body.ExecutionPayload.StateRoot, + ReceiptsRoot: bellatrixBlock.Body.ExecutionPayload.ReceiptsRoot, + LogsBloom: bellatrixBlock.Body.ExecutionPayload.LogsBloom, + PrevRandao: bellatrixBlock.Body.ExecutionPayload.PrevRandao, + BlockNumber: bellatrixBlock.Body.ExecutionPayload.BlockNumber, + GasLimit: bellatrixBlock.Body.ExecutionPayload.GasLimit, + GasUsed: bellatrixBlock.Body.ExecutionPayload.GasUsed, + Timestamp: bellatrixBlock.Body.ExecutionPayload.Timestamp, + ExtraData: bellatrixBlock.Body.ExecutionPayload.ExtraData, + BaseFeePerGas: bellatrixBlock.Body.ExecutionPayload.BaseFeePerGas, + BlockHash: bellatrixBlock.Body.ExecutionPayload.BlockHash, + Transactions: bellatrixBlock.Body.ExecutionPayload.Transactions, + Withdrawals: withdrawals, + }, + BlsToExecutionChanges: blsToExecutionChanges, + }, + }, nil +} diff --git a/validator/client/beacon-api/beacon_block_converter_test.go b/validator/client/beacon-api/beacon_block_converter_test.go new file mode 100644 index 000000000..76327f847 --- /dev/null +++ b/validator/client/beacon-api/beacon_block_converter_test.go @@ -0,0 +1,505 @@ +package beacon_api + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + "github.com/prysmaticlabs/prysm/v3/testing/assert" + "github.com/prysmaticlabs/prysm/v3/testing/require" + test_helpers "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/test-helpers" +) + +func TestGetBeaconBlockConverter_Phase0Valid(t *testing.T) { + expectedBeaconBlock := test_helpers.GenerateProtoPhase0BeaconBlock() + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + beaconBlock, err := beaconBlockConverter.ConvertRESTPhase0BlockToProto(test_helpers.GenerateJsonPhase0BeaconBlock()) + require.NoError(t, err) + assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) +} + +func TestGetBeaconBlockConverter_Phase0Error(t *testing.T) { + testCases := []struct { + name string + expectedErrorMessage string + generateData func() *apimiddleware.BeaconBlockJson + }{ + { + name: "nil body", + expectedErrorMessage: "block body is nil", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body = nil + return beaconBlock + }, + }, + { + name: "nil eth1 data", + expectedErrorMessage: "eth1 data is nil", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Eth1Data = nil + return beaconBlock + }, + }, + { + name: "bad slot", + expectedErrorMessage: "failed to parse slot `foo`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Slot = "foo" + return beaconBlock + }, + }, + { + name: "bad proposer index", + expectedErrorMessage: "failed to parse proposer index `bar`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.ProposerIndex = "bar" + return beaconBlock + }, + }, + { + name: "bad parent root", + expectedErrorMessage: "failed to decode parent root `foo`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.ParentRoot = "foo" + return beaconBlock + }, + }, + { + name: "bad state root", + expectedErrorMessage: "failed to decode state root `bar`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.StateRoot = "bar" + return beaconBlock + }, + }, + { + name: "bad randao reveal", + expectedErrorMessage: "failed to decode randao reveal `foo`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.RandaoReveal = "foo" + return beaconBlock + }, + }, + { + name: "bad deposit root", + expectedErrorMessage: "failed to decode deposit root `bar`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Eth1Data.DepositRoot = "bar" + return beaconBlock + }, + }, + { + name: "bad deposit count", + expectedErrorMessage: "failed to parse deposit count `foo`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Eth1Data.DepositCount = "foo" + return beaconBlock + }, + }, + { + name: "bad block hash", + expectedErrorMessage: "failed to decode block hash `bar`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Eth1Data.BlockHash = "bar" + return beaconBlock + }, + }, + { + name: "bad graffiti", + expectedErrorMessage: "failed to decode graffiti `foo`", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Graffiti = "foo" + return beaconBlock + }, + }, + { + name: "bad proposer slashings", + expectedErrorMessage: "failed to get proposer slashings", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.ProposerSlashings[0] = nil + return beaconBlock + }, + }, + { + name: "bad attester slashings", + expectedErrorMessage: "failed to get attester slashings", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.AttesterSlashings[0] = nil + return beaconBlock + }, + }, + { + name: "bad attestations", + expectedErrorMessage: "failed to get attestations", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Attestations[0] = nil + return beaconBlock + }, + }, + { + name: "bad deposits", + expectedErrorMessage: "failed to get deposits", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.Deposits[0] = nil + return beaconBlock + }, + }, + { + name: "bad voluntary exits", + expectedErrorMessage: "failed to get voluntary exits", + generateData: func() *apimiddleware.BeaconBlockJson { + beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() + beaconBlock.Body.VoluntaryExits[0] = nil + return beaconBlock + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + beaconBlockJson := testCase.generateData() + + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + _, err := beaconBlockConverter.ConvertRESTPhase0BlockToProto(beaconBlockJson) + assert.ErrorContains(t, testCase.expectedErrorMessage, err) + }) + } +} + +func TestGetBeaconBlockConverter_AltairValid(t *testing.T) { + expectedBeaconBlock := test_helpers.GenerateProtoAltairBeaconBlock() + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + beaconBlock, err := beaconBlockConverter.ConvertRESTAltairBlockToProto(test_helpers.GenerateJsonAltairBeaconBlock()) + require.NoError(t, err) + assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) +} + +func TestGetBeaconBlockConverter_AltairError(t *testing.T) { + testCases := []struct { + name string + expectedErrorMessage string + generateData func() *apimiddleware.BeaconBlockAltairJson + }{ + { + name: "nil body", + expectedErrorMessage: "block body is nil", + generateData: func() *apimiddleware.BeaconBlockAltairJson { + beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() + beaconBlock.Body = nil + return beaconBlock + }, + }, + { + name: "nil sync aggregate", + expectedErrorMessage: "sync aggregate is nil", + generateData: func() *apimiddleware.BeaconBlockAltairJson { + beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() + beaconBlock.Body.SyncAggregate = nil + return beaconBlock + }, + }, + { + name: "bad phase0 fields", + expectedErrorMessage: "failed to get the phase0 fields of the altair block", + generateData: func() *apimiddleware.BeaconBlockAltairJson { + beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() + beaconBlock.Body.Eth1Data = nil + return beaconBlock + }, + }, + { + name: "bad sync committee bits", + expectedErrorMessage: "failed to decode sync committee bits `foo`", + generateData: func() *apimiddleware.BeaconBlockAltairJson { + beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() + beaconBlock.Body.SyncAggregate.SyncCommitteeBits = "foo" + return beaconBlock + }, + }, + { + name: "bad sync committee signature", + expectedErrorMessage: "failed to decode sync committee signature `bar`", + generateData: func() *apimiddleware.BeaconBlockAltairJson { + beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() + beaconBlock.Body.SyncAggregate.SyncCommitteeSignature = "bar" + return beaconBlock + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + beaconBlockJson := testCase.generateData() + + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + _, err := beaconBlockConverter.ConvertRESTAltairBlockToProto(beaconBlockJson) + assert.ErrorContains(t, testCase.expectedErrorMessage, err) + }) + } +} + +func TestGetBeaconBlockConverter_BellatrixValid(t *testing.T) { + expectedBeaconBlock := test_helpers.GenerateProtoBellatrixBeaconBlock() + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + beaconBlock, err := beaconBlockConverter.ConvertRESTBellatrixBlockToProto(test_helpers.GenerateJsonBellatrixBeaconBlock()) + require.NoError(t, err) + assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) +} + +func TestGetBeaconBlockConverter_BellatrixError(t *testing.T) { + testCases := []struct { + name string + expectedErrorMessage string + generateData func() *apimiddleware.BeaconBlockBellatrixJson + }{ + { + name: "nil body", + expectedErrorMessage: "block body is nil", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body = nil + return beaconBlock + }, + }, + { + name: "nil execution payload", + expectedErrorMessage: "execution payload is nil", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload = nil + return beaconBlock + }, + }, + { + name: "bad altair fields", + expectedErrorMessage: "failed to get the altair fields of the bellatrix block", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.Eth1Data = nil + return beaconBlock + }, + }, + { + name: "bad parent hash", + expectedErrorMessage: "failed to decode execution payload parent hash `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.ParentHash = "foo" + return beaconBlock + }, + }, + { + name: "bad fee recipient", + expectedErrorMessage: "failed to decode execution payload fee recipient `bar`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.FeeRecipient = "bar" + return beaconBlock + }, + }, + { + name: "bad state root", + expectedErrorMessage: "failed to decode execution payload state root `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.StateRoot = "foo" + return beaconBlock + }, + }, + { + name: "bad receipts root", + expectedErrorMessage: "failed to decode execution payload receipts root `bar`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.ReceiptsRoot = "bar" + return beaconBlock + }, + }, + { + name: "bad logs bloom", + expectedErrorMessage: "failed to decode execution payload logs bloom `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.LogsBloom = "foo" + return beaconBlock + }, + }, + { + name: "bad prev randao", + expectedErrorMessage: "failed to decode execution payload prev randao `bar`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.PrevRandao = "bar" + return beaconBlock + }, + }, + { + name: "bad block number", + expectedErrorMessage: "failed to parse execution payload block number `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.BlockNumber = "foo" + return beaconBlock + }, + }, + { + name: "bad gas limit", + expectedErrorMessage: "failed to parse execution payload gas limit `bar`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.GasLimit = "bar" + return beaconBlock + }, + }, + { + name: "bad gas used", + expectedErrorMessage: "failed to parse execution payload gas used `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.GasUsed = "foo" + return beaconBlock + }, + }, + { + name: "bad timestamp", + expectedErrorMessage: "failed to parse execution payload timestamp `bar`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.TimeStamp = "bar" + return beaconBlock + }, + }, + { + name: "bad extra data", + expectedErrorMessage: "failed to decode execution payload extra data `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.ExtraData = "foo" + return beaconBlock + }, + }, + { + name: "bad base fee per gas", + expectedErrorMessage: "failed to parse execution payload base fee per gas `bar`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.BaseFeePerGas = "bar" + return beaconBlock + }, + }, + { + name: "bad block hash", + expectedErrorMessage: "failed to decode execution payload block hash `foo`", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.BlockHash = "foo" + return beaconBlock + }, + }, + { + name: "bad transactions", + expectedErrorMessage: "failed to get execution payload transactions", + generateData: func() *apimiddleware.BeaconBlockBellatrixJson { + beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() + beaconBlock.Body.ExecutionPayload.Transactions[0] = "bar" + return beaconBlock + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + beaconBlockJson := testCase.generateData() + + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + _, err := beaconBlockConverter.ConvertRESTBellatrixBlockToProto(beaconBlockJson) + assert.ErrorContains(t, testCase.expectedErrorMessage, err) + }) + } +} + +func TestGetBeaconBlockConverter_CapellaValid(t *testing.T) { + expectedBeaconBlock := test_helpers.GenerateProtoCapellaBeaconBlock() + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + beaconBlock, err := beaconBlockConverter.ConvertRESTCapellaBlockToProto(test_helpers.GenerateJsonCapellaBeaconBlock()) + require.NoError(t, err) + assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) +} + +func TestGetBeaconBlockConverter_CapellaError(t *testing.T) { + testCases := []struct { + name string + expectedErrorMessage string + generateData func() *apimiddleware.BeaconBlockCapellaJson + }{ + { + name: "nil body", + expectedErrorMessage: "block body is nil", + generateData: func() *apimiddleware.BeaconBlockCapellaJson { + beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() + beaconBlock.Body = nil + return beaconBlock + }, + }, + { + name: "nil execution payload", + expectedErrorMessage: "execution payload is nil", + generateData: func() *apimiddleware.BeaconBlockCapellaJson { + beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() + beaconBlock.Body.ExecutionPayload = nil + return beaconBlock + }, + }, + { + name: "bad bellatrix fields", + expectedErrorMessage: "failed to get the bellatrix fields of the capella block", + generateData: func() *apimiddleware.BeaconBlockCapellaJson { + beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() + beaconBlock.Body.Eth1Data = nil + return beaconBlock + }, + }, + { + name: "bad withdrawals", + expectedErrorMessage: "failed to get withdrawals", + generateData: func() *apimiddleware.BeaconBlockCapellaJson { + beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() + beaconBlock.Body.ExecutionPayload.Withdrawals[0] = nil + return beaconBlock + }, + }, + { + name: "bad bls execution changes", + expectedErrorMessage: "failed to get bls to execution changes", + generateData: func() *apimiddleware.BeaconBlockCapellaJson { + beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() + beaconBlock.Body.BLSToExecutionChanges[0] = nil + return beaconBlock + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + beaconBlockJson := testCase.generateData() + + beaconBlockConverter := &beaconApiBeaconBlockConverter{} + _, err := beaconBlockConverter.ConvertRESTCapellaBlockToProto(beaconBlockJson) + assert.ErrorContains(t, testCase.expectedErrorMessage, err) + }) + } +} diff --git a/validator/client/beacon-api/get_beacon_block.go b/validator/client/beacon-api/get_beacon_block.go index cc51a37c8..05f52fa5f 100644 --- a/validator/client/beacon-api/get_beacon_block.go +++ b/validator/client/beacon-api/get_beacon_block.go @@ -5,16 +5,12 @@ import ( "context" "encoding/json" "fmt" - "math/big" neturl "net/url" - "strconv" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" - enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" ) @@ -53,11 +49,13 @@ func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primi return nil, errors.Wrap(err, "failed to decode phase0 block response json") } - phase0Block, err := convertRESTPhase0BlockToProto(&jsonPhase0Block) + phase0Block, err := c.beaconBlockConverter.ConvertRESTPhase0BlockToProto(&jsonPhase0Block) if err != nil { return nil, errors.Wrap(err, "failed to get phase0 block") } - response.Block = phase0Block + response.Block = ðpb.GenericBeaconBlock_Phase0{ + Phase0: phase0Block, + } case "altair": jsonAltairBlock := apimiddleware.BeaconBlockAltairJson{} @@ -65,11 +63,13 @@ func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primi return nil, errors.Wrap(err, "failed to decode altair block response json") } - altairBlock, err := convertRESTAltairBlockToProto(&jsonAltairBlock) + altairBlock, err := c.beaconBlockConverter.ConvertRESTAltairBlockToProto(&jsonAltairBlock) if err != nil { return nil, errors.Wrap(err, "failed to get altair block") } - response.Block = altairBlock + response.Block = ðpb.GenericBeaconBlock_Altair{ + Altair: altairBlock, + } case "bellatrix": jsonBellatrixBlock := apimiddleware.BeaconBlockBellatrixJson{} @@ -77,11 +77,13 @@ func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primi return nil, errors.Wrap(err, "failed to decode bellatrix block response json") } - bellatrixBlock, err := convertRESTBellatrixBlockToProto(&jsonBellatrixBlock) + bellatrixBlock, err := c.beaconBlockConverter.ConvertRESTBellatrixBlockToProto(&jsonBellatrixBlock) if err != nil { return nil, errors.Wrap(err, "failed to get bellatrix block") } - response.Block = bellatrixBlock + response.Block = ðpb.GenericBeaconBlock_Bellatrix{ + Bellatrix: bellatrixBlock, + } case "capella": jsonCapellaBlock := apimiddleware.BeaconBlockCapellaJson{} @@ -89,418 +91,16 @@ func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primi return nil, errors.Wrap(err, "failed to decode capella block response json") } - capellaBlock, err := convertRESTCapellaBlockToProto(&jsonCapellaBlock) + capellaBlock, err := c.beaconBlockConverter.ConvertRESTCapellaBlockToProto(&jsonCapellaBlock) if err != nil { return nil, errors.Wrap(err, "failed to get capella block") } - response.Block = capellaBlock + response.Block = ðpb.GenericBeaconBlock_Capella{ + Capella: capellaBlock, + } default: return nil, errors.Errorf("unsupported consensus version `%s`", produceBlockResponseJson.Version) } return response, nil } - -func convertRESTPhase0BlockToProto(block *apimiddleware.BeaconBlockJson) (*ethpb.GenericBeaconBlock_Phase0, error) { - blockSlot, err := strconv.ParseUint(block.Slot, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse slot `%s`", block.Slot) - } - - blockProposerIndex, err := strconv.ParseUint(block.ProposerIndex, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse proposer index `%s`", block.ProposerIndex) - } - - parentRoot, err := hexutil.Decode(block.ParentRoot) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode parent root `%s`", block.ParentRoot) - } - - stateRoot, err := hexutil.Decode(block.StateRoot) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode state root `%s`", block.StateRoot) - } - - if block.Body == nil { - return nil, errors.New("block body is nil") - } - - randaoReveal, err := hexutil.Decode(block.Body.RandaoReveal) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode randao reveal `%s`", block.Body.RandaoReveal) - } - - if block.Body.Eth1Data == nil { - return nil, errors.New("eth1 data is nil") - } - - depositRoot, err := hexutil.Decode(block.Body.Eth1Data.DepositRoot) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode deposit root `%s`", block.Body.Eth1Data.DepositRoot) - } - - depositCount, err := strconv.ParseUint(block.Body.Eth1Data.DepositCount, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse deposit count `%s`", block.Body.Eth1Data.DepositCount) - } - - blockHash, err := hexutil.Decode(block.Body.Eth1Data.BlockHash) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode block hash `%s`", block.Body.Eth1Data.BlockHash) - } - - graffiti, err := hexutil.Decode(block.Body.Graffiti) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode graffiti `%s`", block.Body.Graffiti) - } - - proposerSlashings, err := convertProposerSlashingsToProto(block.Body.ProposerSlashings) - if err != nil { - return nil, errors.Wrap(err, "failed to get proposer slashings") - } - - attesterSlashings, err := convertAttesterSlashingsToProto(block.Body.AttesterSlashings) - if err != nil { - return nil, errors.Wrap(err, "failed to get attester slashings") - } - - attestations, err := convertAttestationsToProto(block.Body.Attestations) - if err != nil { - return nil, errors.Wrap(err, "failed to get attestations") - } - - deposits, err := convertDepositsToProto(block.Body.Deposits) - if err != nil { - return nil, errors.Wrap(err, "failed to get deposits") - } - - voluntaryExits, err := convertVoluntaryExitsToProto(block.Body.VoluntaryExits) - if err != nil { - return nil, errors.Wrap(err, "failed to get voluntary exits") - } - - return ðpb.GenericBeaconBlock_Phase0{ - Phase0: ðpb.BeaconBlock{ - Slot: primitives.Slot(blockSlot), - ProposerIndex: primitives.ValidatorIndex(blockProposerIndex), - ParentRoot: parentRoot, - StateRoot: stateRoot, - Body: ðpb.BeaconBlockBody{ - RandaoReveal: randaoReveal, - Eth1Data: ðpb.Eth1Data{ - DepositRoot: depositRoot, - DepositCount: depositCount, - BlockHash: blockHash, - }, - Graffiti: graffiti, - ProposerSlashings: proposerSlashings, - AttesterSlashings: attesterSlashings, - Attestations: attestations, - Deposits: deposits, - VoluntaryExits: voluntaryExits, - }, - }, - }, nil -} - -func convertRESTAltairBlockToProto(block *apimiddleware.BeaconBlockAltairJson) (*ethpb.GenericBeaconBlock_Altair, error) { - if block.Body == nil { - return nil, errors.New("block body is nil") - } - - // Call convertRESTPhase0BlockToProto to set the phase0 fields because all the error handling and the heavy lifting - // has already been done - phase0Block, err := convertRESTPhase0BlockToProto(&apimiddleware.BeaconBlockJson{ - Slot: block.Slot, - ProposerIndex: block.ProposerIndex, - ParentRoot: block.ParentRoot, - StateRoot: block.StateRoot, - Body: &apimiddleware.BeaconBlockBodyJson{ - RandaoReveal: block.Body.RandaoReveal, - Eth1Data: block.Body.Eth1Data, - Graffiti: block.Body.Graffiti, - ProposerSlashings: block.Body.ProposerSlashings, - AttesterSlashings: block.Body.AttesterSlashings, - Attestations: block.Body.Attestations, - Deposits: block.Body.Deposits, - VoluntaryExits: block.Body.VoluntaryExits, - }, - }) - if err != nil { - return nil, errors.Wrap(err, "failed to get the phase0 fields of the altair block") - } - - if block.Body.SyncAggregate == nil { - return nil, errors.New("sync aggregate is nil") - } - - syncCommitteeBits, err := hexutil.Decode(block.Body.SyncAggregate.SyncCommitteeBits) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode sync committee bits `%s`", block.Body.SyncAggregate.SyncCommitteeBits) - } - - syncCommitteeSignature, err := hexutil.Decode(block.Body.SyncAggregate.SyncCommitteeSignature) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode sync committee signature `%s`", block.Body.SyncAggregate.SyncCommitteeSignature) - } - - return ðpb.GenericBeaconBlock_Altair{ - Altair: ðpb.BeaconBlockAltair{ - Slot: phase0Block.Phase0.Slot, - ProposerIndex: phase0Block.Phase0.ProposerIndex, - ParentRoot: phase0Block.Phase0.ParentRoot, - StateRoot: phase0Block.Phase0.StateRoot, - Body: ðpb.BeaconBlockBodyAltair{ - RandaoReveal: phase0Block.Phase0.Body.RandaoReveal, - Eth1Data: phase0Block.Phase0.Body.Eth1Data, - Graffiti: phase0Block.Phase0.Body.Graffiti, - ProposerSlashings: phase0Block.Phase0.Body.ProposerSlashings, - AttesterSlashings: phase0Block.Phase0.Body.AttesterSlashings, - Attestations: phase0Block.Phase0.Body.Attestations, - Deposits: phase0Block.Phase0.Body.Deposits, - VoluntaryExits: phase0Block.Phase0.Body.VoluntaryExits, - SyncAggregate: ðpb.SyncAggregate{ - SyncCommitteeBits: syncCommitteeBits, - SyncCommitteeSignature: syncCommitteeSignature, - }, - }, - }, - }, nil -} - -func convertRESTBellatrixBlockToProto(block *apimiddleware.BeaconBlockBellatrixJson) (*ethpb.GenericBeaconBlock_Bellatrix, error) { - if block.Body == nil { - return nil, errors.New("block body is nil") - } - - // Call convertRESTAltairBlockToProto to set the altair fields because all the error handling and the heavy lifting - // has already been done - altairBlock, err := convertRESTAltairBlockToProto(&apimiddleware.BeaconBlockAltairJson{ - Slot: block.Slot, - ProposerIndex: block.ProposerIndex, - ParentRoot: block.ParentRoot, - StateRoot: block.StateRoot, - Body: &apimiddleware.BeaconBlockBodyAltairJson{ - RandaoReveal: block.Body.RandaoReveal, - Eth1Data: block.Body.Eth1Data, - Graffiti: block.Body.Graffiti, - ProposerSlashings: block.Body.ProposerSlashings, - AttesterSlashings: block.Body.AttesterSlashings, - Attestations: block.Body.Attestations, - Deposits: block.Body.Deposits, - VoluntaryExits: block.Body.VoluntaryExits, - SyncAggregate: block.Body.SyncAggregate, - }, - }) - if err != nil { - return nil, errors.Wrap(err, "failed to get the altair fields of the bellatrix block") - } - - if block.Body.ExecutionPayload == nil { - return nil, errors.New("execution payload is nil") - } - - parentHash, err := hexutil.Decode(block.Body.ExecutionPayload.ParentHash) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload parent hash `%s`", block.Body.ExecutionPayload.ParentHash) - } - - feeRecipient, err := hexutil.Decode(block.Body.ExecutionPayload.FeeRecipient) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload fee recipient `%s`", block.Body.ExecutionPayload.FeeRecipient) - } - - stateRoot, err := hexutil.Decode(block.Body.ExecutionPayload.StateRoot) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload state root `%s`", block.Body.ExecutionPayload.StateRoot) - } - - receiptsRoot, err := hexutil.Decode(block.Body.ExecutionPayload.ReceiptsRoot) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload receipts root `%s`", block.Body.ExecutionPayload.ReceiptsRoot) - } - - logsBloom, err := hexutil.Decode(block.Body.ExecutionPayload.LogsBloom) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload logs bloom `%s`", block.Body.ExecutionPayload.LogsBloom) - } - - prevRandao, err := hexutil.Decode(block.Body.ExecutionPayload.PrevRandao) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload prev randao `%s`", block.Body.ExecutionPayload.PrevRandao) - } - - blockNumber, err := strconv.ParseUint(block.Body.ExecutionPayload.BlockNumber, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse execution payload block number `%s`", block.Body.ExecutionPayload.BlockNumber) - } - - gasLimit, err := strconv.ParseUint(block.Body.ExecutionPayload.GasLimit, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse execution payload gas limit `%s`", block.Body.ExecutionPayload.GasLimit) - } - - gasUsed, err := strconv.ParseUint(block.Body.ExecutionPayload.GasUsed, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse execution payload gas used `%s`", block.Body.ExecutionPayload.GasUsed) - } - - timestamp, err := strconv.ParseUint(block.Body.ExecutionPayload.TimeStamp, 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse execution payload timestamp `%s`", block.Body.ExecutionPayload.TimeStamp) - } - - extraData, err := hexutil.Decode(block.Body.ExecutionPayload.ExtraData) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload extra data `%s`", block.Body.ExecutionPayload.ExtraData) - } - - baseFeePerGas := new(big.Int) - if _, ok := baseFeePerGas.SetString(block.Body.ExecutionPayload.BaseFeePerGas, 10); !ok { - return nil, errors.Errorf("failed to parse execution payload base fee per gas `%s`", block.Body.ExecutionPayload.BaseFeePerGas) - } - - blockHash, err := hexutil.Decode(block.Body.ExecutionPayload.BlockHash) - if err != nil { - return nil, errors.Wrapf(err, "failed to decode execution payload block hash `%s`", block.Body.ExecutionPayload.BlockHash) - } - - transactions, err := convertTransactionsToProto(block.Body.ExecutionPayload.Transactions) - if err != nil { - return nil, errors.Wrap(err, "failed to get execution payload transactions") - } - - return ðpb.GenericBeaconBlock_Bellatrix{ - Bellatrix: ðpb.BeaconBlockBellatrix{ - Slot: altairBlock.Altair.Slot, - ProposerIndex: altairBlock.Altair.ProposerIndex, - ParentRoot: altairBlock.Altair.ParentRoot, - StateRoot: altairBlock.Altair.StateRoot, - Body: ðpb.BeaconBlockBodyBellatrix{ - RandaoReveal: altairBlock.Altair.Body.RandaoReveal, - Eth1Data: altairBlock.Altair.Body.Eth1Data, - Graffiti: altairBlock.Altair.Body.Graffiti, - ProposerSlashings: altairBlock.Altair.Body.ProposerSlashings, - AttesterSlashings: altairBlock.Altair.Body.AttesterSlashings, - Attestations: altairBlock.Altair.Body.Attestations, - Deposits: altairBlock.Altair.Body.Deposits, - VoluntaryExits: altairBlock.Altair.Body.VoluntaryExits, - SyncAggregate: altairBlock.Altair.Body.SyncAggregate, - ExecutionPayload: &enginev1.ExecutionPayload{ - ParentHash: parentHash, - FeeRecipient: feeRecipient, - StateRoot: stateRoot, - ReceiptsRoot: receiptsRoot, - LogsBloom: logsBloom, - PrevRandao: prevRandao, - BlockNumber: blockNumber, - GasLimit: gasLimit, - GasUsed: gasUsed, - Timestamp: timestamp, - ExtraData: extraData, - BaseFeePerGas: bytesutil.PadTo(bytesutil.BigIntToLittleEndianBytes(baseFeePerGas), 32), - BlockHash: blockHash, - Transactions: transactions, - }, - }, - }, - }, nil -} - -func convertRESTCapellaBlockToProto(block *apimiddleware.BeaconBlockCapellaJson) (*ethpb.GenericBeaconBlock_Capella, error) { - if block.Body == nil { - return nil, errors.New("block body is nil") - } - - if block.Body.ExecutionPayload == nil { - return nil, errors.New("execution payload is nil") - } - - // Call convertRESTBellatrixBlockToProto to set the bellatrix fields because all the error handling and the heavy - // lifting has already been done - bellatrixBlock, err := convertRESTBellatrixBlockToProto(&apimiddleware.BeaconBlockBellatrixJson{ - Slot: block.Slot, - ProposerIndex: block.ProposerIndex, - ParentRoot: block.ParentRoot, - StateRoot: block.StateRoot, - Body: &apimiddleware.BeaconBlockBodyBellatrixJson{ - RandaoReveal: block.Body.RandaoReveal, - Eth1Data: block.Body.Eth1Data, - Graffiti: block.Body.Graffiti, - ProposerSlashings: block.Body.ProposerSlashings, - AttesterSlashings: block.Body.AttesterSlashings, - Attestations: block.Body.Attestations, - Deposits: block.Body.Deposits, - VoluntaryExits: block.Body.VoluntaryExits, - SyncAggregate: block.Body.SyncAggregate, - ExecutionPayload: &apimiddleware.ExecutionPayloadJson{ - ParentHash: block.Body.ExecutionPayload.ParentHash, - FeeRecipient: block.Body.ExecutionPayload.FeeRecipient, - StateRoot: block.Body.ExecutionPayload.StateRoot, - ReceiptsRoot: block.Body.ExecutionPayload.ReceiptsRoot, - LogsBloom: block.Body.ExecutionPayload.LogsBloom, - PrevRandao: block.Body.ExecutionPayload.PrevRandao, - BlockNumber: block.Body.ExecutionPayload.BlockNumber, - GasLimit: block.Body.ExecutionPayload.GasLimit, - GasUsed: block.Body.ExecutionPayload.GasUsed, - TimeStamp: block.Body.ExecutionPayload.TimeStamp, - ExtraData: block.Body.ExecutionPayload.ExtraData, - BaseFeePerGas: block.Body.ExecutionPayload.BaseFeePerGas, - BlockHash: block.Body.ExecutionPayload.BlockHash, - Transactions: block.Body.ExecutionPayload.Transactions, - }, - }, - }) - if err != nil { - return nil, errors.Wrap(err, "failed to get the bellatrix fields of the capella block") - } - - withdrawals, err := convertWithdrawalsToProto(block.Body.ExecutionPayload.Withdrawals) - if err != nil { - return nil, errors.Wrap(err, "failed to get withdrawals") - } - - blsToExecutionChanges, err := convertBlsToExecutionChangesToProto(block.Body.BLSToExecutionChanges) - if err != nil { - return nil, errors.Wrap(err, "failed to get bls to execution changes") - } - - return ðpb.GenericBeaconBlock_Capella{ - Capella: ðpb.BeaconBlockCapella{ - Slot: bellatrixBlock.Bellatrix.Slot, - ProposerIndex: bellatrixBlock.Bellatrix.ProposerIndex, - ParentRoot: bellatrixBlock.Bellatrix.ParentRoot, - StateRoot: bellatrixBlock.Bellatrix.StateRoot, - Body: ðpb.BeaconBlockBodyCapella{ - RandaoReveal: bellatrixBlock.Bellatrix.Body.RandaoReveal, - Eth1Data: bellatrixBlock.Bellatrix.Body.Eth1Data, - Graffiti: bellatrixBlock.Bellatrix.Body.Graffiti, - ProposerSlashings: bellatrixBlock.Bellatrix.Body.ProposerSlashings, - AttesterSlashings: bellatrixBlock.Bellatrix.Body.AttesterSlashings, - Attestations: bellatrixBlock.Bellatrix.Body.Attestations, - Deposits: bellatrixBlock.Bellatrix.Body.Deposits, - VoluntaryExits: bellatrixBlock.Bellatrix.Body.VoluntaryExits, - SyncAggregate: bellatrixBlock.Bellatrix.Body.SyncAggregate, - ExecutionPayload: &enginev1.ExecutionPayloadCapella{ - ParentHash: bellatrixBlock.Bellatrix.Body.ExecutionPayload.ParentHash, - FeeRecipient: bellatrixBlock.Bellatrix.Body.ExecutionPayload.FeeRecipient, - StateRoot: bellatrixBlock.Bellatrix.Body.ExecutionPayload.StateRoot, - ReceiptsRoot: bellatrixBlock.Bellatrix.Body.ExecutionPayload.ReceiptsRoot, - LogsBloom: bellatrixBlock.Bellatrix.Body.ExecutionPayload.LogsBloom, - PrevRandao: bellatrixBlock.Bellatrix.Body.ExecutionPayload.PrevRandao, - BlockNumber: bellatrixBlock.Bellatrix.Body.ExecutionPayload.BlockNumber, - GasLimit: bellatrixBlock.Bellatrix.Body.ExecutionPayload.GasLimit, - GasUsed: bellatrixBlock.Bellatrix.Body.ExecutionPayload.GasUsed, - Timestamp: bellatrixBlock.Bellatrix.Body.ExecutionPayload.Timestamp, - ExtraData: bellatrixBlock.Bellatrix.Body.ExecutionPayload.ExtraData, - BaseFeePerGas: bellatrixBlock.Bellatrix.Body.ExecutionPayload.BaseFeePerGas, - BlockHash: bellatrixBlock.Bellatrix.Body.ExecutionPayload.BlockHash, - Transactions: bellatrixBlock.Bellatrix.Body.ExecutionPayload.Transactions, - Withdrawals: withdrawals, - }, - BlsToExecutionChanges: blsToExecutionChanges, - }, - }, - }, nil -} diff --git a/validator/client/beacon-api/get_beacon_block_altair_test.go b/validator/client/beacon-api/get_beacon_block_altair_test.go deleted file mode 100644 index 824d3208d..000000000 --- a/validator/client/beacon-api/get_beacon_block_altair_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package beacon_api - -import ( - "context" - "encoding/json" - "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_AltairValid(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - altairProtoBeaconBlock := test_helpers.GenerateProtoAltairBeaconBlock() - altairBeaconBlockBytes, err := json.Marshal(test_helpers.GenerateJsonAltairBeaconBlock()) - 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) - - expectedBeaconBlock := generateProtoAltairBlock(altairProtoBeaconBlock) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) - require.NoError(t, err) - assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) -} - -func TestGetBeaconBlock_AltairError(t *testing.T) { - testCases := []struct { - name string - expectedErrorMessage string - generateData func() *apimiddleware.BeaconBlockAltairJson - }{ - { - name: "nil body", - expectedErrorMessage: "block body is nil", - generateData: func() *apimiddleware.BeaconBlockAltairJson { - beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() - beaconBlock.Body = nil - return beaconBlock - }, - }, - { - name: "nil sync aggregate", - expectedErrorMessage: "sync aggregate is nil", - generateData: func() *apimiddleware.BeaconBlockAltairJson { - beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() - beaconBlock.Body.SyncAggregate = nil - return beaconBlock - }, - }, - { - name: "bad phase0 fields", - expectedErrorMessage: "failed to get the phase0 fields of the altair block", - generateData: func() *apimiddleware.BeaconBlockAltairJson { - beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() - beaconBlock.Body.Eth1Data = nil - return beaconBlock - }, - }, - { - name: "bad sync committee bits", - expectedErrorMessage: "failed to decode sync committee bits `foo`", - generateData: func() *apimiddleware.BeaconBlockAltairJson { - beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() - beaconBlock.Body.SyncAggregate.SyncCommitteeBits = "foo" - return beaconBlock - }, - }, - { - name: "bad sync committee signature", - expectedErrorMessage: "failed to decode sync committee signature `bar`", - generateData: func() *apimiddleware.BeaconBlockAltairJson { - beaconBlock := test_helpers.GenerateJsonAltairBeaconBlock() - beaconBlock.Body.SyncAggregate.SyncCommitteeSignature = "bar" - return beaconBlock - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - dataBytes, err := json.Marshal(testCase.generateData()) - require.NoError(t, err) - - ctx := context.Background() - - jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) - jsonRestHandler.EXPECT().GetRestJsonResponse( - ctx, - gomock.Any(), - &abstractProduceBlockResponseJson{}, - ).SetArg( - 2, - abstractProduceBlockResponseJson{ - Version: "altair", - Data: dataBytes, - }, - ).Return( - nil, - nil, - ).Times(1) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - _, err = validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) - assert.ErrorContains(t, "failed to get altair block", err) - assert.ErrorContains(t, testCase.expectedErrorMessage, err) - }) - } -} - -func generateProtoAltairBlock(altairProtoBeaconBlock *ethpb.BeaconBlockAltair) *ethpb.GenericBeaconBlock { - return ðpb.GenericBeaconBlock{ - Block: ðpb.GenericBeaconBlock_Altair{ - Altair: altairProtoBeaconBlock, - }, - } -} diff --git a/validator/client/beacon-api/get_beacon_block_bellatrix_test.go b/validator/client/beacon-api/get_beacon_block_bellatrix_test.go deleted file mode 100644 index 855bcf13f..000000000 --- a/validator/client/beacon-api/get_beacon_block_bellatrix_test.go +++ /dev/null @@ -1,259 +0,0 @@ -package beacon_api - -import ( - "context" - "encoding/json" - "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_BellatrixValid(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - bellatrixProtoBeaconBlock := test_helpers.GenerateProtoBellatrixBeaconBlock() - bellatrixBeaconBlockBytes, err := json.Marshal(test_helpers.GenerateJsonBellatrixBeaconBlock()) - 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) - - expectedBeaconBlock := generateProtoBellatrixBlock(bellatrixProtoBeaconBlock) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) - require.NoError(t, err) - assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) -} - -func TestGetBeaconBlock_BellatrixError(t *testing.T) { - testCases := []struct { - name string - expectedErrorMessage string - generateData func() *apimiddleware.BeaconBlockBellatrixJson - }{ - { - name: "nil body", - expectedErrorMessage: "block body is nil", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body = nil - return beaconBlock - }, - }, - { - name: "nil execution payload", - expectedErrorMessage: "execution payload is nil", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload = nil - return beaconBlock - }, - }, - { - name: "bad altair fields", - expectedErrorMessage: "failed to get the altair fields of the bellatrix block", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.Eth1Data = nil - return beaconBlock - }, - }, - { - name: "bad parent hash", - expectedErrorMessage: "failed to decode execution payload parent hash `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.ParentHash = "foo" - return beaconBlock - }, - }, - { - name: "bad fee recipient", - expectedErrorMessage: "failed to decode execution payload fee recipient `bar`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.FeeRecipient = "bar" - return beaconBlock - }, - }, - { - name: "bad state root", - expectedErrorMessage: "failed to decode execution payload state root `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.StateRoot = "foo" - return beaconBlock - }, - }, - { - name: "bad receipts root", - expectedErrorMessage: "failed to decode execution payload receipts root `bar`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.ReceiptsRoot = "bar" - return beaconBlock - }, - }, - { - name: "bad logs bloom", - expectedErrorMessage: "failed to decode execution payload logs bloom `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.LogsBloom = "foo" - return beaconBlock - }, - }, - { - name: "bad prev randao", - expectedErrorMessage: "failed to decode execution payload prev randao `bar`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.PrevRandao = "bar" - return beaconBlock - }, - }, - { - name: "bad block number", - expectedErrorMessage: "failed to parse execution payload block number `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.BlockNumber = "foo" - return beaconBlock - }, - }, - { - name: "bad gas limit", - expectedErrorMessage: "failed to parse execution payload gas limit `bar`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.GasLimit = "bar" - return beaconBlock - }, - }, - { - name: "bad gas used", - expectedErrorMessage: "failed to parse execution payload gas used `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.GasUsed = "foo" - return beaconBlock - }, - }, - { - name: "bad timestamp", - expectedErrorMessage: "failed to parse execution payload timestamp `bar`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.TimeStamp = "bar" - return beaconBlock - }, - }, - { - name: "bad extra data", - expectedErrorMessage: "failed to decode execution payload extra data `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.ExtraData = "foo" - return beaconBlock - }, - }, - { - name: "bad base fee per gas", - expectedErrorMessage: "failed to parse execution payload base fee per gas `bar`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.BaseFeePerGas = "bar" - return beaconBlock - }, - }, - { - name: "bad block hash", - expectedErrorMessage: "failed to decode execution payload block hash `foo`", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.BlockHash = "foo" - return beaconBlock - }, - }, - { - name: "bad transactions", - expectedErrorMessage: "failed to get execution payload transactions", - generateData: func() *apimiddleware.BeaconBlockBellatrixJson { - beaconBlock := test_helpers.GenerateJsonBellatrixBeaconBlock() - beaconBlock.Body.ExecutionPayload.Transactions[0] = "bar" - return beaconBlock - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ctx := context.Background() - - dataBytes, err := json.Marshal(testCase.generateData()) - require.NoError(t, err) - - jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) - jsonRestHandler.EXPECT().GetRestJsonResponse( - ctx, - gomock.Any(), - &abstractProduceBlockResponseJson{}, - ).SetArg( - 2, - abstractProduceBlockResponseJson{ - Version: "bellatrix", - Data: dataBytes, - }, - ).Return( - nil, - nil, - ).Times(1) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - _, err = validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) - assert.ErrorContains(t, "failed to get bellatrix block", err) - assert.ErrorContains(t, testCase.expectedErrorMessage, err) - }) - } -} - -func generateProtoBellatrixBlock(bellatrixProtoBeaconBlock *ethpb.BeaconBlockBellatrix) *ethpb.GenericBeaconBlock { - return ðpb.GenericBeaconBlock{ - Block: ðpb.GenericBeaconBlock_Bellatrix{ - Bellatrix: bellatrixProtoBeaconBlock, - }, - } -} diff --git a/validator/client/beacon-api/get_beacon_block_capella_test.go b/validator/client/beacon-api/get_beacon_block_capella_test.go deleted file mode 100644 index 841c99528..000000000 --- a/validator/client/beacon-api/get_beacon_block_capella_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package beacon_api - -import ( - "context" - "encoding/json" - "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_CapellaValid(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - capellaProtoBeaconBlock := test_helpers.GenerateProtoCapellaBeaconBlock() - capellaBeaconBlockBytes, err := json.Marshal(test_helpers.GenerateJsonCapellaBeaconBlock()) - 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) - - expectedBeaconBlock := generateProtoCapellaBlock(capellaProtoBeaconBlock) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) - require.NoError(t, err) - assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) -} - -func TestGetBeaconBlock_CapellaError(t *testing.T) { - testCases := []struct { - name string - expectedErrorMessage string - generateData func() *apimiddleware.BeaconBlockCapellaJson - }{ - { - name: "nil body", - expectedErrorMessage: "block body is nil", - generateData: func() *apimiddleware.BeaconBlockCapellaJson { - beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() - beaconBlock.Body = nil - return beaconBlock - }, - }, - { - name: "nil execution payload", - expectedErrorMessage: "execution payload is nil", - generateData: func() *apimiddleware.BeaconBlockCapellaJson { - beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() - beaconBlock.Body.ExecutionPayload = nil - return beaconBlock - }, - }, - { - name: "bad bellatrix fields", - expectedErrorMessage: "failed to get the bellatrix fields of the capella block", - generateData: func() *apimiddleware.BeaconBlockCapellaJson { - beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() - beaconBlock.Body.Eth1Data = nil - return beaconBlock - }, - }, - { - name: "bad withdrawals", - expectedErrorMessage: "failed to get withdrawals", - generateData: func() *apimiddleware.BeaconBlockCapellaJson { - beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() - beaconBlock.Body.ExecutionPayload.Withdrawals[0] = nil - return beaconBlock - }, - }, - { - name: "bad bls execution changes", - expectedErrorMessage: "failed to get bls to execution changes", - generateData: func() *apimiddleware.BeaconBlockCapellaJson { - beaconBlock := test_helpers.GenerateJsonCapellaBeaconBlock() - beaconBlock.Body.BLSToExecutionChanges[0] = nil - return beaconBlock - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - dataBytes, err := json.Marshal(testCase.generateData()) - require.NoError(t, err) - - jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) - jsonRestHandler.EXPECT().GetRestJsonResponse( - context.Background(), - gomock.Any(), - &abstractProduceBlockResponseJson{}, - ).SetArg( - 2, - abstractProduceBlockResponseJson{ - Version: "capella", - Data: dataBytes, - }, - ).Return( - nil, - nil, - ).Times(1) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - _, err = validatorClient.getBeaconBlock(context.Background(), 1, []byte{1}, []byte{2}) - assert.ErrorContains(t, "failed to get capella block", err) - assert.ErrorContains(t, testCase.expectedErrorMessage, err) - }) - } -} - -func generateProtoCapellaBlock(capellaProtoBeaconBlock *ethpb.BeaconBlockCapella) *ethpb.GenericBeaconBlock { - return ðpb.GenericBeaconBlock{ - Block: ðpb.GenericBeaconBlock_Capella{ - Capella: capellaProtoBeaconBlock, - }, - } -} diff --git a/validator/client/beacon-api/get_beacon_block_phase0_test.go b/validator/client/beacon-api/get_beacon_block_phase0_test.go deleted file mode 100644 index 9d5422b4f..000000000 --- a/validator/client/beacon-api/get_beacon_block_phase0_test.go +++ /dev/null @@ -1,249 +0,0 @@ -package beacon_api - -import ( - "context" - "encoding/json" - "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_Phase0Valid(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - phase0ProtoBeaconBlock := test_helpers.GenerateProtoPhase0BeaconBlock() - phase0BeaconBlockBytes, err := json.Marshal(test_helpers.GenerateJsonPhase0BeaconBlock()) - 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) - - expectedBeaconBlock := generateProtoPhase0Block(phase0ProtoBeaconBlock) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) - require.NoError(t, err) - assert.DeepEqual(t, expectedBeaconBlock, beaconBlock) -} - -func TestGetBeaconBlock_Phase0Error(t *testing.T) { - testCases := []struct { - name string - expectedErrorMessage string - generateData func() *apimiddleware.BeaconBlockJson - }{ - { - name: "nil body", - expectedErrorMessage: "block body is nil", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body = nil - return beaconBlock - }, - }, - { - name: "nil eth1 data", - expectedErrorMessage: "eth1 data is nil", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Eth1Data = nil - return beaconBlock - }, - }, - { - name: "bad slot", - expectedErrorMessage: "failed to parse slot `foo`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Slot = "foo" - return beaconBlock - }, - }, - { - name: "bad proposer index", - expectedErrorMessage: "failed to parse proposer index `bar`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.ProposerIndex = "bar" - return beaconBlock - }, - }, - { - name: "bad parent root", - expectedErrorMessage: "failed to decode parent root `foo`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.ParentRoot = "foo" - return beaconBlock - }, - }, - { - name: "bad state root", - expectedErrorMessage: "failed to decode state root `bar`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.StateRoot = "bar" - return beaconBlock - }, - }, - { - name: "bad randao reveal", - expectedErrorMessage: "failed to decode randao reveal `foo`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.RandaoReveal = "foo" - return beaconBlock - }, - }, - { - name: "bad deposit root", - expectedErrorMessage: "failed to decode deposit root `bar`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Eth1Data.DepositRoot = "bar" - return beaconBlock - }, - }, - { - name: "bad deposit count", - expectedErrorMessage: "failed to parse deposit count `foo`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Eth1Data.DepositCount = "foo" - return beaconBlock - }, - }, - { - name: "bad block hash", - expectedErrorMessage: "failed to decode block hash `bar`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Eth1Data.BlockHash = "bar" - return beaconBlock - }, - }, - { - name: "bad graffiti", - expectedErrorMessage: "failed to decode graffiti `foo`", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Graffiti = "foo" - return beaconBlock - }, - }, - { - name: "bad proposer slashings", - expectedErrorMessage: "failed to get proposer slashings", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.ProposerSlashings[0] = nil - return beaconBlock - }, - }, - { - name: "bad attester slashings", - expectedErrorMessage: "failed to get attester slashings", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.AttesterSlashings[0] = nil - return beaconBlock - }, - }, - { - name: "bad attestations", - expectedErrorMessage: "failed to get attestations", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Attestations[0] = nil - return beaconBlock - }, - }, - { - name: "bad deposits", - expectedErrorMessage: "failed to get deposits", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.Deposits[0] = nil - return beaconBlock - }, - }, - { - name: "bad voluntary exits", - expectedErrorMessage: "failed to get voluntary exits", - generateData: func() *apimiddleware.BeaconBlockJson { - beaconBlock := test_helpers.GenerateJsonPhase0BeaconBlock() - beaconBlock.Body.VoluntaryExits[0] = nil - return beaconBlock - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - dataBytes, err := json.Marshal(testCase.generateData()) - require.NoError(t, err) - - ctx := context.Background() - - jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) - jsonRestHandler.EXPECT().GetRestJsonResponse( - ctx, - gomock.Any(), - &abstractProduceBlockResponseJson{}, - ).SetArg( - 2, - abstractProduceBlockResponseJson{ - Version: "phase0", - Data: dataBytes, - }, - ).Return( - nil, - nil, - ).Times(1) - - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - _, err = validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) - assert.ErrorContains(t, "failed to get phase0 block", err) - assert.ErrorContains(t, testCase.expectedErrorMessage, err) - }) - } -} - -func generateProtoPhase0Block(phase0ProtoBeaconBlock *ethpb.BeaconBlock) *ethpb.GenericBeaconBlock { - return ðpb.GenericBeaconBlock{ - Block: ðpb.GenericBeaconBlock_Phase0{ - Phase0: phase0ProtoBeaconBlock, - }, - } -} diff --git a/validator/client/beacon-api/get_beacon_block_test.go b/validator/client/beacon-api/get_beacon_block_test.go index 9315aac18..fedd13447 100644 --- a/validator/client/beacon-api/get_beacon_block_test.go +++ b/validator/client/beacon-api/get_beacon_block_test.go @@ -4,13 +4,18 @@ 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) { @@ -132,9 +137,245 @@ func TestGetBeaconBlock_Error(t *testing.T) { nil, ).Times(1) - validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + 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) +} diff --git a/validator/client/beacon-api/mock/BUILD.bazel b/validator/client/beacon-api/mock/BUILD.bazel index 614cde870..2c8045d31 100644 --- a/validator/client/beacon-api/mock/BUILD.bazel +++ b/validator/client/beacon-api/mock/BUILD.bazel @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ + "beacon_block_converter_mock.go", "duties_mock.go", "genesis_mock.go", "json_rest_handler_mock.go", @@ -14,6 +15,7 @@ go_library( "//api/gateway/apimiddleware:go_default_library", "//beacon-chain/rpc/apimiddleware:go_default_library", "//consensus-types/primitives:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", "@com_github_golang_mock//gomock:go_default_library", ], ) diff --git a/validator/client/beacon-api/mock/beacon_block_converter_mock.go b/validator/client/beacon-api/mock/beacon_block_converter_mock.go new file mode 100644 index 000000000..3d2721032 --- /dev/null +++ b/validator/client/beacon-api/mock/beacon_block_converter_mock.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: validator/client/beacon-api/beacon_block_converter.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + apimiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + eth "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" +) + +// MockbeaconBlockConverter is a mock of beaconBlockConverter interface. +type MockbeaconBlockConverter struct { + ctrl *gomock.Controller + recorder *MockbeaconBlockConverterMockRecorder +} + +// MockbeaconBlockConverterMockRecorder is the mock recorder for MockbeaconBlockConverter. +type MockbeaconBlockConverterMockRecorder struct { + mock *MockbeaconBlockConverter +} + +// NewMockbeaconBlockConverter creates a new mock instance. +func NewMockbeaconBlockConverter(ctrl *gomock.Controller) *MockbeaconBlockConverter { + mock := &MockbeaconBlockConverter{ctrl: ctrl} + mock.recorder = &MockbeaconBlockConverterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockbeaconBlockConverter) EXPECT() *MockbeaconBlockConverterMockRecorder { + return m.recorder +} + +// ConvertRESTAltairBlockToProto mocks base method. +func (m *MockbeaconBlockConverter) ConvertRESTAltairBlockToProto(block *apimiddleware.BeaconBlockAltairJson) (*eth.BeaconBlockAltair, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConvertRESTAltairBlockToProto", block) + ret0, _ := ret[0].(*eth.BeaconBlockAltair) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConvertRESTAltairBlockToProto indicates an expected call of ConvertRESTAltairBlockToProto. +func (mr *MockbeaconBlockConverterMockRecorder) ConvertRESTAltairBlockToProto(block interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConvertRESTAltairBlockToProto", reflect.TypeOf((*MockbeaconBlockConverter)(nil).ConvertRESTAltairBlockToProto), block) +} + +// ConvertRESTBellatrixBlockToProto mocks base method. +func (m *MockbeaconBlockConverter) ConvertRESTBellatrixBlockToProto(block *apimiddleware.BeaconBlockBellatrixJson) (*eth.BeaconBlockBellatrix, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConvertRESTBellatrixBlockToProto", block) + ret0, _ := ret[0].(*eth.BeaconBlockBellatrix) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConvertRESTBellatrixBlockToProto indicates an expected call of ConvertRESTBellatrixBlockToProto. +func (mr *MockbeaconBlockConverterMockRecorder) ConvertRESTBellatrixBlockToProto(block interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConvertRESTBellatrixBlockToProto", reflect.TypeOf((*MockbeaconBlockConverter)(nil).ConvertRESTBellatrixBlockToProto), block) +} + +// ConvertRESTCapellaBlockToProto mocks base method. +func (m *MockbeaconBlockConverter) ConvertRESTCapellaBlockToProto(block *apimiddleware.BeaconBlockCapellaJson) (*eth.BeaconBlockCapella, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConvertRESTCapellaBlockToProto", block) + ret0, _ := ret[0].(*eth.BeaconBlockCapella) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConvertRESTCapellaBlockToProto indicates an expected call of ConvertRESTCapellaBlockToProto. +func (mr *MockbeaconBlockConverterMockRecorder) ConvertRESTCapellaBlockToProto(block interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConvertRESTCapellaBlockToProto", reflect.TypeOf((*MockbeaconBlockConverter)(nil).ConvertRESTCapellaBlockToProto), block) +} + +// ConvertRESTPhase0BlockToProto mocks base method. +func (m *MockbeaconBlockConverter) ConvertRESTPhase0BlockToProto(block *apimiddleware.BeaconBlockJson) (*eth.BeaconBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConvertRESTPhase0BlockToProto", block) + ret0, _ := ret[0].(*eth.BeaconBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConvertRESTPhase0BlockToProto indicates an expected call of ConvertRESTPhase0BlockToProto. +func (mr *MockbeaconBlockConverterMockRecorder) ConvertRESTPhase0BlockToProto(block interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConvertRESTPhase0BlockToProto", reflect.TypeOf((*MockbeaconBlockConverter)(nil).ConvertRESTPhase0BlockToProto), block) +} diff --git a/validator/client/beacon-api/mock/duties_mock.go b/validator/client/beacon-api/mock/duties_mock.go index 3a82e2ce1..b9bc58760 100644 --- a/validator/client/beacon-api/mock/duties_mock.go +++ b/validator/client/beacon-api/mock/duties_mock.go @@ -10,7 +10,7 @@ import ( gomock "github.com/golang/mock/gomock" apimiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" + primitives "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" ) // MockdutiesProvider is a mock of dutiesProvider interface. @@ -37,7 +37,7 @@ func (m *MockdutiesProvider) EXPECT() *MockdutiesProviderMockRecorder { } // GetAttesterDuties mocks base method. -func (m *MockdutiesProvider) GetAttesterDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.AttesterDutyJson, error) { +func (m *MockdutiesProvider) GetAttesterDuties(ctx context.Context, epoch primitives.Epoch, validatorIndices []primitives.ValidatorIndex) ([]*apimiddleware.AttesterDutyJson, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAttesterDuties", ctx, epoch, validatorIndices) ret0, _ := ret[0].([]*apimiddleware.AttesterDutyJson) @@ -52,7 +52,7 @@ func (mr *MockdutiesProviderMockRecorder) GetAttesterDuties(ctx, epoch, validato } // GetCommittees mocks base method. -func (m *MockdutiesProvider) GetCommittees(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.CommitteeJson, error) { +func (m *MockdutiesProvider) GetCommittees(ctx context.Context, epoch primitives.Epoch) ([]*apimiddleware.CommitteeJson, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCommittees", ctx, epoch) ret0, _ := ret[0].([]*apimiddleware.CommitteeJson) @@ -67,7 +67,7 @@ func (mr *MockdutiesProviderMockRecorder) GetCommittees(ctx, epoch interface{}) } // GetProposerDuties mocks base method. -func (m *MockdutiesProvider) GetProposerDuties(ctx context.Context, epoch types.Epoch) ([]*apimiddleware.ProposerDutyJson, error) { +func (m *MockdutiesProvider) GetProposerDuties(ctx context.Context, epoch primitives.Epoch) ([]*apimiddleware.ProposerDutyJson, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetProposerDuties", ctx, epoch) ret0, _ := ret[0].([]*apimiddleware.ProposerDutyJson) @@ -82,7 +82,7 @@ func (mr *MockdutiesProviderMockRecorder) GetProposerDuties(ctx, epoch interface } // GetSyncDuties mocks base method. -func (m *MockdutiesProvider) GetSyncDuties(ctx context.Context, epoch types.Epoch, validatorIndices []types.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error) { +func (m *MockdutiesProvider) GetSyncDuties(ctx context.Context, epoch primitives.Epoch, validatorIndices []primitives.ValidatorIndex) ([]*apimiddleware.SyncCommitteeDuty, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSyncDuties", ctx, epoch, validatorIndices) ret0, _ := ret[0].([]*apimiddleware.SyncCommitteeDuty) diff --git a/validator/client/beacon-api/stream_blocks.go b/validator/client/beacon-api/stream_blocks.go new file mode 100644 index 000000000..86435ef41 --- /dev/null +++ b/validator/client/beacon-api/stream_blocks.go @@ -0,0 +1,196 @@ +package beacon_api + +import ( + "bytes" + "context" + "encoding/json" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "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" + "google.golang.org/grpc" +) + +type abstractSignedBlockResponseJson struct { + Version string `json:"version" enum:"true"` + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data json.RawMessage `json:"data"` +} + +type streamBlocksAltairClient struct { + grpc.ClientStream + ctx context.Context + beaconApiClient beaconApiValidatorClient + streamBlocksRequest *ethpb.StreamBlocksRequest + prevBlockSlot primitives.Slot + pingDelay time.Duration +} + +type headSignedBeaconBlockResult struct { + streamBlocksResponse *ethpb.StreamBlocksResponse + executionOptimistic bool + slot primitives.Slot +} + +func (c beaconApiValidatorClient) streamBlocks(ctx context.Context, in *ethpb.StreamBlocksRequest, pingDelay time.Duration) ethpb.BeaconNodeValidator_StreamBlocksAltairClient { + return &streamBlocksAltairClient{ + ctx: ctx, + beaconApiClient: c, + streamBlocksRequest: in, + pingDelay: pingDelay, + } +} + +func (c *streamBlocksAltairClient) Recv() (*ethpb.StreamBlocksResponse, error) { + result, err := c.beaconApiClient.getHeadSignedBeaconBlock(c.ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get latest signed block") + } + + // We keep querying the beacon chain for the latest block until we receive a new slot + for (c.streamBlocksRequest.VerifiedOnly && result.executionOptimistic) || c.prevBlockSlot == result.slot { + select { + case <-time.After(c.pingDelay): + result, err = c.beaconApiClient.getHeadSignedBeaconBlock(c.ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get latest signed block") + } + case <-c.ctx.Done(): + return nil, errors.New("context canceled") + } + } + + c.prevBlockSlot = result.slot + return result.streamBlocksResponse, nil +} + +func (c beaconApiValidatorClient) getHeadSignedBeaconBlock(ctx context.Context) (*headSignedBeaconBlockResult, error) { + // Since we don't know yet what the json looks like, we unmarshal into an abstract structure that has only a version + // and a blob of data + signedBlockResponseJson := abstractSignedBlockResponseJson{} + if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, "/eth/v2/beacon/blocks/head", &signedBlockResponseJson); err != nil { + return nil, errors.Wrap(err, "failed to query GET REST endpoint") + } + + // Once we know what the consensus version is, we can go ahead and unmarshal into the specific structs unique to each version + decoder := json.NewDecoder(bytes.NewReader(signedBlockResponseJson.Data)) + decoder.DisallowUnknownFields() + + response := ðpb.StreamBlocksResponse{} + var slot primitives.Slot + + switch signedBlockResponseJson.Version { + case "phase0": + jsonPhase0Block := apimiddleware.SignedBeaconBlockContainerJson{} + if err := decoder.Decode(&jsonPhase0Block); err != nil { + return nil, errors.Wrap(err, "failed to decode signed phase0 block response json") + } + + phase0Block, err := c.beaconBlockConverter.ConvertRESTPhase0BlockToProto(jsonPhase0Block.Message) + if err != nil { + return nil, errors.Wrap(err, "failed to get signed phase0 block") + } + + decodedSignature, err := hexutil.Decode(jsonPhase0Block.Signature) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode phase0 block signature `%s`", jsonPhase0Block.Signature) + } + + response.Block = ðpb.StreamBlocksResponse_Phase0Block{ + Phase0Block: ðpb.SignedBeaconBlock{ + Signature: decodedSignature, + Block: phase0Block, + }, + } + + slot = phase0Block.Slot + + case "altair": + jsonAltairBlock := apimiddleware.SignedBeaconBlockAltairContainerJson{} + if err := decoder.Decode(&jsonAltairBlock); err != nil { + return nil, errors.Wrap(err, "failed to decode signed altair block response json") + } + + altairBlock, err := c.beaconBlockConverter.ConvertRESTAltairBlockToProto(jsonAltairBlock.Message) + if err != nil { + return nil, errors.Wrap(err, "failed to get signed altair block") + } + + decodedSignature, err := hexutil.Decode(jsonAltairBlock.Signature) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode altair block signature `%s`", jsonAltairBlock.Signature) + } + + response.Block = ðpb.StreamBlocksResponse_AltairBlock{ + AltairBlock: ðpb.SignedBeaconBlockAltair{ + Signature: decodedSignature, + Block: altairBlock, + }, + } + + slot = altairBlock.Slot + + case "bellatrix": + jsonBellatrixBlock := apimiddleware.SignedBeaconBlockBellatrixContainerJson{} + if err := decoder.Decode(&jsonBellatrixBlock); err != nil { + return nil, errors.Wrap(err, "failed to decode signed bellatrix block response json") + } + + bellatrixBlock, err := c.beaconBlockConverter.ConvertRESTBellatrixBlockToProto(jsonBellatrixBlock.Message) + if err != nil { + return nil, errors.Wrap(err, "failed to get signed bellatrix block") + } + + decodedSignature, err := hexutil.Decode(jsonBellatrixBlock.Signature) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode bellatrix block signature `%s`", jsonBellatrixBlock.Signature) + } + + response.Block = ðpb.StreamBlocksResponse_BellatrixBlock{ + BellatrixBlock: ðpb.SignedBeaconBlockBellatrix{ + Signature: decodedSignature, + Block: bellatrixBlock, + }, + } + + slot = bellatrixBlock.Slot + + case "capella": + jsonCapellaBlock := apimiddleware.SignedBeaconBlockCapellaContainerJson{} + if err := decoder.Decode(&jsonCapellaBlock); err != nil { + return nil, errors.Wrap(err, "failed to decode signed capella block response json") + } + + capellaBlock, err := c.beaconBlockConverter.ConvertRESTCapellaBlockToProto(jsonCapellaBlock.Message) + if err != nil { + return nil, errors.Wrap(err, "failed to get signed capella block") + } + + decodedSignature, err := hexutil.Decode(jsonCapellaBlock.Signature) + if err != nil { + return nil, errors.Wrapf(err, "failed to decode capella block signature `%s`", jsonCapellaBlock.Signature) + } + + response.Block = ðpb.StreamBlocksResponse_CapellaBlock{ + CapellaBlock: ðpb.SignedBeaconBlockCapella{ + Signature: decodedSignature, + Block: capellaBlock, + }, + } + + slot = capellaBlock.Slot + + default: + return nil, errors.Errorf("unsupported consensus version `%s`", signedBlockResponseJson.Version) + } + + return &headSignedBeaconBlockResult{ + streamBlocksResponse: response, + executionOptimistic: signedBlockResponseJson.ExecutionOptimistic, + slot: slot, + }, nil +} diff --git a/validator/client/beacon-api/stream_blocks_test.go b/validator/client/beacon-api/stream_blocks_test.go new file mode 100644 index 000000000..2dced6f6a --- /dev/null +++ b/validator/client/beacon-api/stream_blocks_test.go @@ -0,0 +1,832 @@ +package beacon_api + +import ( + "context" + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + eth "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 TestStreamBlocks_UnsupportedConsensusVersion(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + gomock.Any(), + &abstractSignedBlockResponseJson{}, + ).SetArg( + 2, + abstractSignedBlockResponseJson{Version: "foo"}, + ).Return( + nil, + nil, + ).Times(1) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + streamBlocksClient := validatorClient.streamBlocks(ctx, ð.StreamBlocksRequest{}, time.Millisecond*100) + _, err := streamBlocksClient.Recv() + assert.ErrorContains(t, "unsupported consensus version `foo`", err) +} + +func TestStreamBlocks_Error(t *testing.T) { + testSuites := []struct { + consensusVersion string + generateBeaconBlockConverter func(ctrl *gomock.Controller, conversionError error) *mock.MockbeaconBlockConverter + }{ + { + consensusVersion: "phase0", + generateBeaconBlockConverter: func(ctrl *gomock.Controller, conversionError error) *mock.MockbeaconBlockConverter { + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + beaconBlockConverter.EXPECT().ConvertRESTPhase0BlockToProto( + gomock.Any(), + ).Return( + nil, + conversionError, + ).AnyTimes() + + return beaconBlockConverter + }, + }, + { + consensusVersion: "altair", + generateBeaconBlockConverter: func(ctrl *gomock.Controller, conversionError error) *mock.MockbeaconBlockConverter { + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + beaconBlockConverter.EXPECT().ConvertRESTAltairBlockToProto( + gomock.Any(), + ).Return( + nil, + conversionError, + ).AnyTimes() + + return beaconBlockConverter + }, + }, + { + consensusVersion: "bellatrix", + generateBeaconBlockConverter: func(ctrl *gomock.Controller, conversionError error) *mock.MockbeaconBlockConverter { + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + beaconBlockConverter.EXPECT().ConvertRESTBellatrixBlockToProto( + gomock.Any(), + ).Return( + nil, + conversionError, + ).AnyTimes() + + return beaconBlockConverter + }, + }, + { + consensusVersion: "capella", + generateBeaconBlockConverter: func(ctrl *gomock.Controller, conversionError error) *mock.MockbeaconBlockConverter { + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + beaconBlockConverter.EXPECT().ConvertRESTCapellaBlockToProto( + gomock.Any(), + ).Return( + nil, + conversionError, + ).AnyTimes() + + return beaconBlockConverter + }, + }, + } + + testCases := []struct { + name string + expectedErrorMessage string + conversionError error + generateData func(consensusVersion string) []byte + }{ + { + name: "block decoding failed", + expectedErrorMessage: "failed to decode signed %s block response json", + generateData: func(consensusVersion string) []byte { return []byte{} }, + }, + { + name: "block conversion failed", + expectedErrorMessage: "failed to get signed %s block", + conversionError: errors.New("foo"), + generateData: func(consensusVersion string) []byte { + blockBytes, err := json.Marshal(apimiddleware.SignedBeaconBlockContainerJson{Signature: "0x01"}) + require.NoError(t, err) + return blockBytes + }, + }, + { + name: "signature decoding failed", + expectedErrorMessage: "failed to decode %s block signature `foo`", + generateData: func(consensusVersion string) []byte { + blockBytes, err := json.Marshal(apimiddleware.SignedBeaconBlockContainerJson{Signature: "foo"}) + require.NoError(t, err) + return blockBytes + }, + }, + } + + for _, testSuite := range testSuites { + t.Run(testSuite.consensusVersion, func(t *testing.T) { + 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(), + &abstractSignedBlockResponseJson{}, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: testSuite.consensusVersion, + Data: testCase.generateData(testSuite.consensusVersion), + }, + ).Return( + nil, + nil, + ).Times(1) + + beaconBlockConverter := testSuite.generateBeaconBlockConverter(ctrl, testCase.conversionError) + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} + streamBlocksClient := validatorClient.streamBlocks(ctx, ð.StreamBlocksRequest{}, time.Millisecond*100) + + _, err := streamBlocksClient.Recv() + assert.ErrorContains(t, fmt.Sprintf(testCase.expectedErrorMessage, testSuite.consensusVersion), err) + }) + } + }) + } + +} + +func TestStreamBlocks_Phase0Valid(t *testing.T) { + testCases := []struct { + name string + verifiedOnly bool + }{ + { + name: "verified optional", + verifiedOnly: false, + }, + { + name: "verified only", + verifiedOnly: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + + signedBlockResponseJson := abstractSignedBlockResponseJson{} + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + + // For the first call, return a block that satisfies the verifiedOnly condition. This block should be returned by the first Recv(). + // For the second call, return the same block as the previous one. This block shouldn't be returned by the second Recv(). + phase0BeaconBlock1 := test_helpers.GenerateJsonPhase0BeaconBlock() + phase0BeaconBlock1.Slot = "1" + signedBeaconBlockContainer1 := apimiddleware.SignedBeaconBlockContainerJson{ + Message: phase0BeaconBlock1, + Signature: "0x01", + } + + marshalledSignedBeaconBlockContainer1, err := json.Marshal(signedBeaconBlockContainer1) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "phase0", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer1, + }, + ).Times(2) + + phase0ProtoBeaconBlock1 := test_helpers.GenerateProtoPhase0BeaconBlock() + phase0ProtoBeaconBlock1.Slot = 1 + + beaconBlockConverter.EXPECT().ConvertRESTPhase0BlockToProto( + phase0BeaconBlock1, + ).Return( + phase0ProtoBeaconBlock1, + nil, + ).Times(2) + + // For the third call, return a block with a different slot than the previous one, but with the verifiedOnly condition not satisfied. + // If verifiedOnly == false, this block will be returned by the second Recv(); otherwise, another block will be requested. + phase0BeaconBlock2 := test_helpers.GenerateJsonPhase0BeaconBlock() + phase0BeaconBlock2.Slot = "2" + signedBeaconBlockContainer2 := apimiddleware.SignedBeaconBlockContainerJson{ + Message: phase0BeaconBlock2, + Signature: "0x02", + } + + marshalledSignedBeaconBlockContainer2, err := json.Marshal(signedBeaconBlockContainer2) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "phase0", + ExecutionOptimistic: true, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + phase0ProtoBeaconBlock2 := test_helpers.GenerateProtoPhase0BeaconBlock() + phase0ProtoBeaconBlock2.Slot = 2 + + beaconBlockConverter.EXPECT().ConvertRESTPhase0BlockToProto( + phase0BeaconBlock2, + ).Return( + phase0ProtoBeaconBlock2, + nil, + ).Times(1) + + // The fourth call is only necessary when verifiedOnly == true since the previous block was optimistic + if testCase.verifiedOnly { + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "phase0", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + beaconBlockConverter.EXPECT().ConvertRESTPhase0BlockToProto( + phase0BeaconBlock2, + ).Return( + phase0ProtoBeaconBlock2, + nil, + ).Times(1) + } + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} + streamBlocksClient := validatorClient.streamBlocks(ctx, ð.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100) + + // Get the first block + streamBlocksResponse1, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse1 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_Phase0Block{ + Phase0Block: ð.SignedBeaconBlock{ + Block: phase0ProtoBeaconBlock1, + Signature: []byte{1}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1) + + // Get the second block + streamBlocksResponse2, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse2 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_Phase0Block{ + Phase0Block: ð.SignedBeaconBlock{ + Block: phase0ProtoBeaconBlock2, + Signature: []byte{2}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse2, streamBlocksResponse2) + }) + } +} + +func TestStreamBlocks_AltairValid(t *testing.T) { + testCases := []struct { + name string + verifiedOnly bool + }{ + { + name: "verified optional", + verifiedOnly: false, + }, + { + name: "verified only", + verifiedOnly: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + + signedBlockResponseJson := abstractSignedBlockResponseJson{} + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + + // For the first call, return a block that satisfies the verifiedOnly condition. This block should be returned by the first Recv(). + // For the second call, return the same block as the previous one. This block shouldn't be returned by the second Recv(). + altairBeaconBlock1 := test_helpers.GenerateJsonAltairBeaconBlock() + altairBeaconBlock1.Slot = "1" + signedBeaconBlockContainer1 := apimiddleware.SignedBeaconBlockAltairContainerJson{ + Message: altairBeaconBlock1, + Signature: "0x01", + } + + marshalledSignedBeaconBlockContainer1, err := json.Marshal(signedBeaconBlockContainer1) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "altair", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer1, + }, + ).Times(2) + + altairProtoBeaconBlock1 := test_helpers.GenerateProtoAltairBeaconBlock() + altairProtoBeaconBlock1.Slot = 1 + + beaconBlockConverter.EXPECT().ConvertRESTAltairBlockToProto( + altairBeaconBlock1, + ).Return( + altairProtoBeaconBlock1, + nil, + ).Times(2) + + // For the third call, return a block with a different slot than the previous one, but with the verifiedOnly condition not satisfied. + // If verifiedOnly == false, this block will be returned by the second Recv(); otherwise, another block will be requested. + altairBeaconBlock2 := test_helpers.GenerateJsonAltairBeaconBlock() + altairBeaconBlock2.Slot = "2" + signedBeaconBlockContainer2 := apimiddleware.SignedBeaconBlockAltairContainerJson{ + Message: altairBeaconBlock2, + Signature: "0x02", + } + + marshalledSignedBeaconBlockContainer2, err := json.Marshal(signedBeaconBlockContainer2) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "altair", + ExecutionOptimistic: true, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + altairProtoBeaconBlock2 := test_helpers.GenerateProtoAltairBeaconBlock() + altairProtoBeaconBlock2.Slot = 2 + + beaconBlockConverter.EXPECT().ConvertRESTAltairBlockToProto( + altairBeaconBlock2, + ).Return( + altairProtoBeaconBlock2, + nil, + ).Times(1) + + // The fourth call is only necessary when verifiedOnly == true since the previous block was optimistic + if testCase.verifiedOnly { + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "altair", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + beaconBlockConverter.EXPECT().ConvertRESTAltairBlockToProto( + altairBeaconBlock2, + ).Return( + altairProtoBeaconBlock2, + nil, + ).Times(1) + } + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} + streamBlocksClient := validatorClient.streamBlocks(ctx, ð.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100) + + // Get the first block + streamBlocksResponse1, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse1 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_AltairBlock{ + AltairBlock: ð.SignedBeaconBlockAltair{ + Block: altairProtoBeaconBlock1, + Signature: []byte{1}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1) + + // Get the second block + streamBlocksResponse2, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse2 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_AltairBlock{ + AltairBlock: ð.SignedBeaconBlockAltair{ + Block: altairProtoBeaconBlock2, + Signature: []byte{2}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse2, streamBlocksResponse2) + }) + } +} + +func TestStreamBlocks_BellatrixValid(t *testing.T) { + testCases := []struct { + name string + verifiedOnly bool + }{ + { + name: "verified optional", + verifiedOnly: false, + }, + { + name: "verified only", + verifiedOnly: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + + signedBlockResponseJson := abstractSignedBlockResponseJson{} + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + + // For the first call, return a block that satisfies the verifiedOnly condition. This block should be returned by the first Recv(). + // For the second call, return the same block as the previous one. This block shouldn't be returned by the second Recv(). + bellatrixBeaconBlock1 := test_helpers.GenerateJsonBellatrixBeaconBlock() + bellatrixBeaconBlock1.Slot = "1" + signedBeaconBlockContainer1 := apimiddleware.SignedBeaconBlockBellatrixContainerJson{ + Message: bellatrixBeaconBlock1, + Signature: "0x01", + } + + marshalledSignedBeaconBlockContainer1, err := json.Marshal(signedBeaconBlockContainer1) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "bellatrix", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer1, + }, + ).Times(2) + + bellatrixProtoBeaconBlock1 := test_helpers.GenerateProtoBellatrixBeaconBlock() + bellatrixProtoBeaconBlock1.Slot = 1 + + beaconBlockConverter.EXPECT().ConvertRESTBellatrixBlockToProto( + bellatrixBeaconBlock1, + ).Return( + bellatrixProtoBeaconBlock1, + nil, + ).Times(2) + + // For the third call, return a block with a different slot than the previous one, but with the verifiedOnly condition not satisfied. + // If verifiedOnly == false, this block will be returned by the second Recv(); otherwise, another block will be requested. + bellatrixBeaconBlock2 := test_helpers.GenerateJsonBellatrixBeaconBlock() + bellatrixBeaconBlock2.Slot = "2" + signedBeaconBlockContainer2 := apimiddleware.SignedBeaconBlockBellatrixContainerJson{ + Message: bellatrixBeaconBlock2, + Signature: "0x02", + } + + marshalledSignedBeaconBlockContainer2, err := json.Marshal(signedBeaconBlockContainer2) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "bellatrix", + ExecutionOptimistic: true, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + bellatrixProtoBeaconBlock2 := test_helpers.GenerateProtoBellatrixBeaconBlock() + bellatrixProtoBeaconBlock2.Slot = 2 + + beaconBlockConverter.EXPECT().ConvertRESTBellatrixBlockToProto( + bellatrixBeaconBlock2, + ).Return( + bellatrixProtoBeaconBlock2, + nil, + ).Times(1) + + // The fourth call is only necessary when verifiedOnly == true since the previous block was optimistic + if testCase.verifiedOnly { + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "bellatrix", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + beaconBlockConverter.EXPECT().ConvertRESTBellatrixBlockToProto( + bellatrixBeaconBlock2, + ).Return( + bellatrixProtoBeaconBlock2, + nil, + ).Times(1) + } + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} + streamBlocksClient := validatorClient.streamBlocks(ctx, ð.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100) + + // Get the first block + streamBlocksResponse1, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse1 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_BellatrixBlock{ + BellatrixBlock: ð.SignedBeaconBlockBellatrix{ + Block: bellatrixProtoBeaconBlock1, + Signature: []byte{1}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1) + + // Get the second block + streamBlocksResponse2, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse2 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_BellatrixBlock{ + BellatrixBlock: ð.SignedBeaconBlockBellatrix{ + Block: bellatrixProtoBeaconBlock2, + Signature: []byte{2}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse2, streamBlocksResponse2) + }) + } +} + +func TestStreamBlocks_CapellaValid(t *testing.T) { + testCases := []struct { + name string + verifiedOnly bool + }{ + { + name: "verified optional", + verifiedOnly: false, + }, + { + name: "verified only", + verifiedOnly: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + + signedBlockResponseJson := abstractSignedBlockResponseJson{} + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + beaconBlockConverter := mock.NewMockbeaconBlockConverter(ctrl) + + // For the first call, return a block that satisfies the verifiedOnly condition. This block should be returned by the first Recv(). + // For the second call, return the same block as the previous one. This block shouldn't be returned by the second Recv(). + capellaBeaconBlock1 := test_helpers.GenerateJsonCapellaBeaconBlock() + capellaBeaconBlock1.Slot = "1" + signedBeaconBlockContainer1 := apimiddleware.SignedBeaconBlockCapellaContainerJson{ + Message: capellaBeaconBlock1, + Signature: "0x01", + } + + marshalledSignedBeaconBlockContainer1, err := json.Marshal(signedBeaconBlockContainer1) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "capella", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer1, + }, + ).Times(2) + + capellaProtoBeaconBlock1 := test_helpers.GenerateProtoCapellaBeaconBlock() + capellaProtoBeaconBlock1.Slot = 1 + + beaconBlockConverter.EXPECT().ConvertRESTCapellaBlockToProto( + capellaBeaconBlock1, + ).Return( + capellaProtoBeaconBlock1, + nil, + ).Times(2) + + // For the third call, return a block with a different slot than the previous one, but with the verifiedOnly condition not satisfied. + // If verifiedOnly == false, this block will be returned by the second Recv(); otherwise, another block will be requested. + capellaBeaconBlock2 := test_helpers.GenerateJsonCapellaBeaconBlock() + capellaBeaconBlock2.Slot = "2" + signedBeaconBlockContainer2 := apimiddleware.SignedBeaconBlockCapellaContainerJson{ + Message: capellaBeaconBlock2, + Signature: "0x02", + } + + marshalledSignedBeaconBlockContainer2, err := json.Marshal(signedBeaconBlockContainer2) + require.NoError(t, err) + + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "capella", + ExecutionOptimistic: true, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + capellaProtoBeaconBlock2 := test_helpers.GenerateProtoCapellaBeaconBlock() + capellaProtoBeaconBlock2.Slot = 2 + + beaconBlockConverter.EXPECT().ConvertRESTCapellaBlockToProto( + capellaBeaconBlock2, + ).Return( + capellaProtoBeaconBlock2, + nil, + ).Times(1) + + // The fourth call is only necessary when verifiedOnly == true since the previous block was optimistic + if testCase.verifiedOnly { + jsonRestHandler.EXPECT().GetRestJsonResponse( + ctx, + "/eth/v2/beacon/blocks/head", + &signedBlockResponseJson, + ).Return( + nil, + nil, + ).SetArg( + 2, + abstractSignedBlockResponseJson{ + Version: "capella", + ExecutionOptimistic: false, + Data: marshalledSignedBeaconBlockContainer2, + }, + ).Times(1) + + beaconBlockConverter.EXPECT().ConvertRESTCapellaBlockToProto( + capellaBeaconBlock2, + ).Return( + capellaProtoBeaconBlock2, + nil, + ).Times(1) + } + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler, beaconBlockConverter: beaconBlockConverter} + streamBlocksClient := validatorClient.streamBlocks(ctx, ð.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100) + + // Get the first block + streamBlocksResponse1, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse1 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_CapellaBlock{ + CapellaBlock: ð.SignedBeaconBlockCapella{ + Block: capellaProtoBeaconBlock1, + Signature: []byte{1}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1) + + // Get the second block + streamBlocksResponse2, err := streamBlocksClient.Recv() + require.NoError(t, err) + + expectedStreamBlocksResponse2 := ð.StreamBlocksResponse{ + Block: ð.StreamBlocksResponse_CapellaBlock{ + CapellaBlock: ð.SignedBeaconBlockCapella{ + Block: capellaProtoBeaconBlock2, + Signature: []byte{2}, + }, + }, + } + + assert.DeepEqual(t, expectedStreamBlocksResponse2, streamBlocksResponse2) + }) + } +}