Add REST implementation for Validator's StreamBlocksAltair (#11974)

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
Patrice Vignola 2023-02-13 05:22:11 -08:00 committed by GitHub
parent 63186c8b0f
commit 791110f795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2327 additions and 1242 deletions

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -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) {

View File

@ -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 &ethpb.BeaconBlock{
Slot: primitives.Slot(blockSlot),
ProposerIndex: primitives.ValidatorIndex(blockProposerIndex),
ParentRoot: parentRoot,
StateRoot: stateRoot,
Body: &ethpb.BeaconBlockBody{
RandaoReveal: randaoReveal,
Eth1Data: &ethpb.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 &ethpb.BeaconBlockAltair{
Slot: phase0Block.Slot,
ProposerIndex: phase0Block.ProposerIndex,
ParentRoot: phase0Block.ParentRoot,
StateRoot: phase0Block.StateRoot,
Body: &ethpb.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: &ethpb.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 &ethpb.BeaconBlockBellatrix{
Slot: altairBlock.Slot,
ProposerIndex: altairBlock.ProposerIndex,
ParentRoot: altairBlock.ParentRoot,
StateRoot: altairBlock.StateRoot,
Body: &ethpb.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 &ethpb.BeaconBlockCapella{
Slot: bellatrixBlock.Slot,
ProposerIndex: bellatrixBlock.ProposerIndex,
ParentRoot: bellatrixBlock.ParentRoot,
StateRoot: bellatrixBlock.StateRoot,
Body: &ethpb.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
}

View File

@ -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)
})
}
}

View File

@ -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 = &ethpb.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 = &ethpb.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 = &ethpb.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 = &ethpb.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 &ethpb.GenericBeaconBlock_Phase0{
Phase0: &ethpb.BeaconBlock{
Slot: primitives.Slot(blockSlot),
ProposerIndex: primitives.ValidatorIndex(blockProposerIndex),
ParentRoot: parentRoot,
StateRoot: stateRoot,
Body: &ethpb.BeaconBlockBody{
RandaoReveal: randaoReveal,
Eth1Data: &ethpb.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 &ethpb.GenericBeaconBlock_Altair{
Altair: &ethpb.BeaconBlockAltair{
Slot: phase0Block.Phase0.Slot,
ProposerIndex: phase0Block.Phase0.ProposerIndex,
ParentRoot: phase0Block.Phase0.ParentRoot,
StateRoot: phase0Block.Phase0.StateRoot,
Body: &ethpb.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: &ethpb.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 &ethpb.GenericBeaconBlock_Bellatrix{
Bellatrix: &ethpb.BeaconBlockBellatrix{
Slot: altairBlock.Altair.Slot,
ProposerIndex: altairBlock.Altair.ProposerIndex,
ParentRoot: altairBlock.Altair.ParentRoot,
StateRoot: altairBlock.Altair.StateRoot,
Body: &ethpb.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 &ethpb.GenericBeaconBlock_Capella{
Capella: &ethpb.BeaconBlockCapella{
Slot: bellatrixBlock.Bellatrix.Slot,
ProposerIndex: bellatrixBlock.Bellatrix.ProposerIndex,
ParentRoot: bellatrixBlock.Bellatrix.ParentRoot,
StateRoot: bellatrixBlock.Bellatrix.StateRoot,
Body: &ethpb.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
}

View File

@ -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 &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Altair{
Altair: altairProtoBeaconBlock,
},
}
}

View File

@ -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 &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Bellatrix{
Bellatrix: bellatrixProtoBeaconBlock,
},
}
}

View File

@ -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 &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Capella{
Capella: capellaProtoBeaconBlock,
},
}
}

View File

@ -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 &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Phase0{
Phase0: phase0ProtoBeaconBlock,
},
}
}

View File

@ -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 := &ethpb.GenericBeaconBlock{
Block: &ethpb.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 := &ethpb.GenericBeaconBlock{
Block: &ethpb.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 := &ethpb.GenericBeaconBlock{
Block: &ethpb.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 := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Capella{
Capella: capellaProtoBeaconBlock,
},
}
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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)

View File

@ -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 := &ethpb.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 = &ethpb.StreamBlocksResponse_Phase0Block{
Phase0Block: &ethpb.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 = &ethpb.StreamBlocksResponse_AltairBlock{
AltairBlock: &ethpb.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 = &ethpb.StreamBlocksResponse_BellatrixBlock{
BellatrixBlock: &ethpb.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 = &ethpb.StreamBlocksResponse_CapellaBlock{
CapellaBlock: &ethpb.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
}

View File

@ -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, &eth.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, &eth.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, &eth.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100)
// Get the first block
streamBlocksResponse1, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse1 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_Phase0Block{
Phase0Block: &eth.SignedBeaconBlock{
Block: phase0ProtoBeaconBlock1,
Signature: []byte{1},
},
},
}
assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1)
// Get the second block
streamBlocksResponse2, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse2 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_Phase0Block{
Phase0Block: &eth.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, &eth.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100)
// Get the first block
streamBlocksResponse1, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse1 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_AltairBlock{
AltairBlock: &eth.SignedBeaconBlockAltair{
Block: altairProtoBeaconBlock1,
Signature: []byte{1},
},
},
}
assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1)
// Get the second block
streamBlocksResponse2, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse2 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_AltairBlock{
AltairBlock: &eth.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, &eth.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100)
// Get the first block
streamBlocksResponse1, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse1 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_BellatrixBlock{
BellatrixBlock: &eth.SignedBeaconBlockBellatrix{
Block: bellatrixProtoBeaconBlock1,
Signature: []byte{1},
},
},
}
assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1)
// Get the second block
streamBlocksResponse2, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse2 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_BellatrixBlock{
BellatrixBlock: &eth.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, &eth.StreamBlocksRequest{VerifiedOnly: testCase.verifiedOnly}, time.Millisecond*100)
// Get the first block
streamBlocksResponse1, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse1 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_CapellaBlock{
CapellaBlock: &eth.SignedBeaconBlockCapella{
Block: capellaProtoBeaconBlock1,
Signature: []byte{1},
},
},
}
assert.DeepEqual(t, expectedStreamBlocksResponse1, streamBlocksResponse1)
// Get the second block
streamBlocksResponse2, err := streamBlocksClient.Recv()
require.NoError(t, err)
expectedStreamBlocksResponse2 := &eth.StreamBlocksResponse{
Block: &eth.StreamBlocksResponse_CapellaBlock{
CapellaBlock: &eth.SignedBeaconBlockCapella{
Block: capellaProtoBeaconBlock2,
Signature: []byte{2},
},
},
}
assert.DeepEqual(t, expectedStreamBlocksResponse2, streamBlocksResponse2)
})
}
}