diff --git a/api/client/builder/types.go b/api/client/builder/types.go index 9e54be39d3..bb6c94f61c 100644 --- a/api/client/builder/types.go +++ b/api/client/builder/types.go @@ -110,8 +110,7 @@ func stringToUint256(s string) (Uint256, error) { // sszBytesToUint256 creates a Uint256 from a ssz-style (little-endian byte slice) representation. func sszBytesToUint256(b []byte) (Uint256, error) { - bi := new(big.Int) - bi.SetBytes(bytesutil.ReverseByteOrder(b)) + bi := bytesutil.LittleEndianBytesToBigInt(b) if !isValidUint256(bi) { return Uint256{}, errors.Wrapf(errDecodeUint256, "value=%s", b) } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go index d3b34cef18..f986497fd4 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "math/big" "time" "github.com/pkg/errors" @@ -128,7 +127,7 @@ func (vs *Server) getPayloadHeaderFromBuilder(ctx context.Context, slot types.Sl return nil, errors.New("builder returned nil bid") } - v := new(big.Int).SetBytes(bytesutil.ReverseByteOrder(bid.Message.Value)) + v := bytesutil.LittleEndianBytesToBigInt(bid.Message.Value) if v.String() == "0" { return nil, errors.New("builder returned header with 0 bid amount") } diff --git a/encoding/bytesutil/bytes.go b/encoding/bytesutil/bytes.go index 869be8aa17..323f9d083e 100644 --- a/encoding/bytesutil/bytes.go +++ b/encoding/bytesutil/bytes.go @@ -4,6 +4,7 @@ package bytesutil import ( "encoding/binary" "fmt" + "math/big" "math/bits" "regexp" @@ -438,3 +439,9 @@ func IsRoot(root []byte) bool { func IsValidRoot(root []byte) bool { return IsRoot(root) && !ZeroRoot(root) } + +// LittleEndianBytesToBigInt takes bytes of a number stored as little-endian and returns a big integer +func LittleEndianBytesToBigInt(bytes []byte) *big.Int { + // Integers are stored as little-endian, but big.Int expects big-endian. So we need to reverse the byte order before decoding. + return new(big.Int).SetBytes(ReverseByteOrder(bytes)) +} diff --git a/encoding/bytesutil/bytes_test.go b/encoding/bytesutil/bytes_test.go index 389dc7775e..459fef7bc4 100644 --- a/encoding/bytesutil/bytes_test.go +++ b/encoding/bytesutil/bytes_test.go @@ -2,7 +2,9 @@ package bytesutil_test import ( "bytes" + "encoding/binary" "fmt" + "math/big" "reflect" "testing" @@ -634,3 +636,11 @@ func TestToBytes48Array(t *testing.T) { assert.DeepEqual(t, tt.b, b) } } + +func TestLittleEndianBytesToBigInt(t *testing.T) { + bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, 1234567890) + converted := bytesutil.LittleEndianBytesToBigInt(bytes) + expected := new(big.Int).SetInt64(1234567890) + assert.DeepEqual(t, expected, converted) +} diff --git a/proto/engine/v1/json_marshal_unmarshal.go b/proto/engine/v1/json_marshal_unmarshal.go index a75c2f0089..630c52f162 100644 --- a/proto/engine/v1/json_marshal_unmarshal.go +++ b/proto/engine/v1/json_marshal_unmarshal.go @@ -132,7 +132,7 @@ func (e *ExecutionPayload) MarshalJSON() ([]byte, error) { for i, tx := range e.Transactions { transactions[i] = tx } - baseFee := new(big.Int).SetBytes(bytesutil.ReverseByteOrder(e.BaseFeePerGas)) + baseFee := bytesutil.LittleEndianBytesToBigInt(e.BaseFeePerGas) baseFeeHex := hexutil.EncodeBig(baseFee) pHash := common.BytesToHash(e.ParentHash) sRoot := common.BytesToHash(e.StateRoot) diff --git a/runtime/interop/generate_keys.go b/runtime/interop/generate_keys.go index 4a256d6d35..ac5a3baf9f 100644 --- a/runtime/interop/generate_keys.go +++ b/runtime/interop/generate_keys.go @@ -54,9 +54,7 @@ func deterministicallyGenerateKeys(startIndex, numKeys uint64) ([]bls.SecretKey, binary.LittleEndian.PutUint32(enc, uint32(i)) h := hash.Hash(enc) // Reverse byte order to big endian for use with big ints. - b := bytesutil.ReverseByteOrder(h[:]) - num := new(big.Int) - num = num.SetBytes(b) + num := bytesutil.LittleEndianBytesToBigInt(h[:]) order := new(big.Int) var ok bool order, ok = order.SetString(bls.CurveOrder, 10) diff --git a/testing/spectest/shared/common/forkchoice/service.go b/testing/spectest/shared/common/forkchoice/service.go index 13a0b358c5..d29e4ff284 100644 --- a/testing/spectest/shared/common/forkchoice/service.go +++ b/testing/spectest/shared/common/forkchoice/service.go @@ -2,7 +2,6 @@ package forkchoice import ( "context" - "math/big" "testing" "github.com/ethereum/go-ethereum/common" @@ -105,7 +104,7 @@ func (m *engineMock) ExecutionBlockByHash(_ context.Context, hash common.Hash, _ return nil, nil } - td := new(big.Int).SetBytes(bytesutil.ReverseByteOrder(b.TotalDifficulty)) + td := bytesutil.LittleEndianBytesToBigInt(b.TotalDifficulty) tdHex := hexutil.EncodeBig(td) return &pb.ExecutionBlock{ Header: gethtypes.Header{ diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index 9ab79e8fb1..1192884b3f 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -7,10 +7,12 @@ go_library( "attestation_data.go", "beacon_api_helpers.go", "beacon_api_validator_client.go", + "beacon_block_json_helpers.go", "domain_data.go", "genesis.go", "index.go", "json_rest_handler.go", + "propose_beacon_block.go", "state_validators.go", ], importpath = "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api", @@ -40,10 +42,17 @@ go_test( "attestation_data_test.go", "beacon_api_helpers_test.go", "beacon_api_validator_client_test.go", + "beacon_block_json_helpers_test.go", "domain_data_test.go", "genesis_test.go", "index_test.go", "json_rest_handler_test.go", + "propose_beacon_block_altair_test.go", + "propose_beacon_block_bellatrix_test.go", + "propose_beacon_block_blinded_bellatrix_test.go", + "propose_beacon_block_blinded_capella_test.go", + "propose_beacon_block_phase0_test.go", + "propose_beacon_block_test.go", "state_validators_test.go", "wait_for_chain_start_test.go", ], @@ -54,10 +63,12 @@ go_test( "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", + "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//validator/client/beacon-api/mock:go_default_library", + "//validator/client/beacon-api/test-helpers:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_golang_mock//gomock:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/validator/client/beacon-api/beacon_api_helpers.go b/validator/client/beacon-api/beacon_api_helpers.go index 08c70e6555..08c5f23cd3 100644 --- a/validator/client/beacon-api/beacon_api_helpers.go +++ b/validator/client/beacon-api/beacon_api_helpers.go @@ -4,6 +4,9 @@ import ( "fmt" neturl "net/url" "regexp" + "strconv" + + types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" ) func validRoot(root string) bool { @@ -14,6 +17,10 @@ func validRoot(root string) bool { return matchesRegex } +func uint64ToString[T uint64 | types.Slot | types.ValidatorIndex | types.CommitteeIndex | types.Epoch](val T) string { + return strconv.FormatUint(uint64(val), 10) +} + func buildURL(path string, queryParams ...neturl.Values) string { if len(queryParams) == 0 { return path diff --git a/validator/client/beacon-api/beacon_api_helpers_test.go b/validator/client/beacon-api/beacon_api_helpers_test.go index ee786a5433..15c46b1211 100644 --- a/validator/client/beacon-api/beacon_api_helpers_test.go +++ b/validator/client/beacon-api/beacon_api_helpers_test.go @@ -4,6 +4,7 @@ import ( "net/url" "testing" + types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/testing/assert" ) @@ -52,6 +53,17 @@ func TestBeaconApiHelpers(t *testing.T) { } } +func TestBeaconApiHelpers_TestUint64ToString(t *testing.T) { + const expectedResult = "1234" + const val = uint64(1234) + + assert.Equal(t, expectedResult, uint64ToString(val)) + assert.Equal(t, expectedResult, uint64ToString(types.Slot(val))) + assert.Equal(t, expectedResult, uint64ToString(types.ValidatorIndex(val))) + assert.Equal(t, expectedResult, uint64ToString(types.CommitteeIndex(val))) + assert.Equal(t, expectedResult, uint64ToString(types.Epoch(val))) +} + func TestBuildURL_NoParams(t *testing.T) { wanted := "/aaa/bbb/ccc" actual := buildURL("/aaa/bbb/ccc") diff --git a/validator/client/beacon-api/beacon_api_validator_client.go b/validator/client/beacon-api/beacon_api_validator_client.go index 28cabda679..6e1e21dd8f 100644 --- a/validator/client/beacon-api/beacon_api_validator_client.go +++ b/validator/client/beacon-api/beacon_api_validator_client.go @@ -143,13 +143,8 @@ func (c *beaconApiValidatorClient) ProposeAttestation(ctx context.Context, in *e panic("beaconApiValidatorClient.ProposeAttestation is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.") } -func (c *beaconApiValidatorClient) ProposeBeaconBlock(ctx context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) { - if c.fallbackClient != nil { - return c.fallbackClient.ProposeBeaconBlock(ctx, in) - } - - // TODO: Implement me - panic("beaconApiValidatorClient.ProposeBeaconBlock is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.") +func (c *beaconApiValidatorClient) ProposeBeaconBlock(_ context.Context, in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) { + return c.proposeBeaconBlock(in) } func (c *beaconApiValidatorClient) ProposeExit(ctx context.Context, in *ethpb.SignedVoluntaryExit) (*ethpb.ProposeExitResponse, error) { diff --git a/validator/client/beacon-api/beacon_api_validator_client_test.go b/validator/client/beacon-api/beacon_api_validator_client_test.go index 61eda925b7..4984a65e61 100644 --- a/validator/client/beacon-api/beacon_api_validator_client_test.go +++ b/validator/client/beacon-api/beacon_api_validator_client_test.go @@ -18,12 +18,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock" ) -func TestBeaconApiValidatorClient_GetAttestationDataNilInput(t *testing.T) { - validatorClient := beaconApiValidatorClient{} - _, err := validatorClient.GetAttestationData(context.Background(), nil) - assert.ErrorContains(t, "GetAttestationData received nil argument `in`", err) -} - // Make sure that GetAttestationData() returns the same thing as the internal getAttestationData() func TestBeaconApiValidatorClient_GetAttestationDataValid(t *testing.T) { const slot = types.Slot(1) @@ -57,6 +51,44 @@ func TestBeaconApiValidatorClient_GetAttestationDataValid(t *testing.T) { assert.DeepEqual(t, expectedResp, resp) } +func TestBeaconApiValidatorClient_GetAttestationDataNilInput(t *testing.T) { + validatorClient := beaconApiValidatorClient{} + _, err := validatorClient.GetAttestationData(context.Background(), nil) + assert.ErrorContains(t, "GetAttestationData received nil argument `in`", err) +} + +func TestBeaconApiValidatorClient_GetAttestationDataError(t *testing.T) { + const slot = types.Slot(1) + const committeeIndex = types.CommitteeIndex(2) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + produceAttestationDataResponseJson := rpcmiddleware.ProduceAttestationDataResponseJson{} + jsonRestHandler.EXPECT().GetRestJsonResponse( + fmt.Sprintf("/eth/v1/validator/attestation_data?committee_index=%d&slot=%d", committeeIndex, slot), + &produceAttestationDataResponseJson, + ).Return( + nil, + errors.New("some specific json error"), + ).SetArg( + 1, + generateValidAttestation(uint64(slot), uint64(committeeIndex)), + ).Times(2) + + validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + expectedResp, expectedErr := validatorClient.getAttestationData(slot, committeeIndex) + + resp, err := validatorClient.GetAttestationData( + context.Background(), + ðpb.AttestationDataRequest{Slot: slot, CommitteeIndex: committeeIndex}, + ) + + assert.ErrorContains(t, expectedErr.Error(), err) + assert.DeepEqual(t, expectedResp, resp) +} + func TestBeaconApiValidatorClient_DomainDataValid(t *testing.T) { const genesisValidatorRoot = "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" epoch := params.BeaconConfig().AltairForkEpoch @@ -89,7 +121,41 @@ func TestBeaconApiValidatorClient_DomainDataError(t *testing.T) { assert.ErrorContains(t, fmt.Sprintf("invalid domain type: %s", hexutil.Encode(domainType)), err) } -func TestBeaconApiValidatorClient_GetAttestationDataError(t *testing.T) { +func TestBeaconApiValidatorClient_ProposeBeaconBlockValid(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blocks", + map[string]string{"Eth-Consensus-Version": "phase0"}, + gomock.Any(), + nil, + ).Return( + nil, + nil, + ).Times(2) + + validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + expectedResp, expectedErr := validatorClient.proposeBeaconBlock( + ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + ) + + resp, err := validatorClient.ProposeBeaconBlock( + context.Background(), + ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + ) + + assert.DeepEqual(t, expectedErr, err) + assert.DeepEqual(t, expectedResp, resp) +} + +func TestBeaconApiValidatorClient_ProposeBeaconBlockError(t *testing.T) { const slot = types.Slot(1) const committeeIndex = types.CommitteeIndex(2) @@ -97,24 +163,28 @@ func TestBeaconApiValidatorClient_GetAttestationDataError(t *testing.T) { defer ctrl.Finish() jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) - produceAttestationDataResponseJson := rpcmiddleware.ProduceAttestationDataResponseJson{} - jsonRestHandler.EXPECT().GetRestJsonResponse( - fmt.Sprintf("/eth/v1/validator/attestation_data?committee_index=%d&slot=%d", committeeIndex, slot), - &produceAttestationDataResponseJson, + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blocks", + map[string]string{"Eth-Consensus-Version": "phase0"}, + gomock.Any(), + nil, ).Return( nil, - errors.New("some specific json error"), - ).SetArg( - 1, - generateValidAttestation(uint64(slot), uint64(committeeIndex)), + errors.New("foo error"), ).Times(2) validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - expectedResp, expectedErr := validatorClient.getAttestationData(slot, committeeIndex) + expectedResp, expectedErr := validatorClient.proposeBeaconBlock( + ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + ) - resp, err := validatorClient.GetAttestationData( + resp, err := validatorClient.ProposeBeaconBlock( context.Background(), - ðpb.AttestationDataRequest{Slot: slot, CommitteeIndex: committeeIndex}, + ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, ) assert.ErrorContains(t, expectedErr.Error(), err) diff --git a/validator/client/beacon-api/beacon_block_json_helpers.go b/validator/client/beacon-api/beacon_block_json_helpers.go new file mode 100644 index 0000000000..0034ed589d --- /dev/null +++ b/validator/client/beacon-api/beacon_block_json_helpers.go @@ -0,0 +1,154 @@ +package beacon_api + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" +) + +func jsonifyTransactions(transactions [][]byte) []string { + jsonTransactions := make([]string, len(transactions)) + for index, transaction := range transactions { + jsonTransaction := hexutil.Encode(transaction) + jsonTransactions[index] = jsonTransaction + } + return jsonTransactions +} + +func jsonifyBlsToExecutionChanges(blsToExecutionChanges []*ethpb.SignedBLSToExecutionChange) []*apimiddleware.BLSToExecutionChangeJson { + jsonBlsToExecutionChanges := make([]*apimiddleware.BLSToExecutionChangeJson, len(blsToExecutionChanges)) + for index, signedBlsToExecutionChange := range blsToExecutionChanges { + blsToExecutionChangeJson := &apimiddleware.BLSToExecutionChangeJson{ + ValidatorIndex: uint64ToString(signedBlsToExecutionChange.Message.ValidatorIndex), + FromBLSPubkey: hexutil.Encode(signedBlsToExecutionChange.Message.FromBlsPubkey), + ToExecutionAddress: hexutil.Encode(signedBlsToExecutionChange.Message.ToExecutionAddress), + } + jsonBlsToExecutionChanges[index] = blsToExecutionChangeJson + } + return jsonBlsToExecutionChanges +} + +func jsonifyEth1Data(eth1Data *ethpb.Eth1Data) *apimiddleware.Eth1DataJson { + return &apimiddleware.Eth1DataJson{ + BlockHash: hexutil.Encode(eth1Data.BlockHash), + DepositCount: uint64ToString(eth1Data.DepositCount), + DepositRoot: hexutil.Encode(eth1Data.DepositRoot), + } +} + +func jsonifyAttestations(attestations []*ethpb.Attestation) []*apimiddleware.AttestationJson { + jsonAttestations := make([]*apimiddleware.AttestationJson, len(attestations)) + for index, attestation := range attestations { + jsonAttestation := &apimiddleware.AttestationJson{ + AggregationBits: hexutil.Encode(attestation.AggregationBits), + Data: jsonifyAttestationData(attestation.Data), + Signature: hexutil.Encode(attestation.Signature), + } + jsonAttestations[index] = jsonAttestation + } + return jsonAttestations +} + +func jsonifyAttesterSlashings(attesterSlashings []*ethpb.AttesterSlashing) []*apimiddleware.AttesterSlashingJson { + jsonAttesterSlashings := make([]*apimiddleware.AttesterSlashingJson, len(attesterSlashings)) + for index, attesterSlashing := range attesterSlashings { + jsonAttesterSlashing := &apimiddleware.AttesterSlashingJson{ + Attestation_1: jsonifyIndexedAttestation(attesterSlashing.Attestation_1), + Attestation_2: jsonifyIndexedAttestation(attesterSlashing.Attestation_2), + } + jsonAttesterSlashings[index] = jsonAttesterSlashing + } + return jsonAttesterSlashings +} + +func jsonifyDeposits(deposits []*ethpb.Deposit) []*apimiddleware.DepositJson { + jsonDeposits := make([]*apimiddleware.DepositJson, len(deposits)) + for depositIndex, deposit := range deposits { + proofs := make([]string, len(deposit.Proof)) + for proofIndex, proof := range deposit.Proof { + proofs[proofIndex] = hexutil.Encode(proof) + } + + jsonDeposit := &apimiddleware.DepositJson{ + Data: &apimiddleware.Deposit_DataJson{ + Amount: uint64ToString(deposit.Data.Amount), + PublicKey: hexutil.Encode(deposit.Data.PublicKey), + Signature: hexutil.Encode(deposit.Data.Signature), + WithdrawalCredentials: hexutil.Encode(deposit.Data.WithdrawalCredentials), + }, + Proof: proofs, + } + jsonDeposits[depositIndex] = jsonDeposit + } + return jsonDeposits +} + +func jsonifyProposerSlashings(proposerSlashings []*ethpb.ProposerSlashing) []*apimiddleware.ProposerSlashingJson { + jsonProposerSlashings := make([]*apimiddleware.ProposerSlashingJson, len(proposerSlashings)) + for index, proposerSlashing := range proposerSlashings { + jsonProposerSlashing := &apimiddleware.ProposerSlashingJson{ + Header_1: jsonifySignedBeaconBlockHeader(proposerSlashing.Header_1), + Header_2: jsonifySignedBeaconBlockHeader(proposerSlashing.Header_2), + } + jsonProposerSlashings[index] = jsonProposerSlashing + } + return jsonProposerSlashings +} + +func jsonifySignedVoluntaryExits(voluntaryExits []*ethpb.SignedVoluntaryExit) []*apimiddleware.SignedVoluntaryExitJson { + jsonSignedVoluntaryExits := make([]*apimiddleware.SignedVoluntaryExitJson, len(voluntaryExits)) + for index, signedVoluntaryExit := range voluntaryExits { + jsonSignedVoluntaryExit := &apimiddleware.SignedVoluntaryExitJson{ + Exit: &apimiddleware.VoluntaryExitJson{ + Epoch: uint64ToString(signedVoluntaryExit.Exit.Epoch), + ValidatorIndex: uint64ToString(signedVoluntaryExit.Exit.ValidatorIndex), + }, + Signature: hexutil.Encode(signedVoluntaryExit.Signature), + } + jsonSignedVoluntaryExits[index] = jsonSignedVoluntaryExit + } + return jsonSignedVoluntaryExits +} + +func jsonifySignedBeaconBlockHeader(signedBeaconBlockHeader *ethpb.SignedBeaconBlockHeader) *apimiddleware.SignedBeaconBlockHeaderJson { + return &apimiddleware.SignedBeaconBlockHeaderJson{ + Header: &apimiddleware.BeaconBlockHeaderJson{ + BodyRoot: hexutil.Encode(signedBeaconBlockHeader.Header.BodyRoot), + ParentRoot: hexutil.Encode(signedBeaconBlockHeader.Header.ParentRoot), + ProposerIndex: uint64ToString(signedBeaconBlockHeader.Header.ProposerIndex), + Slot: uint64ToString(signedBeaconBlockHeader.Header.Slot), + StateRoot: hexutil.Encode(signedBeaconBlockHeader.Header.StateRoot), + }, + Signature: hexutil.Encode(signedBeaconBlockHeader.Signature), + } +} + +func jsonifyIndexedAttestation(indexedAttestation *ethpb.IndexedAttestation) *apimiddleware.IndexedAttestationJson { + attestingIndices := make([]string, len(indexedAttestation.AttestingIndices)) + for index, attestingIndex := range indexedAttestation.AttestingIndices { + attestingIndex := uint64ToString(attestingIndex) + attestingIndices[index] = attestingIndex + } + + return &apimiddleware.IndexedAttestationJson{ + AttestingIndices: attestingIndices, + Data: jsonifyAttestationData(indexedAttestation.Data), + Signature: hexutil.Encode(indexedAttestation.Signature), + } +} + +func jsonifyAttestationData(attestationData *ethpb.AttestationData) *apimiddleware.AttestationDataJson { + return &apimiddleware.AttestationDataJson{ + BeaconBlockRoot: hexutil.Encode(attestationData.BeaconBlockRoot), + CommitteeIndex: uint64ToString(attestationData.CommitteeIndex), + Slot: uint64ToString(attestationData.Slot), + Source: &apimiddleware.CheckpointJson{ + Epoch: uint64ToString(attestationData.Source.Epoch), + Root: hexutil.Encode(attestationData.Source.Root), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: uint64ToString(attestationData.Target.Epoch), + Root: hexutil.Encode(attestationData.Target.Root), + }, + } +} diff --git a/validator/client/beacon-api/beacon_block_json_helpers_test.go b/validator/client/beacon-api/beacon_block_json_helpers_test.go new file mode 100644 index 0000000000..a1b7050762 --- /dev/null +++ b/validator/client/beacon-api/beacon_block_json_helpers_test.go @@ -0,0 +1,605 @@ +package beacon_api + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v3/testing/assert" +) + +func TestBeaconBlockJsonHelpers_JsonifyTransactions(t *testing.T) { + input := [][]byte{{1}, {2}, {3}, {4}} + + expectedResult := []string{ + hexutil.Encode([]byte{1}), + hexutil.Encode([]byte{2}), + hexutil.Encode([]byte{3}), + hexutil.Encode([]byte{4}), + } + + result := jsonifyTransactions(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyBlsToExecutionChanges(t *testing.T) { + input := []*ethpb.SignedBLSToExecutionChange{ + { + Message: ðpb.BLSToExecutionChange{ + ValidatorIndex: 1, + FromBlsPubkey: []byte{2}, + ToExecutionAddress: []byte{3}, + }, + }, + { + Message: ðpb.BLSToExecutionChange{ + ValidatorIndex: 4, + FromBlsPubkey: []byte{5}, + ToExecutionAddress: []byte{6}, + }, + }, + } + + expectedResult := []*apimiddleware.BLSToExecutionChangeJson{ + { + ValidatorIndex: "1", + FromBLSPubkey: hexutil.Encode([]byte{2}), + ToExecutionAddress: hexutil.Encode([]byte{3}), + }, + { + ValidatorIndex: "4", + FromBLSPubkey: hexutil.Encode([]byte{5}), + ToExecutionAddress: hexutil.Encode([]byte{6}), + }, + } + + result := jsonifyBlsToExecutionChanges(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyEth1Data(t *testing.T) { + input := ðpb.Eth1Data{ + DepositRoot: []byte{1}, + DepositCount: 2, + BlockHash: []byte{3}, + } + + expectedResult := &apimiddleware.Eth1DataJson{ + DepositRoot: hexutil.Encode([]byte{1}), + DepositCount: "2", + BlockHash: hexutil.Encode([]byte{3}), + } + + result := jsonifyEth1Data(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyAttestations(t *testing.T) { + input := []*ethpb.Attestation{ + { + AggregationBits: []byte{1}, + Data: ðpb.AttestationData{ + Slot: 2, + CommitteeIndex: 3, + BeaconBlockRoot: []byte{4}, + Source: ðpb.Checkpoint{ + Epoch: 5, + Root: []byte{6}, + }, + Target: ðpb.Checkpoint{ + Epoch: 7, + Root: []byte{8}, + }, + }, + Signature: []byte{9}, + }, + { + AggregationBits: []byte{10}, + Data: ðpb.AttestationData{ + Slot: 11, + CommitteeIndex: 12, + BeaconBlockRoot: []byte{13}, + Source: ðpb.Checkpoint{ + Epoch: 14, + Root: []byte{15}, + }, + Target: ðpb.Checkpoint{ + Epoch: 16, + Root: []byte{17}, + }, + }, + Signature: []byte{18}, + }, + } + + expectedResult := []*apimiddleware.AttestationJson{ + { + AggregationBits: hexutil.Encode([]byte{1}), + Data: &apimiddleware.AttestationDataJson{ + Slot: "2", + CommitteeIndex: "3", + BeaconBlockRoot: hexutil.Encode([]byte{4}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "5", + Root: hexutil.Encode([]byte{6}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "7", + Root: hexutil.Encode([]byte{8}), + }, + }, + Signature: hexutil.Encode([]byte{9}), + }, + { + AggregationBits: hexutil.Encode([]byte{10}), + Data: &apimiddleware.AttestationDataJson{ + Slot: "11", + CommitteeIndex: "12", + BeaconBlockRoot: hexutil.Encode([]byte{13}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "14", + Root: hexutil.Encode([]byte{15}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "16", + Root: hexutil.Encode([]byte{17}), + }, + }, + Signature: hexutil.Encode([]byte{18}), + }, + } + + result := jsonifyAttestations(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyAttesterSlashings(t *testing.T) { + input := []*ethpb.AttesterSlashing{ + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{1, 2}, + Data: ðpb.AttestationData{ + Slot: 3, + CommitteeIndex: 4, + BeaconBlockRoot: []byte{5}, + Source: ðpb.Checkpoint{ + Epoch: 6, + Root: []byte{7}, + }, + Target: ðpb.Checkpoint{ + Epoch: 8, + Root: []byte{9}, + }, + }, + Signature: []byte{10}, + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{11, 12}, + Data: ðpb.AttestationData{ + Slot: 13, + CommitteeIndex: 14, + BeaconBlockRoot: []byte{15}, + Source: ðpb.Checkpoint{ + Epoch: 16, + Root: []byte{17}, + }, + Target: ðpb.Checkpoint{ + Epoch: 18, + Root: []byte{19}, + }, + }, + Signature: []byte{20}, + }, + }, + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{21, 22}, + Data: ðpb.AttestationData{ + Slot: 23, + CommitteeIndex: 24, + BeaconBlockRoot: []byte{25}, + Source: ðpb.Checkpoint{ + Epoch: 26, + Root: []byte{27}, + }, + Target: ðpb.Checkpoint{ + Epoch: 28, + Root: []byte{29}, + }, + }, + Signature: []byte{30}, + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{31, 32}, + Data: ðpb.AttestationData{ + Slot: 33, + CommitteeIndex: 34, + BeaconBlockRoot: []byte{35}, + Source: ðpb.Checkpoint{ + Epoch: 36, + Root: []byte{37}, + }, + Target: ðpb.Checkpoint{ + Epoch: 38, + Root: []byte{39}, + }, + }, + Signature: []byte{40}, + }, + }, + } + + expectedResult := []*apimiddleware.AttesterSlashingJson{ + { + Attestation_1: &apimiddleware.IndexedAttestationJson{ + AttestingIndices: []string{"1", "2"}, + Data: &apimiddleware.AttestationDataJson{ + Slot: "3", + CommitteeIndex: "4", + BeaconBlockRoot: hexutil.Encode([]byte{5}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "6", + Root: hexutil.Encode([]byte{7}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "8", + Root: hexutil.Encode([]byte{9}), + }, + }, + Signature: hexutil.Encode([]byte{10}), + }, + Attestation_2: &apimiddleware.IndexedAttestationJson{ + AttestingIndices: []string{"11", "12"}, + Data: &apimiddleware.AttestationDataJson{ + Slot: "13", + CommitteeIndex: "14", + BeaconBlockRoot: hexutil.Encode([]byte{15}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "16", + Root: hexutil.Encode([]byte{17}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "18", + Root: hexutil.Encode([]byte{19}), + }, + }, + Signature: hexutil.Encode([]byte{20}), + }, + }, + { + Attestation_1: &apimiddleware.IndexedAttestationJson{ + AttestingIndices: []string{"21", "22"}, + Data: &apimiddleware.AttestationDataJson{ + Slot: "23", + CommitteeIndex: "24", + BeaconBlockRoot: hexutil.Encode([]byte{25}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "26", + Root: hexutil.Encode([]byte{27}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "28", + Root: hexutil.Encode([]byte{29}), + }, + }, + Signature: hexutil.Encode([]byte{30}), + }, + Attestation_2: &apimiddleware.IndexedAttestationJson{ + AttestingIndices: []string{"31", "32"}, + Data: &apimiddleware.AttestationDataJson{ + Slot: "33", + CommitteeIndex: "34", + BeaconBlockRoot: hexutil.Encode([]byte{35}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "36", + Root: hexutil.Encode([]byte{37}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "38", + Root: hexutil.Encode([]byte{39}), + }, + }, + Signature: hexutil.Encode([]byte{40}), + }, + }, + } + + result := jsonifyAttesterSlashings(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyDeposits(t *testing.T) { + input := []*ethpb.Deposit{ + { + Proof: [][]byte{{1}, {2}}, + Data: ðpb.Deposit_Data{ + PublicKey: []byte{3}, + WithdrawalCredentials: []byte{4}, + Amount: 5, + Signature: []byte{6}, + }, + }, + { + Proof: [][]byte{ + {7}, + {8}, + }, + Data: ðpb.Deposit_Data{ + PublicKey: []byte{9}, + WithdrawalCredentials: []byte{10}, + Amount: 11, + Signature: []byte{12}, + }, + }, + } + + expectedResult := []*apimiddleware.DepositJson{ + { + Proof: []string{ + hexutil.Encode([]byte{1}), + hexutil.Encode([]byte{2}), + }, + Data: &apimiddleware.Deposit_DataJson{ + PublicKey: hexutil.Encode([]byte{3}), + WithdrawalCredentials: hexutil.Encode([]byte{4}), + Amount: "5", + Signature: hexutil.Encode([]byte{6}), + }, + }, + { + Proof: []string{ + hexutil.Encode([]byte{7}), + hexutil.Encode([]byte{8}), + }, + Data: &apimiddleware.Deposit_DataJson{ + PublicKey: hexutil.Encode([]byte{9}), + WithdrawalCredentials: hexutil.Encode([]byte{10}), + Amount: "11", + Signature: hexutil.Encode([]byte{12}), + }, + }, + } + + result := jsonifyDeposits(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyProposerSlashings(t *testing.T) { + input := []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: []byte{3}, + StateRoot: []byte{4}, + BodyRoot: []byte{5}, + }, + Signature: []byte{6}, + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 7, + ProposerIndex: 8, + ParentRoot: []byte{9}, + StateRoot: []byte{10}, + BodyRoot: []byte{11}, + }, + Signature: []byte{12}, + }, + }, + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 13, + ProposerIndex: 14, + ParentRoot: []byte{15}, + StateRoot: []byte{16}, + BodyRoot: []byte{17}, + }, + Signature: []byte{18}, + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 19, + ProposerIndex: 20, + ParentRoot: []byte{21}, + StateRoot: []byte{22}, + BodyRoot: []byte{23}, + }, + Signature: []byte{24}, + }, + }, + } + + expectedResult := []*apimiddleware.ProposerSlashingJson{ + { + Header_1: &apimiddleware.SignedBeaconBlockHeaderJson{ + Header: &apimiddleware.BeaconBlockHeaderJson{ + Slot: "1", + ProposerIndex: "2", + ParentRoot: hexutil.Encode([]byte{3}), + StateRoot: hexutil.Encode([]byte{4}), + BodyRoot: hexutil.Encode([]byte{5}), + }, + Signature: hexutil.Encode([]byte{6}), + }, + Header_2: &apimiddleware.SignedBeaconBlockHeaderJson{ + Header: &apimiddleware.BeaconBlockHeaderJson{ + Slot: "7", + ProposerIndex: "8", + ParentRoot: hexutil.Encode([]byte{9}), + StateRoot: hexutil.Encode([]byte{10}), + BodyRoot: hexutil.Encode([]byte{11}), + }, + Signature: hexutil.Encode([]byte{12}), + }, + }, + { + Header_1: &apimiddleware.SignedBeaconBlockHeaderJson{ + Header: &apimiddleware.BeaconBlockHeaderJson{ + Slot: "13", + ProposerIndex: "14", + ParentRoot: hexutil.Encode([]byte{15}), + StateRoot: hexutil.Encode([]byte{16}), + BodyRoot: hexutil.Encode([]byte{17}), + }, + Signature: hexutil.Encode([]byte{18}), + }, + Header_2: &apimiddleware.SignedBeaconBlockHeaderJson{ + Header: &apimiddleware.BeaconBlockHeaderJson{ + Slot: "19", + ProposerIndex: "20", + ParentRoot: hexutil.Encode([]byte{21}), + StateRoot: hexutil.Encode([]byte{22}), + BodyRoot: hexutil.Encode([]byte{23}), + }, + Signature: hexutil.Encode([]byte{24}), + }, + }, + } + + result := jsonifyProposerSlashings(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifySignedVoluntaryExits(t *testing.T) { + input := []*ethpb.SignedVoluntaryExit{ + { + Exit: ðpb.VoluntaryExit{ + Epoch: 1, + ValidatorIndex: 2, + }, + Signature: []byte{3}, + }, + { + Exit: ðpb.VoluntaryExit{ + Epoch: 4, + ValidatorIndex: 5, + }, + Signature: []byte{6}, + }, + } + + expectedResult := []*apimiddleware.SignedVoluntaryExitJson{ + { + Exit: &apimiddleware.VoluntaryExitJson{ + Epoch: "1", + ValidatorIndex: "2", + }, + Signature: hexutil.Encode([]byte{3}), + }, + { + Exit: &apimiddleware.VoluntaryExitJson{ + Epoch: "4", + ValidatorIndex: "5", + }, + Signature: hexutil.Encode([]byte{6}), + }, + } + + result := jsonifySignedVoluntaryExits(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifySignedBeaconBlockHeader(t *testing.T) { + input := ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: []byte{3}, + StateRoot: []byte{4}, + BodyRoot: []byte{5}, + }, + Signature: []byte{6}, + } + + expectedResult := &apimiddleware.SignedBeaconBlockHeaderJson{ + Header: &apimiddleware.BeaconBlockHeaderJson{ + Slot: "1", + ProposerIndex: "2", + ParentRoot: hexutil.Encode([]byte{3}), + StateRoot: hexutil.Encode([]byte{4}), + BodyRoot: hexutil.Encode([]byte{5}), + }, + Signature: hexutil.Encode([]byte{6}), + } + + result := jsonifySignedBeaconBlockHeader(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyIndexedAttestation(t *testing.T) { + input := ðpb.IndexedAttestation{ + AttestingIndices: []uint64{1, 2}, + Data: ðpb.AttestationData{ + Slot: 3, + CommitteeIndex: 4, + BeaconBlockRoot: []byte{5}, + Source: ðpb.Checkpoint{ + Epoch: 6, + Root: []byte{7}, + }, + Target: ðpb.Checkpoint{ + Epoch: 8, + Root: []byte{9}, + }, + }, + Signature: []byte{10}, + } + + expectedResult := &apimiddleware.IndexedAttestationJson{ + AttestingIndices: []string{"1", "2"}, + Data: &apimiddleware.AttestationDataJson{ + Slot: "3", + CommitteeIndex: "4", + BeaconBlockRoot: hexutil.Encode([]byte{5}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "6", + Root: hexutil.Encode([]byte{7}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "8", + Root: hexutil.Encode([]byte{9}), + }, + }, + Signature: hexutil.Encode([]byte{10}), + } + + result := jsonifyIndexedAttestation(input) + assert.DeepEqual(t, expectedResult, result) +} + +func TestBeaconBlockJsonHelpers_JsonifyAttestationData(t *testing.T) { + input := ðpb.AttestationData{ + Slot: 1, + CommitteeIndex: 2, + BeaconBlockRoot: []byte{3}, + Source: ðpb.Checkpoint{ + Epoch: 4, + Root: []byte{5}, + }, + Target: ðpb.Checkpoint{ + Epoch: 6, + Root: []byte{7}, + }, + } + + expectedResult := &apimiddleware.AttestationDataJson{ + Slot: "1", + CommitteeIndex: "2", + BeaconBlockRoot: hexutil.Encode([]byte{3}), + Source: &apimiddleware.CheckpointJson{ + Epoch: "4", + Root: hexutil.Encode([]byte{5}), + }, + Target: &apimiddleware.CheckpointJson{ + Epoch: "6", + Root: hexutil.Encode([]byte{7}), + }, + } + + result := jsonifyAttestationData(input) + assert.DeepEqual(t, expectedResult, result) +} diff --git a/validator/client/beacon-api/json_rest_handler.go b/validator/client/beacon-api/json_rest_handler.go index e5c634fd43..dfe0b5ca65 100644 --- a/validator/client/beacon-api/json_rest_handler.go +++ b/validator/client/beacon-api/json_rest_handler.go @@ -1,6 +1,7 @@ package beacon_api import ( + "bytes" "encoding/json" "net/http" @@ -10,6 +11,7 @@ import ( type jsonRestHandler interface { GetRestJsonResponse(query string, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) + PostRestJson(apiEndpoint string, headers map[string]string, data *bytes.Buffer, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) } type beaconApiJsonRestHandler struct { @@ -35,17 +37,51 @@ func (c beaconApiJsonRestHandler) GetRestJsonResponse(apiEndpoint string, respon } }() + return decodeJsonResp(resp, responseJson) +} + +// PostRestJson sends a POST requests to apiEndpoint and decodes the response body as a JSON object into responseJson. If responseJson +// is nil, nothing is decoded. If an HTTP error is returned, the body is decoded as a DefaultErrorJson JSON object instead and returned +// as the first return value. +func (c beaconApiJsonRestHandler) PostRestJson(apiEndpoint string, headers map[string]string, data *bytes.Buffer, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) { + if data == nil { + return nil, errors.New("POST data is nil") + } + + url := c.host + apiEndpoint + req, err := http.NewRequest("POST", url, data) + + for headerKey, headerValue := range headers { + req.Header.Set(headerKey, headerValue) + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "failed to send POST data to REST endpoint %s", url) + } + defer func() { + if err = resp.Body.Close(); err != nil { + return + } + }() + + return decodeJsonResp(resp, responseJson) +} + +func decodeJsonResp(resp *http.Response, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) { if resp.StatusCode != http.StatusOK { errorJson := &apimiddleware.DefaultErrorJson{} if err := json.NewDecoder(resp.Body).Decode(errorJson); err != nil { - return nil, errors.Wrapf(err, "failed to decode error json for %s", url) + return nil, errors.Wrapf(err, "failed to decode error json for %s", resp.Request.URL) } return errorJson, errors.Errorf("error %d: %s", errorJson.Code, errorJson.Message) } - if err := json.NewDecoder(resp.Body).Decode(responseJson); err != nil { - return nil, errors.Wrapf(err, "failed to decode response json for %s", url) + if responseJson != nil { + if err := json.NewDecoder(resp.Body).Decode(responseJson); err != nil { + return nil, errors.Wrapf(err, "failed to decode response json for %s", resp.Request.URL) + } } return nil, nil diff --git a/validator/client/beacon-api/json_rest_handler_test.go b/validator/client/beacon-api/json_rest_handler_test.go index 873d1df6ff..4ef5af301b 100644 --- a/validator/client/beacon-api/json_rest_handler_test.go +++ b/validator/client/beacon-api/json_rest_handler_test.go @@ -1,7 +1,9 @@ package beacon_api import ( + "bytes" "encoding/json" + "io" "net/http" "net/http/httptest" "testing" @@ -153,6 +155,203 @@ func TestGetRestJsonResponse_Error(t *testing.T) { } } +func TestPostRestJson_Valid(t *testing.T) { + const endpoint = "/example/rest/api/endpoint" + dataBytes := []byte{1, 2, 3, 4, 5} + + genesisJson := &rpcmiddleware.GenesisResponseJson{ + Data: &rpcmiddleware.GenesisResponse_GenesisJson{ + GenesisTime: "123", + GenesisValidatorsRoot: "0x456", + GenesisForkVersion: "0x789", + }, + } + + testCases := []struct { + name string + headers map[string]string + data *bytes.Buffer + responseJson interface{} + }{ + { + name: "nil headers", + headers: nil, + data: bytes.NewBuffer(dataBytes), + responseJson: &rpcmiddleware.GenesisResponseJson{}, + }, + { + name: "empty headers", + headers: map[string]string{}, + data: bytes.NewBuffer(dataBytes), + responseJson: &rpcmiddleware.GenesisResponseJson{}, + }, + { + name: "nil response json", + headers: map[string]string{"DummyHeaderKey": "DummyHeaderValue"}, + data: bytes.NewBuffer(dataBytes), + responseJson: nil, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) { + // Make sure the request headers have been set + for headerKey, headerValue := range testCase.headers { + assert.Equal(t, headerValue, r.Header.Get(headerKey)) + } + + // Make sure the data matches + receivedBytes := make([]byte, len(dataBytes)) + numBytes, err := r.Body.Read(receivedBytes) + assert.Equal(t, io.EOF, err) + assert.Equal(t, len(dataBytes), numBytes) + assert.DeepEqual(t, dataBytes, receivedBytes) + + marshalledJson, err := json.Marshal(genesisJson) + require.NoError(t, err) + + _, err = w.Write(marshalledJson) + require.NoError(t, err) + }) + server := httptest.NewServer(mux) + defer server.Close() + + jsonRestHandler := beaconApiJsonRestHandler{ + httpClient: http.Client{Timeout: time.Second * 5}, + host: server.URL, + } + + _, err := jsonRestHandler.PostRestJson( + endpoint, + testCase.headers, + testCase.data, + testCase.responseJson, + ) + + assert.NoError(t, err) + + if testCase.responseJson != nil { + assert.DeepEqual(t, genesisJson, testCase.responseJson) + } + }) + } +} + +func TestPostRestJson_Error(t *testing.T) { + const endpoint = "/example/rest/api/endpoint" + + testCases := []struct { + name string + funcHandler func(w http.ResponseWriter, r *http.Request) + expectedErrorJson *apimiddleware.DefaultErrorJson + expectedErrorMessage string + timeout time.Duration + responseJson *rpcmiddleware.GenesisResponseJson + data *bytes.Buffer + }{ + { + name: "nil POST data", + funcHandler: httpErrorJsonHandler(http.StatusNotFound, "Not found"), + expectedErrorMessage: "POST data is nil", + timeout: time.Second * 5, + data: nil, + }, + { + name: "400 error", + funcHandler: httpErrorJsonHandler(http.StatusBadRequest, "Bad request"), + expectedErrorMessage: "error 400: Bad request", + expectedErrorJson: &apimiddleware.DefaultErrorJson{ + Code: http.StatusBadRequest, + Message: "Bad request", + }, + timeout: time.Second * 5, + responseJson: &rpcmiddleware.GenesisResponseJson{}, + data: &bytes.Buffer{}, + }, + { + name: "404 error", + funcHandler: httpErrorJsonHandler(http.StatusNotFound, "Not found"), + expectedErrorMessage: "error 404: Not found", + expectedErrorJson: &apimiddleware.DefaultErrorJson{ + Code: http.StatusNotFound, + Message: "Not found", + }, + timeout: time.Second * 5, + data: &bytes.Buffer{}, + }, + { + name: "500 error", + funcHandler: httpErrorJsonHandler(http.StatusInternalServerError, "Internal server error"), + expectedErrorMessage: "error 500: Internal server error", + expectedErrorJson: &apimiddleware.DefaultErrorJson{ + Code: http.StatusInternalServerError, + Message: "Internal server error", + }, + timeout: time.Second * 5, + data: &bytes.Buffer{}, + }, + { + name: "999 error", + funcHandler: httpErrorJsonHandler(999, "Invalid error"), + expectedErrorMessage: "error 999: Invalid error", + expectedErrorJson: &apimiddleware.DefaultErrorJson{ + Code: 999, + Message: "Invalid error", + }, + timeout: time.Second * 5, + data: &bytes.Buffer{}, + }, + { + name: "bad error json formatting", + funcHandler: invalidJsonErrHandler, + expectedErrorMessage: "failed to decode error json", + timeout: time.Second * 5, + data: &bytes.Buffer{}, + }, + { + name: "bad response json formatting", + funcHandler: invalidJsonResponseHandler, + expectedErrorMessage: "failed to decode response json", + timeout: time.Second * 5, + responseJson: &rpcmiddleware.GenesisResponseJson{}, + data: &bytes.Buffer{}, + }, + { + name: "timeout", + funcHandler: httpErrorJsonHandler(http.StatusNotFound, "Not found"), + expectedErrorMessage: "failed to send POST data to REST endpoint", + timeout: 1, + data: &bytes.Buffer{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc(endpoint, testCase.funcHandler) + server := httptest.NewServer(mux) + defer server.Close() + + jsonRestHandler := beaconApiJsonRestHandler{ + httpClient: http.Client{Timeout: testCase.timeout}, + host: server.URL, + } + + errorJson, err := jsonRestHandler.PostRestJson( + endpoint, + map[string]string{}, + testCase.data, + testCase.responseJson, + ) + + assert.ErrorContains(t, testCase.expectedErrorMessage, err) + assert.DeepEqual(t, testCase.expectedErrorJson, errorJson) + }) + } +} + func httpErrorJsonHandler(statusCode int, errorMessage string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, _ *http.Request) { errorJson := &apimiddleware.DefaultErrorJson{ diff --git a/validator/client/beacon-api/mock/json_rest_handler_mock.go b/validator/client/beacon-api/mock/json_rest_handler_mock.go index 1352145bc0..6abf7b0020 100644 --- a/validator/client/beacon-api/mock/json_rest_handler_mock.go +++ b/validator/client/beacon-api/mock/json_rest_handler_mock.go @@ -5,6 +5,7 @@ package mock import ( + bytes "bytes" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -48,3 +49,18 @@ func (mr *MockjsonRestHandlerMockRecorder) GetRestJsonResponse(query, responseJs mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRestJsonResponse", reflect.TypeOf((*MockjsonRestHandler)(nil).GetRestJsonResponse), query, responseJson) } + +// PostRestJson mocks base method. +func (m *MockjsonRestHandler) PostRestJson(apiEndpoint string, headers map[string]string, data *bytes.Buffer, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PostRestJson", apiEndpoint, headers, data, responseJson) + ret0, _ := ret[0].(*apimiddleware.DefaultErrorJson) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PostRestJson indicates an expected call of PostRestJson. +func (mr *MockjsonRestHandlerMockRecorder) PostRestJson(apiEndpoint, headers, data, responseJson interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostRestJson", reflect.TypeOf((*MockjsonRestHandler)(nil).PostRestJson), apiEndpoint, headers, data, responseJson) +} diff --git a/validator/client/beacon-api/propose_beacon_block.go b/validator/client/beacon-api/propose_beacon_block.go new file mode 100644 index 0000000000..894300945d --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block.go @@ -0,0 +1,292 @@ +package beacon_api + +import ( + "bytes" + "encoding/json" + "net/http" + + "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/encoding/bytesutil" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" +) + +func (c beaconApiValidatorClient) proposeBeaconBlock(in *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) { + var consensusVersion string + var beaconBlockRoot [32]byte + + var err error + var marshalledSignedBeaconBlockJson []byte + blinded := false + + switch blockType := in.Block.(type) { + case *ethpb.GenericSignedBeaconBlock_Phase0: + consensusVersion = "phase0" + beaconBlockRoot, err = blockType.Phase0.Block.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute block root for phase0 beacon block") + } + + marshalledSignedBeaconBlockJson, err = marshallBeaconBlockPhase0(blockType.Phase0) + if err != nil { + return nil, errors.Wrap(err, "failed to marshall phase0 beacon block") + } + case *ethpb.GenericSignedBeaconBlock_Altair: + consensusVersion = "altair" + beaconBlockRoot, err = blockType.Altair.Block.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute block root for altair beacon block") + } + + marshalledSignedBeaconBlockJson, err = marshallBeaconBlockAltair(blockType.Altair) + if err != nil { + return nil, errors.Wrap(err, "failed to marshall altair beacon block") + } + case *ethpb.GenericSignedBeaconBlock_Bellatrix: + consensusVersion = "bellatrix" + beaconBlockRoot, err = blockType.Bellatrix.Block.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute block root for bellatrix beacon block") + } + + marshalledSignedBeaconBlockJson, err = marshallBeaconBlockBellatrix(blockType.Bellatrix) + if err != nil { + return nil, errors.Wrap(err, "failed to marshall bellatrix beacon block") + } + case *ethpb.GenericSignedBeaconBlock_BlindedBellatrix: + blinded = true + consensusVersion = "bellatrix" + beaconBlockRoot, err = blockType.BlindedBellatrix.Block.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute block root for blinded bellatrix beacon block") + } + + marshalledSignedBeaconBlockJson, err = marshallBeaconBlockBlindedBellatrix(blockType.BlindedBellatrix) + if err != nil { + return nil, errors.Wrap(err, "failed to marshall blinded bellatrix beacon block") + } + case *ethpb.GenericSignedBeaconBlock_BlindedCapella: + blinded = true + consensusVersion = "capella" + beaconBlockRoot, err = blockType.BlindedCapella.Block.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "failed to compute block root for blinded capella beacon block") + } + + marshalledSignedBeaconBlockJson, err = marshallBeaconBlockBlindedCapella(blockType.BlindedCapella) + if err != nil { + return nil, errors.Wrap(err, "failed to marshall blinded capella beacon block") + } + case *ethpb.GenericSignedBeaconBlock_Capella: + return nil, errors.Errorf("Capella blocks are not supported yet") + default: + return nil, errors.Errorf("unsupported block type %T", in.Block) + } + + var endpoint string + + if blinded { + endpoint = "/eth/v1/beacon/blinded_blocks" + } else { + endpoint = "/eth/v1/beacon/blocks" + } + + headers := map[string]string{"Eth-Consensus-Version": consensusVersion} + if httpError, err := c.jsonRestHandler.PostRestJson(endpoint, headers, bytes.NewBuffer(marshalledSignedBeaconBlockJson), nil); err != nil { + if httpError != nil && httpError.Code == http.StatusAccepted { + // Error 202 means that the block was successfully broadcasted, but validation failed + return nil, errors.Wrap(err, "block was successfully broadcasted but failed validation") + } + + return nil, errors.Wrap(err, "failed to send POST data to REST endpoint") + } + + return ðpb.ProposeResponse{BlockRoot: beaconBlockRoot[:]}, nil +} + +func marshallBeaconBlockPhase0(block *ethpb.SignedBeaconBlock) ([]byte, error) { + signedBeaconBlockJson := &apimiddleware.SignedBeaconBlockContainerJson{ + Signature: hexutil.Encode(block.Signature), + Message: &apimiddleware.BeaconBlockJson{ + Body: &apimiddleware.BeaconBlockBodyJson{ + Attestations: jsonifyAttestations(block.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(block.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(block.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(block.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(block.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(block.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(block.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(block.Block.Body.VoluntaryExits), + }, + ParentRoot: hexutil.Encode(block.Block.ParentRoot), + ProposerIndex: uint64ToString(block.Block.ProposerIndex), + Slot: uint64ToString(block.Block.Slot), + StateRoot: hexutil.Encode(block.Block.StateRoot), + }, + } + + return json.Marshal(signedBeaconBlockJson) +} + +func marshallBeaconBlockAltair(block *ethpb.SignedBeaconBlockAltair) ([]byte, error) { + signedBeaconBlockAltairJson := &apimiddleware.SignedBeaconBlockAltairContainerJson{ + Signature: hexutil.Encode(block.Signature), + Message: &apimiddleware.BeaconBlockAltairJson{ + ParentRoot: hexutil.Encode(block.Block.ParentRoot), + ProposerIndex: uint64ToString(block.Block.ProposerIndex), + Slot: uint64ToString(block.Block.Slot), + StateRoot: hexutil.Encode(block.Block.StateRoot), + Body: &apimiddleware.BeaconBlockBodyAltairJson{ + Attestations: jsonifyAttestations(block.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(block.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(block.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(block.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(block.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(block.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(block.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(block.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + }, + }, + } + + return json.Marshal(signedBeaconBlockAltairJson) +} + +func marshallBeaconBlockBellatrix(block *ethpb.SignedBeaconBlockBellatrix) ([]byte, error) { + signedBeaconBlockBellatrixJson := &apimiddleware.SignedBeaconBlockBellatrixContainerJson{ + Signature: hexutil.Encode(block.Signature), + Message: &apimiddleware.BeaconBlockBellatrixJson{ + ParentRoot: hexutil.Encode(block.Block.ParentRoot), + ProposerIndex: uint64ToString(block.Block.ProposerIndex), + Slot: uint64ToString(block.Block.Slot), + StateRoot: hexutil.Encode(block.Block.StateRoot), + Body: &apimiddleware.BeaconBlockBodyBellatrixJson{ + Attestations: jsonifyAttestations(block.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(block.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(block.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(block.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(block.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(block.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(block.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(block.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + ExecutionPayload: &apimiddleware.ExecutionPayloadJson{ + BaseFeePerGas: bytesutil.LittleEndianBytesToBigInt(block.Block.Body.ExecutionPayload.BaseFeePerGas).String(), + BlockHash: hexutil.Encode(block.Block.Body.ExecutionPayload.BlockHash), + BlockNumber: uint64ToString(block.Block.Body.ExecutionPayload.BlockNumber), + ExtraData: hexutil.Encode(block.Block.Body.ExecutionPayload.ExtraData), + FeeRecipient: hexutil.Encode(block.Block.Body.ExecutionPayload.FeeRecipient), + GasLimit: uint64ToString(block.Block.Body.ExecutionPayload.GasLimit), + GasUsed: uint64ToString(block.Block.Body.ExecutionPayload.GasUsed), + LogsBloom: hexutil.Encode(block.Block.Body.ExecutionPayload.LogsBloom), + ParentHash: hexutil.Encode(block.Block.Body.ExecutionPayload.ParentHash), + PrevRandao: hexutil.Encode(block.Block.Body.ExecutionPayload.PrevRandao), + ReceiptsRoot: hexutil.Encode(block.Block.Body.ExecutionPayload.ReceiptsRoot), + StateRoot: hexutil.Encode(block.Block.Body.ExecutionPayload.StateRoot), + TimeStamp: uint64ToString(block.Block.Body.ExecutionPayload.Timestamp), + Transactions: jsonifyTransactions(block.Block.Body.ExecutionPayload.Transactions), + }, + }, + }, + } + + return json.Marshal(signedBeaconBlockBellatrixJson) +} + +func marshallBeaconBlockBlindedBellatrix(block *ethpb.SignedBlindedBeaconBlockBellatrix) ([]byte, error) { + signedBeaconBlockBellatrixJson := &apimiddleware.SignedBlindedBeaconBlockBellatrixContainerJson{ + Signature: hexutil.Encode(block.Signature), + Message: &apimiddleware.BlindedBeaconBlockBellatrixJson{ + ParentRoot: hexutil.Encode(block.Block.ParentRoot), + ProposerIndex: uint64ToString(block.Block.ProposerIndex), + Slot: uint64ToString(block.Block.Slot), + StateRoot: hexutil.Encode(block.Block.StateRoot), + Body: &apimiddleware.BlindedBeaconBlockBodyBellatrixJson{ + Attestations: jsonifyAttestations(block.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(block.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(block.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(block.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(block.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(block.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(block.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(block.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + ExecutionPayloadHeader: &apimiddleware.ExecutionPayloadHeaderJson{ + BaseFeePerGas: bytesutil.LittleEndianBytesToBigInt(block.Block.Body.ExecutionPayloadHeader.BaseFeePerGas).String(), + BlockHash: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.BlockHash), + BlockNumber: uint64ToString(block.Block.Body.ExecutionPayloadHeader.BlockNumber), + ExtraData: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.ExtraData), + FeeRecipient: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.FeeRecipient), + GasLimit: uint64ToString(block.Block.Body.ExecutionPayloadHeader.GasLimit), + GasUsed: uint64ToString(block.Block.Body.ExecutionPayloadHeader.GasUsed), + LogsBloom: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.LogsBloom), + ParentHash: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.ParentHash), + PrevRandao: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.PrevRandao), + ReceiptsRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.ReceiptsRoot), + StateRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.StateRoot), + TimeStamp: uint64ToString(block.Block.Body.ExecutionPayloadHeader.Timestamp), + TransactionsRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.TransactionsRoot), + }, + }, + }, + } + + return json.Marshal(signedBeaconBlockBellatrixJson) +} + +func marshallBeaconBlockBlindedCapella(block *ethpb.SignedBlindedBeaconBlockCapella) ([]byte, error) { + signedBeaconBlockCapellaJson := &apimiddleware.SignedBlindedBeaconBlockCapellaContainerJson{ + Signature: hexutil.Encode(block.Signature), + Message: &apimiddleware.BlindedBeaconBlockCapellaJson{ + ParentRoot: hexutil.Encode(block.Block.ParentRoot), + ProposerIndex: uint64ToString(block.Block.ProposerIndex), + Slot: uint64ToString(block.Block.Slot), + StateRoot: hexutil.Encode(block.Block.StateRoot), + Body: &apimiddleware.BlindedBeaconBlockBodyCapellaJson{ + Attestations: jsonifyAttestations(block.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(block.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(block.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(block.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(block.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(block.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(block.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(block.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(block.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + ExecutionPayloadHeader: &apimiddleware.ExecutionPayloadHeaderCapellaJson{ + BaseFeePerGas: bytesutil.LittleEndianBytesToBigInt(block.Block.Body.ExecutionPayloadHeader.BaseFeePerGas).String(), + BlockHash: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.BlockHash), + BlockNumber: uint64ToString(block.Block.Body.ExecutionPayloadHeader.BlockNumber), + ExtraData: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.ExtraData), + FeeRecipient: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.FeeRecipient), + GasLimit: uint64ToString(block.Block.Body.ExecutionPayloadHeader.GasLimit), + GasUsed: uint64ToString(block.Block.Body.ExecutionPayloadHeader.GasUsed), + LogsBloom: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.LogsBloom), + ParentHash: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.ParentHash), + PrevRandao: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.PrevRandao), + ReceiptsRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.ReceiptsRoot), + StateRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.StateRoot), + TimeStamp: uint64ToString(block.Block.Body.ExecutionPayloadHeader.Timestamp), + TransactionsRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.TransactionsRoot), + WithdrawalsRoot: hexutil.Encode(block.Block.Body.ExecutionPayloadHeader.WithdrawalsRoot), + }, + BLSToExecutionChanges: jsonifyBlsToExecutionChanges(block.Block.Body.BlsToExecutionChanges), + }, + }, + } + + return json.Marshal(signedBeaconBlockCapellaJson) +} diff --git a/validator/client/beacon-api/propose_beacon_block_altair_test.go b/validator/client/beacon-api/propose_beacon_block_altair_test.go new file mode 100644 index 0000000000..9c0e4356fd --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block_altair_test.go @@ -0,0 +1,293 @@ +package beacon_api + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/golang/mock/gomock" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + 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 TestProposeBeaconBlock_Altair(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + altairBlock := generateSignedAltairBlock() + + genericSignedBlock := ðpb.GenericSignedBeaconBlock{} + genericSignedBlock.Block = altairBlock + + jsonAltairBlock := &apimiddleware.SignedBeaconBlockAltairContainerJson{ + Signature: hexutil.Encode(altairBlock.Altair.Signature), + Message: &apimiddleware.BeaconBlockAltairJson{ + ParentRoot: hexutil.Encode(altairBlock.Altair.Block.ParentRoot), + ProposerIndex: uint64ToString(altairBlock.Altair.Block.ProposerIndex), + Slot: uint64ToString(altairBlock.Altair.Block.Slot), + StateRoot: hexutil.Encode(altairBlock.Altair.Block.StateRoot), + Body: &apimiddleware.BeaconBlockBodyAltairJson{ + Attestations: jsonifyAttestations(altairBlock.Altair.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(altairBlock.Altair.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(altairBlock.Altair.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(altairBlock.Altair.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(altairBlock.Altair.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(altairBlock.Altair.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(altairBlock.Altair.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(altairBlock.Altair.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(altairBlock.Altair.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(altairBlock.Altair.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + }, + }, + } + + marshalledBlock, err := json.Marshal(jsonAltairBlock) + require.NoError(t, err) + + // Make sure that what we send in the POST body is the marshalled version of the protobuf block + headers := map[string]string{"Eth-Consensus-Version": "altair"} + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blocks", + headers, + bytes.NewBuffer(marshalledBlock), + nil, + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := altairBlock.Altair.Block.HashTreeRoot() + require.NoError(t, err) + + // Make sure that the block root is set + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) +} + +func generateSignedAltairBlock() *ethpb.GenericSignedBeaconBlock_Altair { + return ðpb.GenericSignedBeaconBlock_Altair{ + Altair: ðpb.SignedBeaconBlockAltair{ + Block: ðpb.BeaconBlockAltair{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: test_helpers.FillByteSlice(32, 3), + StateRoot: test_helpers.FillByteSlice(32, 4), + Body: ðpb.BeaconBlockBodyAltair{ + RandaoReveal: test_helpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: test_helpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: test_helpers.FillByteSlice(32, 8), + }, + Graffiti: test_helpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: test_helpers.FillByteSlice(32, 12), + StateRoot: test_helpers.FillByteSlice(32, 13), + BodyRoot: test_helpers.FillByteSlice(32, 14), + }, + Signature: test_helpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: test_helpers.FillByteSlice(32, 18), + StateRoot: test_helpers.FillByteSlice(32, 19), + BodyRoot: test_helpers.FillByteSlice(32, 20), + }, + Signature: test_helpers.FillByteSlice(96, 21), + }, + }, + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 22, + ProposerIndex: 23, + ParentRoot: test_helpers.FillByteSlice(32, 24), + StateRoot: test_helpers.FillByteSlice(32, 25), + BodyRoot: test_helpers.FillByteSlice(32, 26), + }, + Signature: test_helpers.FillByteSlice(96, 27), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 28, + ProposerIndex: 29, + ParentRoot: test_helpers.FillByteSlice(32, 30), + StateRoot: test_helpers.FillByteSlice(32, 31), + BodyRoot: test_helpers.FillByteSlice(32, 32), + }, + Signature: test_helpers.FillByteSlice(96, 33), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{34, 35}, + Data: ðpb.AttestationData{ + Slot: 36, + CommitteeIndex: 37, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 39, + Root: test_helpers.FillByteSlice(32, 40), + }, + Target: ðpb.Checkpoint{ + Epoch: 41, + Root: test_helpers.FillByteSlice(32, 42), + }, + }, + Signature: test_helpers.FillByteSlice(96, 43), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{44, 45}, + Data: ðpb.AttestationData{ + Slot: 46, + CommitteeIndex: 47, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 49, + Root: test_helpers.FillByteSlice(32, 50), + }, + Target: ðpb.Checkpoint{ + Epoch: 51, + Root: test_helpers.FillByteSlice(32, 52), + }, + }, + Signature: test_helpers.FillByteSlice(96, 53), + }, + }, + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{54, 55}, + Data: ðpb.AttestationData{ + Slot: 56, + CommitteeIndex: 57, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 59, + Root: test_helpers.FillByteSlice(32, 60), + }, + Target: ðpb.Checkpoint{ + Epoch: 61, + Root: test_helpers.FillByteSlice(32, 62), + }, + }, + Signature: test_helpers.FillByteSlice(96, 63), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{64, 65}, + Data: ðpb.AttestationData{ + Slot: 66, + CommitteeIndex: 67, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 69, + Root: test_helpers.FillByteSlice(32, 70), + }, + Target: ðpb.Checkpoint{ + Epoch: 71, + Root: test_helpers.FillByteSlice(32, 72), + }, + }, + Signature: test_helpers.FillByteSlice(96, 73), + }, + }, + }, + Attestations: []*ethpb.Attestation{ + { + AggregationBits: test_helpers.FillByteSlice(4, 74), + Data: ðpb.AttestationData{ + Slot: 75, + CommitteeIndex: 76, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 78, + Root: test_helpers.FillByteSlice(32, 79), + }, + Target: ðpb.Checkpoint{ + Epoch: 80, + Root: test_helpers.FillByteSlice(32, 81), + }, + }, + Signature: test_helpers.FillByteSlice(96, 82), + }, + { + AggregationBits: test_helpers.FillByteSlice(4, 83), + Data: ðpb.AttestationData{ + Slot: 84, + CommitteeIndex: 85, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 87, + Root: test_helpers.FillByteSlice(32, 88), + }, + Target: ðpb.Checkpoint{ + Epoch: 89, + Root: test_helpers.FillByteSlice(32, 90), + }, + }, + Signature: test_helpers.FillByteSlice(96, 91), + }, + }, + Deposits: []*ethpb.Deposit{ + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 92)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 94), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 95), + Amount: 96, + Signature: test_helpers.FillByteSlice(96, 97), + }, + }, + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 98)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 100), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 101), + Amount: 102, + Signature: test_helpers.FillByteSlice(96, 103), + }, + }, + }, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{ + { + Exit: ðpb.VoluntaryExit{ + Epoch: 104, + ValidatorIndex: 105, + }, + Signature: test_helpers.FillByteSlice(96, 106), + }, + { + Exit: ðpb.VoluntaryExit{ + Epoch: 107, + ValidatorIndex: 108, + }, + Signature: test_helpers.FillByteSlice(96, 109), + }, + }, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: test_helpers.FillByteSlice(64, 110), + SyncCommitteeSignature: test_helpers.FillByteSlice(96, 111), + }, + }, + }, + Signature: test_helpers.FillByteSlice(96, 112), + }, + } +} diff --git a/validator/client/beacon-api/propose_beacon_block_bellatrix_test.go b/validator/client/beacon-api/propose_beacon_block_bellatrix_test.go new file mode 100644 index 0000000000..e0c1f41e44 --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block_bellatrix_test.go @@ -0,0 +1,330 @@ +package beacon_api + +import ( + "bytes" + "encoding/json" + "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/encoding/bytesutil" + enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + 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 TestProposeBeaconBlock_Bellatrix(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + bellatrixBlock := generateSignedBellatrixBlock() + + genericSignedBlock := ðpb.GenericSignedBeaconBlock{} + genericSignedBlock.Block = bellatrixBlock + + jsonBellatrixBlock := &apimiddleware.SignedBeaconBlockBellatrixContainerJson{ + Signature: hexutil.Encode(bellatrixBlock.Bellatrix.Signature), + Message: &apimiddleware.BeaconBlockBellatrixJson{ + ParentRoot: hexutil.Encode(bellatrixBlock.Bellatrix.Block.ParentRoot), + ProposerIndex: uint64ToString(bellatrixBlock.Bellatrix.Block.ProposerIndex), + Slot: uint64ToString(bellatrixBlock.Bellatrix.Block.Slot), + StateRoot: hexutil.Encode(bellatrixBlock.Bellatrix.Block.StateRoot), + Body: &apimiddleware.BeaconBlockBodyBellatrixJson{ + Attestations: jsonifyAttestations(bellatrixBlock.Bellatrix.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(bellatrixBlock.Bellatrix.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(bellatrixBlock.Bellatrix.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(bellatrixBlock.Bellatrix.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(bellatrixBlock.Bellatrix.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(bellatrixBlock.Bellatrix.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + ExecutionPayload: &apimiddleware.ExecutionPayloadJson{ + BaseFeePerGas: bytesutil.LittleEndianBytesToBigInt(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.BaseFeePerGas).String(), + BlockHash: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.BlockHash), + BlockNumber: uint64ToString(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.BlockNumber), + ExtraData: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.ExtraData), + FeeRecipient: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.FeeRecipient), + GasLimit: uint64ToString(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.GasLimit), + GasUsed: uint64ToString(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.GasUsed), + LogsBloom: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.LogsBloom), + ParentHash: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.ParentHash), + PrevRandao: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.PrevRandao), + ReceiptsRoot: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.ReceiptsRoot), + StateRoot: hexutil.Encode(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.StateRoot), + TimeStamp: uint64ToString(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.Timestamp), + Transactions: jsonifyTransactions(bellatrixBlock.Bellatrix.Block.Body.ExecutionPayload.Transactions), + }, + }, + }, + } + + marshalledBlock, err := json.Marshal(jsonBellatrixBlock) + require.NoError(t, err) + + // Make sure that what we send in the POST body is the marshalled version of the protobuf block + headers := map[string]string{"Eth-Consensus-Version": "bellatrix"} + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blocks", + headers, + bytes.NewBuffer(marshalledBlock), + nil, + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := bellatrixBlock.Bellatrix.Block.HashTreeRoot() + require.NoError(t, err) + + // Make sure that the block root is set + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) +} + +func generateSignedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_Bellatrix { + return ðpb.GenericSignedBeaconBlock_Bellatrix{ + Bellatrix: ðpb.SignedBeaconBlockBellatrix{ + Block: ðpb.BeaconBlockBellatrix{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: test_helpers.FillByteSlice(32, 3), + StateRoot: test_helpers.FillByteSlice(32, 4), + Body: ðpb.BeaconBlockBodyBellatrix{ + RandaoReveal: test_helpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: test_helpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: test_helpers.FillByteSlice(32, 8), + }, + Graffiti: test_helpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: test_helpers.FillByteSlice(32, 12), + StateRoot: test_helpers.FillByteSlice(32, 13), + BodyRoot: test_helpers.FillByteSlice(32, 14), + }, + Signature: test_helpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: test_helpers.FillByteSlice(32, 18), + StateRoot: test_helpers.FillByteSlice(32, 19), + BodyRoot: test_helpers.FillByteSlice(32, 20), + }, + Signature: test_helpers.FillByteSlice(96, 21), + }, + }, + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 22, + ProposerIndex: 23, + ParentRoot: test_helpers.FillByteSlice(32, 24), + StateRoot: test_helpers.FillByteSlice(32, 25), + BodyRoot: test_helpers.FillByteSlice(32, 26), + }, + Signature: test_helpers.FillByteSlice(96, 27), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 28, + ProposerIndex: 29, + ParentRoot: test_helpers.FillByteSlice(32, 30), + StateRoot: test_helpers.FillByteSlice(32, 31), + BodyRoot: test_helpers.FillByteSlice(32, 32), + }, + Signature: test_helpers.FillByteSlice(96, 33), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{34, 35}, + Data: ðpb.AttestationData{ + Slot: 36, + CommitteeIndex: 37, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 39, + Root: test_helpers.FillByteSlice(32, 40), + }, + Target: ðpb.Checkpoint{ + Epoch: 41, + Root: test_helpers.FillByteSlice(32, 42), + }, + }, + Signature: test_helpers.FillByteSlice(96, 43), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{44, 45}, + Data: ðpb.AttestationData{ + Slot: 46, + CommitteeIndex: 47, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 49, + Root: test_helpers.FillByteSlice(32, 50), + }, + Target: ðpb.Checkpoint{ + Epoch: 51, + Root: test_helpers.FillByteSlice(32, 52), + }, + }, + Signature: test_helpers.FillByteSlice(96, 53), + }, + }, + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{54, 55}, + Data: ðpb.AttestationData{ + Slot: 56, + CommitteeIndex: 57, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 59, + Root: test_helpers.FillByteSlice(32, 60), + }, + Target: ðpb.Checkpoint{ + Epoch: 61, + Root: test_helpers.FillByteSlice(32, 62), + }, + }, + Signature: test_helpers.FillByteSlice(96, 63), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{64, 65}, + Data: ðpb.AttestationData{ + Slot: 66, + CommitteeIndex: 67, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 69, + Root: test_helpers.FillByteSlice(32, 70), + }, + Target: ðpb.Checkpoint{ + Epoch: 71, + Root: test_helpers.FillByteSlice(32, 72), + }, + }, + Signature: test_helpers.FillByteSlice(96, 73), + }, + }, + }, + Attestations: []*ethpb.Attestation{ + { + AggregationBits: test_helpers.FillByteSlice(4, 74), + Data: ðpb.AttestationData{ + Slot: 75, + CommitteeIndex: 76, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 78, + Root: test_helpers.FillByteSlice(32, 79), + }, + Target: ðpb.Checkpoint{ + Epoch: 80, + Root: test_helpers.FillByteSlice(32, 81), + }, + }, + Signature: test_helpers.FillByteSlice(96, 82), + }, + { + AggregationBits: test_helpers.FillByteSlice(4, 83), + Data: ðpb.AttestationData{ + Slot: 84, + CommitteeIndex: 85, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 87, + Root: test_helpers.FillByteSlice(32, 88), + }, + Target: ðpb.Checkpoint{ + Epoch: 89, + Root: test_helpers.FillByteSlice(32, 90), + }, + }, + Signature: test_helpers.FillByteSlice(96, 91), + }, + }, + Deposits: []*ethpb.Deposit{ + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 92)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 94), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 95), + Amount: 96, + Signature: test_helpers.FillByteSlice(96, 97), + }, + }, + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 98)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 100), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 101), + Amount: 102, + Signature: test_helpers.FillByteSlice(96, 103), + }, + }, + }, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{ + { + Exit: ðpb.VoluntaryExit{ + Epoch: 104, + ValidatorIndex: 105, + }, + Signature: test_helpers.FillByteSlice(96, 106), + }, + { + Exit: ðpb.VoluntaryExit{ + Epoch: 107, + ValidatorIndex: 108, + }, + Signature: test_helpers.FillByteSlice(96, 109), + }, + }, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: test_helpers.FillByteSlice(64, 110), + SyncCommitteeSignature: test_helpers.FillByteSlice(96, 111), + }, + ExecutionPayload: &enginev1.ExecutionPayload{ + ParentHash: test_helpers.FillByteSlice(32, 112), + FeeRecipient: test_helpers.FillByteSlice(20, 113), + StateRoot: test_helpers.FillByteSlice(32, 114), + ReceiptsRoot: test_helpers.FillByteSlice(32, 115), + LogsBloom: test_helpers.FillByteSlice(256, 116), + PrevRandao: test_helpers.FillByteSlice(32, 117), + BlockNumber: 118, + GasLimit: 119, + GasUsed: 120, + Timestamp: 121, + ExtraData: test_helpers.FillByteSlice(32, 122), + BaseFeePerGas: test_helpers.FillByteSlice(32, 123), + BlockHash: test_helpers.FillByteSlice(32, 124), + Transactions: [][]byte{ + {125}, + {126}, + }, + }, + }, + }, + Signature: test_helpers.FillByteSlice(96, 127), + }, + } +} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_bellatrix_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_bellatrix_test.go new file mode 100644 index 0000000000..b51f7bec2c --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block_blinded_bellatrix_test.go @@ -0,0 +1,327 @@ +package beacon_api + +import ( + "bytes" + "encoding/json" + "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/encoding/bytesutil" + enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + 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 TestProposeBeaconBlock_BlindedBellatrix(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + blindedBellatrixBlock := generateSignedBlindedBellatrixBlock() + + genericSignedBlock := ðpb.GenericSignedBeaconBlock{} + genericSignedBlock.Block = blindedBellatrixBlock + + jsonBlindedBellatrixBlock := &apimiddleware.SignedBlindedBeaconBlockBellatrixContainerJson{ + Signature: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Signature), + Message: &apimiddleware.BlindedBeaconBlockBellatrixJson{ + ParentRoot: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.ParentRoot), + ProposerIndex: uint64ToString(blindedBellatrixBlock.BlindedBellatrix.Block.ProposerIndex), + Slot: uint64ToString(blindedBellatrixBlock.BlindedBellatrix.Block.Slot), + StateRoot: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.StateRoot), + Body: &apimiddleware.BlindedBeaconBlockBodyBellatrixJson{ + Attestations: jsonifyAttestations(blindedBellatrixBlock.BlindedBellatrix.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(blindedBellatrixBlock.BlindedBellatrix.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(blindedBellatrixBlock.BlindedBellatrix.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(blindedBellatrixBlock.BlindedBellatrix.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(blindedBellatrixBlock.BlindedBellatrix.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + ExecutionPayloadHeader: &apimiddleware.ExecutionPayloadHeaderJson{ + BaseFeePerGas: bytesutil.LittleEndianBytesToBigInt(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.BaseFeePerGas).String(), + BlockHash: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.BlockHash), + BlockNumber: uint64ToString(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.BlockNumber), + ExtraData: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.ExtraData), + FeeRecipient: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.FeeRecipient), + GasLimit: uint64ToString(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.GasLimit), + GasUsed: uint64ToString(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.GasUsed), + LogsBloom: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.LogsBloom), + ParentHash: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.ParentHash), + PrevRandao: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.PrevRandao), + ReceiptsRoot: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.ReceiptsRoot), + StateRoot: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.StateRoot), + TimeStamp: uint64ToString(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.Timestamp), + TransactionsRoot: hexutil.Encode(blindedBellatrixBlock.BlindedBellatrix.Block.Body.ExecutionPayloadHeader.TransactionsRoot), + }, + }, + }, + } + + marshalledBlock, err := json.Marshal(jsonBlindedBellatrixBlock) + require.NoError(t, err) + + // Make sure that what we send in the POST body is the marshalled version of the protobuf block + headers := map[string]string{"Eth-Consensus-Version": "bellatrix"} + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blinded_blocks", + headers, + bytes.NewBuffer(marshalledBlock), + nil, + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := blindedBellatrixBlock.BlindedBellatrix.Block.HashTreeRoot() + require.NoError(t, err) + + // Make sure that the block root is set + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) +} + +func generateSignedBlindedBellatrixBlock() *ethpb.GenericSignedBeaconBlock_BlindedBellatrix { + return ðpb.GenericSignedBeaconBlock_BlindedBellatrix{ + BlindedBellatrix: ðpb.SignedBlindedBeaconBlockBellatrix{ + Block: ðpb.BlindedBeaconBlockBellatrix{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: test_helpers.FillByteSlice(32, 3), + StateRoot: test_helpers.FillByteSlice(32, 4), + Body: ðpb.BlindedBeaconBlockBodyBellatrix{ + RandaoReveal: test_helpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: test_helpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: test_helpers.FillByteSlice(32, 8), + }, + Graffiti: test_helpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: test_helpers.FillByteSlice(32, 12), + StateRoot: test_helpers.FillByteSlice(32, 13), + BodyRoot: test_helpers.FillByteSlice(32, 14), + }, + Signature: test_helpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: test_helpers.FillByteSlice(32, 18), + StateRoot: test_helpers.FillByteSlice(32, 19), + BodyRoot: test_helpers.FillByteSlice(32, 20), + }, + Signature: test_helpers.FillByteSlice(96, 21), + }, + }, + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 22, + ProposerIndex: 23, + ParentRoot: test_helpers.FillByteSlice(32, 24), + StateRoot: test_helpers.FillByteSlice(32, 25), + BodyRoot: test_helpers.FillByteSlice(32, 26), + }, + Signature: test_helpers.FillByteSlice(96, 27), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 28, + ProposerIndex: 29, + ParentRoot: test_helpers.FillByteSlice(32, 30), + StateRoot: test_helpers.FillByteSlice(32, 31), + BodyRoot: test_helpers.FillByteSlice(32, 32), + }, + Signature: test_helpers.FillByteSlice(96, 33), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{34, 35}, + Data: ðpb.AttestationData{ + Slot: 36, + CommitteeIndex: 37, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 39, + Root: test_helpers.FillByteSlice(32, 40), + }, + Target: ðpb.Checkpoint{ + Epoch: 41, + Root: test_helpers.FillByteSlice(32, 42), + }, + }, + Signature: test_helpers.FillByteSlice(96, 43), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{44, 45}, + Data: ðpb.AttestationData{ + Slot: 46, + CommitteeIndex: 47, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 49, + Root: test_helpers.FillByteSlice(32, 50), + }, + Target: ðpb.Checkpoint{ + Epoch: 51, + Root: test_helpers.FillByteSlice(32, 52), + }, + }, + Signature: test_helpers.FillByteSlice(96, 53), + }, + }, + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{54, 55}, + Data: ðpb.AttestationData{ + Slot: 56, + CommitteeIndex: 57, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 59, + Root: test_helpers.FillByteSlice(32, 60), + }, + Target: ðpb.Checkpoint{ + Epoch: 61, + Root: test_helpers.FillByteSlice(32, 62), + }, + }, + Signature: test_helpers.FillByteSlice(96, 63), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{64, 65}, + Data: ðpb.AttestationData{ + Slot: 66, + CommitteeIndex: 67, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 69, + Root: test_helpers.FillByteSlice(32, 70), + }, + Target: ðpb.Checkpoint{ + Epoch: 71, + Root: test_helpers.FillByteSlice(32, 72), + }, + }, + Signature: test_helpers.FillByteSlice(96, 73), + }, + }, + }, + Attestations: []*ethpb.Attestation{ + { + AggregationBits: test_helpers.FillByteSlice(4, 74), + Data: ðpb.AttestationData{ + Slot: 75, + CommitteeIndex: 76, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 78, + Root: test_helpers.FillByteSlice(32, 79), + }, + Target: ðpb.Checkpoint{ + Epoch: 80, + Root: test_helpers.FillByteSlice(32, 81), + }, + }, + Signature: test_helpers.FillByteSlice(96, 82), + }, + { + AggregationBits: test_helpers.FillByteSlice(4, 83), + Data: ðpb.AttestationData{ + Slot: 84, + CommitteeIndex: 85, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 87, + Root: test_helpers.FillByteSlice(32, 88), + }, + Target: ðpb.Checkpoint{ + Epoch: 89, + Root: test_helpers.FillByteSlice(32, 90), + }, + }, + Signature: test_helpers.FillByteSlice(96, 91), + }, + }, + Deposits: []*ethpb.Deposit{ + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 92)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 94), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 95), + Amount: 96, + Signature: test_helpers.FillByteSlice(96, 97), + }, + }, + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 98)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 100), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 101), + Amount: 102, + Signature: test_helpers.FillByteSlice(96, 103), + }, + }, + }, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{ + { + Exit: ðpb.VoluntaryExit{ + Epoch: 104, + ValidatorIndex: 105, + }, + Signature: test_helpers.FillByteSlice(96, 106), + }, + { + Exit: ðpb.VoluntaryExit{ + Epoch: 107, + ValidatorIndex: 108, + }, + Signature: test_helpers.FillByteSlice(96, 109), + }, + }, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: test_helpers.FillByteSlice(64, 110), + SyncCommitteeSignature: test_helpers.FillByteSlice(96, 111), + }, + ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeader{ + ParentHash: test_helpers.FillByteSlice(32, 112), + FeeRecipient: test_helpers.FillByteSlice(20, 113), + StateRoot: test_helpers.FillByteSlice(32, 114), + ReceiptsRoot: test_helpers.FillByteSlice(32, 115), + LogsBloom: test_helpers.FillByteSlice(256, 116), + PrevRandao: test_helpers.FillByteSlice(32, 117), + BlockNumber: 118, + GasLimit: 119, + GasUsed: 120, + Timestamp: 121, + ExtraData: test_helpers.FillByteSlice(32, 122), + BaseFeePerGas: test_helpers.FillByteSlice(32, 123), + BlockHash: test_helpers.FillByteSlice(32, 124), + TransactionsRoot: test_helpers.FillByteSlice(32, 125), + }, + }, + }, + Signature: test_helpers.FillByteSlice(96, 126), + }, + } +} diff --git a/validator/client/beacon-api/propose_beacon_block_blinded_capella_test.go b/validator/client/beacon-api/propose_beacon_block_blinded_capella_test.go new file mode 100644 index 0000000000..518c7dd0f4 --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block_blinded_capella_test.go @@ -0,0 +1,348 @@ +package beacon_api + +import ( + "bytes" + "encoding/json" + "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/encoding/bytesutil" + enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + 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 TestProposeBeaconBlock_BlindedCapella(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + blindedCapellaBlock := generateSignedBlindedCapellaBlock() + + genericSignedBlock := ðpb.GenericSignedBeaconBlock{} + genericSignedBlock.Block = blindedCapellaBlock + + jsonBlindedCapellaBlock := &apimiddleware.SignedBlindedBeaconBlockCapellaContainerJson{ + Signature: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Signature), + Message: &apimiddleware.BlindedBeaconBlockCapellaJson{ + ParentRoot: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.ParentRoot), + ProposerIndex: uint64ToString(blindedCapellaBlock.BlindedCapella.Block.ProposerIndex), + Slot: uint64ToString(blindedCapellaBlock.BlindedCapella.Block.Slot), + StateRoot: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.StateRoot), + Body: &apimiddleware.BlindedBeaconBlockBodyCapellaJson{ + Attestations: jsonifyAttestations(blindedCapellaBlock.BlindedCapella.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(blindedCapellaBlock.BlindedCapella.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(blindedCapellaBlock.BlindedCapella.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(blindedCapellaBlock.BlindedCapella.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(blindedCapellaBlock.BlindedCapella.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(blindedCapellaBlock.BlindedCapella.Block.Body.VoluntaryExits), + SyncAggregate: &apimiddleware.SyncAggregateJson{ + SyncCommitteeBits: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.SyncAggregate.SyncCommitteeBits), + SyncCommitteeSignature: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.SyncAggregate.SyncCommitteeSignature), + }, + ExecutionPayloadHeader: &apimiddleware.ExecutionPayloadHeaderCapellaJson{ + BaseFeePerGas: bytesutil.LittleEndianBytesToBigInt(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.BaseFeePerGas).String(), + BlockHash: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.BlockHash), + BlockNumber: uint64ToString(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.BlockNumber), + ExtraData: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.ExtraData), + FeeRecipient: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.FeeRecipient), + GasLimit: uint64ToString(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.GasLimit), + GasUsed: uint64ToString(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.GasUsed), + LogsBloom: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.LogsBloom), + ParentHash: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.ParentHash), + PrevRandao: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.PrevRandao), + ReceiptsRoot: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.ReceiptsRoot), + StateRoot: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.StateRoot), + TimeStamp: uint64ToString(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.Timestamp), + TransactionsRoot: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.TransactionsRoot), + WithdrawalsRoot: hexutil.Encode(blindedCapellaBlock.BlindedCapella.Block.Body.ExecutionPayloadHeader.WithdrawalsRoot), + }, + BLSToExecutionChanges: jsonifyBlsToExecutionChanges(blindedCapellaBlock.BlindedCapella.Block.Body.BlsToExecutionChanges), + }, + }, + } + + marshalledBlock, err := json.Marshal(jsonBlindedCapellaBlock) + require.NoError(t, err) + + // Make sure that what we send in the POST body is the marshalled version of the protobuf block + headers := map[string]string{"Eth-Consensus-Version": "capella"} + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blinded_blocks", + headers, + bytes.NewBuffer(marshalledBlock), + nil, + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := blindedCapellaBlock.BlindedCapella.Block.HashTreeRoot() + require.NoError(t, err) + + // Make sure that the block root is set + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) +} + +func generateSignedBlindedCapellaBlock() *ethpb.GenericSignedBeaconBlock_BlindedCapella { + return ðpb.GenericSignedBeaconBlock_BlindedCapella{ + BlindedCapella: ðpb.SignedBlindedBeaconBlockCapella{ + Block: ðpb.BlindedBeaconBlockCapella{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: test_helpers.FillByteSlice(32, 3), + StateRoot: test_helpers.FillByteSlice(32, 4), + Body: ðpb.BlindedBeaconBlockBodyCapella{ + RandaoReveal: test_helpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: test_helpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: test_helpers.FillByteSlice(32, 8), + }, + Graffiti: test_helpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: test_helpers.FillByteSlice(32, 12), + StateRoot: test_helpers.FillByteSlice(32, 13), + BodyRoot: test_helpers.FillByteSlice(32, 14), + }, + Signature: test_helpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: test_helpers.FillByteSlice(32, 18), + StateRoot: test_helpers.FillByteSlice(32, 19), + BodyRoot: test_helpers.FillByteSlice(32, 20), + }, + Signature: test_helpers.FillByteSlice(96, 21), + }, + }, + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 22, + ProposerIndex: 23, + ParentRoot: test_helpers.FillByteSlice(32, 24), + StateRoot: test_helpers.FillByteSlice(32, 25), + BodyRoot: test_helpers.FillByteSlice(32, 26), + }, + Signature: test_helpers.FillByteSlice(96, 27), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 28, + ProposerIndex: 29, + ParentRoot: test_helpers.FillByteSlice(32, 30), + StateRoot: test_helpers.FillByteSlice(32, 31), + BodyRoot: test_helpers.FillByteSlice(32, 32), + }, + Signature: test_helpers.FillByteSlice(96, 33), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{34, 35}, + Data: ðpb.AttestationData{ + Slot: 36, + CommitteeIndex: 37, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 39, + Root: test_helpers.FillByteSlice(32, 40), + }, + Target: ðpb.Checkpoint{ + Epoch: 41, + Root: test_helpers.FillByteSlice(32, 42), + }, + }, + Signature: test_helpers.FillByteSlice(96, 43), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{44, 45}, + Data: ðpb.AttestationData{ + Slot: 46, + CommitteeIndex: 47, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 49, + Root: test_helpers.FillByteSlice(32, 50), + }, + Target: ðpb.Checkpoint{ + Epoch: 51, + Root: test_helpers.FillByteSlice(32, 52), + }, + }, + Signature: test_helpers.FillByteSlice(96, 53), + }, + }, + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{54, 55}, + Data: ðpb.AttestationData{ + Slot: 56, + CommitteeIndex: 57, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 59, + Root: test_helpers.FillByteSlice(32, 60), + }, + Target: ðpb.Checkpoint{ + Epoch: 61, + Root: test_helpers.FillByteSlice(32, 62), + }, + }, + Signature: test_helpers.FillByteSlice(96, 63), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{64, 65}, + Data: ðpb.AttestationData{ + Slot: 66, + CommitteeIndex: 67, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 69, + Root: test_helpers.FillByteSlice(32, 70), + }, + Target: ðpb.Checkpoint{ + Epoch: 71, + Root: test_helpers.FillByteSlice(32, 72), + }, + }, + Signature: test_helpers.FillByteSlice(96, 73), + }, + }, + }, + Attestations: []*ethpb.Attestation{ + { + AggregationBits: test_helpers.FillByteSlice(4, 74), + Data: ðpb.AttestationData{ + Slot: 75, + CommitteeIndex: 76, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 78, + Root: test_helpers.FillByteSlice(32, 79), + }, + Target: ðpb.Checkpoint{ + Epoch: 80, + Root: test_helpers.FillByteSlice(32, 81), + }, + }, + Signature: test_helpers.FillByteSlice(96, 82), + }, + { + AggregationBits: test_helpers.FillByteSlice(4, 83), + Data: ðpb.AttestationData{ + Slot: 84, + CommitteeIndex: 85, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 87, + Root: test_helpers.FillByteSlice(32, 88), + }, + Target: ðpb.Checkpoint{ + Epoch: 89, + Root: test_helpers.FillByteSlice(32, 90), + }, + }, + Signature: test_helpers.FillByteSlice(96, 91), + }, + }, + Deposits: []*ethpb.Deposit{ + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 92)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 94), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 95), + Amount: 96, + Signature: test_helpers.FillByteSlice(96, 97), + }, + }, + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 98)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 100), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 101), + Amount: 102, + Signature: test_helpers.FillByteSlice(96, 103), + }, + }, + }, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{ + { + Exit: ðpb.VoluntaryExit{ + Epoch: 104, + ValidatorIndex: 105, + }, + Signature: test_helpers.FillByteSlice(96, 106), + }, + { + Exit: ðpb.VoluntaryExit{ + Epoch: 107, + ValidatorIndex: 108, + }, + Signature: test_helpers.FillByteSlice(96, 109), + }, + }, + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: test_helpers.FillByteSlice(64, 110), + SyncCommitteeSignature: test_helpers.FillByteSlice(96, 111), + }, + ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderCapella{ + ParentHash: test_helpers.FillByteSlice(32, 112), + FeeRecipient: test_helpers.FillByteSlice(20, 113), + StateRoot: test_helpers.FillByteSlice(32, 114), + ReceiptsRoot: test_helpers.FillByteSlice(32, 115), + LogsBloom: test_helpers.FillByteSlice(256, 116), + PrevRandao: test_helpers.FillByteSlice(32, 117), + BlockNumber: 118, + GasLimit: 119, + GasUsed: 120, + Timestamp: 121, + ExtraData: test_helpers.FillByteSlice(32, 122), + BaseFeePerGas: test_helpers.FillByteSlice(32, 123), + BlockHash: test_helpers.FillByteSlice(32, 124), + TransactionsRoot: test_helpers.FillByteSlice(32, 125), + WithdrawalsRoot: test_helpers.FillByteSlice(32, 126), + }, + BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{ + { + Message: ðpb.BLSToExecutionChange{ + ValidatorIndex: 127, + FromBlsPubkey: test_helpers.FillByteSlice(48, 128), + ToExecutionAddress: test_helpers.FillByteSlice(20, 129), + }, + Signature: test_helpers.FillByteSlice(96, 130), + }, + { + Message: ðpb.BLSToExecutionChange{ + ValidatorIndex: 131, + FromBlsPubkey: test_helpers.FillByteSlice(48, 132), + ToExecutionAddress: test_helpers.FillByteSlice(20, 133), + }, + Signature: test_helpers.FillByteSlice(96, 134), + }, + }, + }, + }, + Signature: test_helpers.FillByteSlice(96, 135), + }, + } +} diff --git a/validator/client/beacon-api/propose_beacon_block_phase0_test.go b/validator/client/beacon-api/propose_beacon_block_phase0_test.go new file mode 100644 index 0000000000..e723660433 --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block_phase0_test.go @@ -0,0 +1,285 @@ +package beacon_api + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/golang/mock/gomock" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware" + 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 TestProposeBeaconBlock_Phase0(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + phase0Block := generateSignedPhase0Block() + + genericSignedBlock := ðpb.GenericSignedBeaconBlock{} + genericSignedBlock.Block = phase0Block + + jsonPhase0Block := &apimiddleware.SignedBeaconBlockContainerJson{ + Signature: hexutil.Encode(phase0Block.Phase0.Signature), + Message: &apimiddleware.BeaconBlockJson{ + ParentRoot: hexutil.Encode(phase0Block.Phase0.Block.ParentRoot), + ProposerIndex: uint64ToString(phase0Block.Phase0.Block.ProposerIndex), + Slot: uint64ToString(phase0Block.Phase0.Block.Slot), + StateRoot: hexutil.Encode(phase0Block.Phase0.Block.StateRoot), + Body: &apimiddleware.BeaconBlockBodyJson{ + Attestations: jsonifyAttestations(phase0Block.Phase0.Block.Body.Attestations), + AttesterSlashings: jsonifyAttesterSlashings(phase0Block.Phase0.Block.Body.AttesterSlashings), + Deposits: jsonifyDeposits(phase0Block.Phase0.Block.Body.Deposits), + Eth1Data: jsonifyEth1Data(phase0Block.Phase0.Block.Body.Eth1Data), + Graffiti: hexutil.Encode(phase0Block.Phase0.Block.Body.Graffiti), + ProposerSlashings: jsonifyProposerSlashings(phase0Block.Phase0.Block.Body.ProposerSlashings), + RandaoReveal: hexutil.Encode(phase0Block.Phase0.Block.Body.RandaoReveal), + VoluntaryExits: jsonifySignedVoluntaryExits(phase0Block.Phase0.Block.Body.VoluntaryExits), + }, + }, + } + + marshalledBlock, err := json.Marshal(jsonPhase0Block) + require.NoError(t, err) + + // Make sure that what we send in the POST body is the marshalled version of the protobuf block + headers := map[string]string{"Eth-Consensus-Version": "phase0"} + jsonRestHandler.EXPECT().PostRestJson( + "/eth/v1/beacon/blocks", + headers, + bytes.NewBuffer(marshalledBlock), + nil, + ) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + proposeResponse, err := validatorClient.proposeBeaconBlock(genericSignedBlock) + assert.NoError(t, err) + require.NotNil(t, proposeResponse) + + expectedBlockRoot, err := phase0Block.Phase0.Block.HashTreeRoot() + require.NoError(t, err) + + // Make sure that the block root is set + assert.DeepEqual(t, expectedBlockRoot[:], proposeResponse.BlockRoot) +} + +func generateSignedPhase0Block() *ethpb.GenericSignedBeaconBlock_Phase0 { + return ðpb.GenericSignedBeaconBlock_Phase0{ + Phase0: ðpb.SignedBeaconBlock{ + Block: ðpb.BeaconBlock{ + Slot: 1, + ProposerIndex: 2, + ParentRoot: test_helpers.FillByteSlice(32, 3), + StateRoot: test_helpers.FillByteSlice(32, 4), + Body: ðpb.BeaconBlockBody{ + RandaoReveal: test_helpers.FillByteSlice(96, 5), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: test_helpers.FillByteSlice(32, 6), + DepositCount: 7, + BlockHash: test_helpers.FillByteSlice(32, 8), + }, + Graffiti: test_helpers.FillByteSlice(32, 9), + ProposerSlashings: []*ethpb.ProposerSlashing{ + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 10, + ProposerIndex: 11, + ParentRoot: test_helpers.FillByteSlice(32, 12), + StateRoot: test_helpers.FillByteSlice(32, 13), + BodyRoot: test_helpers.FillByteSlice(32, 14), + }, + Signature: test_helpers.FillByteSlice(96, 15), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 16, + ProposerIndex: 17, + ParentRoot: test_helpers.FillByteSlice(32, 18), + StateRoot: test_helpers.FillByteSlice(32, 19), + BodyRoot: test_helpers.FillByteSlice(32, 20), + }, + Signature: test_helpers.FillByteSlice(96, 21), + }, + }, + { + Header_1: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 22, + ProposerIndex: 23, + ParentRoot: test_helpers.FillByteSlice(32, 24), + StateRoot: test_helpers.FillByteSlice(32, 25), + BodyRoot: test_helpers.FillByteSlice(32, 26), + }, + Signature: test_helpers.FillByteSlice(96, 27), + }, + Header_2: ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: 28, + ProposerIndex: 29, + ParentRoot: test_helpers.FillByteSlice(32, 30), + StateRoot: test_helpers.FillByteSlice(32, 31), + BodyRoot: test_helpers.FillByteSlice(32, 32), + }, + Signature: test_helpers.FillByteSlice(96, 33), + }, + }, + }, + AttesterSlashings: []*ethpb.AttesterSlashing{ + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{34, 35}, + Data: ðpb.AttestationData{ + Slot: 36, + CommitteeIndex: 37, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 39, + Root: test_helpers.FillByteSlice(32, 40), + }, + Target: ðpb.Checkpoint{ + Epoch: 41, + Root: test_helpers.FillByteSlice(32, 42), + }, + }, + Signature: test_helpers.FillByteSlice(96, 43), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{44, 45}, + Data: ðpb.AttestationData{ + Slot: 46, + CommitteeIndex: 47, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 48), + Source: ðpb.Checkpoint{ + Epoch: 49, + Root: test_helpers.FillByteSlice(32, 50), + }, + Target: ðpb.Checkpoint{ + Epoch: 51, + Root: test_helpers.FillByteSlice(32, 52), + }, + }, + Signature: test_helpers.FillByteSlice(96, 53), + }, + }, + { + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{54, 55}, + Data: ðpb.AttestationData{ + Slot: 56, + CommitteeIndex: 57, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 58), + Source: ðpb.Checkpoint{ + Epoch: 59, + Root: test_helpers.FillByteSlice(32, 60), + }, + Target: ðpb.Checkpoint{ + Epoch: 61, + Root: test_helpers.FillByteSlice(32, 62), + }, + }, + Signature: test_helpers.FillByteSlice(96, 63), + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: []uint64{64, 65}, + Data: ðpb.AttestationData{ + Slot: 66, + CommitteeIndex: 67, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 68), + Source: ðpb.Checkpoint{ + Epoch: 69, + Root: test_helpers.FillByteSlice(32, 70), + }, + Target: ðpb.Checkpoint{ + Epoch: 71, + Root: test_helpers.FillByteSlice(32, 72), + }, + }, + Signature: test_helpers.FillByteSlice(96, 73), + }, + }, + }, + Attestations: []*ethpb.Attestation{ + { + AggregationBits: test_helpers.FillByteSlice(32, 74), + Data: ðpb.AttestationData{ + Slot: 75, + CommitteeIndex: 76, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 77), + Source: ðpb.Checkpoint{ + Epoch: 78, + Root: test_helpers.FillByteSlice(32, 79), + }, + Target: ðpb.Checkpoint{ + Epoch: 80, + Root: test_helpers.FillByteSlice(32, 81), + }, + }, + Signature: test_helpers.FillByteSlice(96, 82), + }, + { + AggregationBits: test_helpers.FillByteSlice(4, 83), + Data: ðpb.AttestationData{ + Slot: 84, + CommitteeIndex: 85, + BeaconBlockRoot: test_helpers.FillByteSlice(32, 38), + Source: ðpb.Checkpoint{ + Epoch: 87, + Root: test_helpers.FillByteSlice(32, 88), + }, + Target: ðpb.Checkpoint{ + Epoch: 89, + Root: test_helpers.FillByteSlice(32, 90), + }, + }, + Signature: test_helpers.FillByteSlice(96, 91), + }, + }, + Deposits: []*ethpb.Deposit{ + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 92)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 94), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 95), + Amount: 96, + Signature: test_helpers.FillByteSlice(96, 97), + }, + }, + { + Proof: test_helpers.FillByteArraySlice(33, test_helpers.FillByteSlice(32, 98)), + Data: ðpb.Deposit_Data{ + PublicKey: test_helpers.FillByteSlice(48, 100), + WithdrawalCredentials: test_helpers.FillByteSlice(32, 101), + Amount: 102, + Signature: test_helpers.FillByteSlice(96, 103), + }, + }, + }, + VoluntaryExits: []*ethpb.SignedVoluntaryExit{ + { + Exit: ðpb.VoluntaryExit{ + Epoch: 104, + ValidatorIndex: 105, + }, + Signature: test_helpers.FillByteSlice(96, 106), + }, + { + Exit: ðpb.VoluntaryExit{ + Epoch: 107, + ValidatorIndex: 108, + }, + Signature: test_helpers.FillByteSlice(96, 109), + }, + }, + }, + }, + Signature: test_helpers.FillByteSlice(96, 110), + }, + } +} diff --git a/validator/client/beacon-api/propose_beacon_block_test.go b/validator/client/beacon-api/propose_beacon_block_test.go new file mode 100644 index 0000000000..e2d7c4f569 --- /dev/null +++ b/validator/client/beacon-api/propose_beacon_block_test.go @@ -0,0 +1,116 @@ +package beacon_api + +import ( + "errors" + "net/http" + "testing" + + "github.com/golang/mock/gomock" + "github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v3/testing/assert" + "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock" +) + +func TestProposeBeaconBlock_Error(t *testing.T) { + testSuites := []struct { + name string + expectedErrorMessage string + expectedHttpError *apimiddleware.DefaultErrorJson + }{ + { + name: "error 202", + expectedErrorMessage: "block was successfully broadcasted but failed validation", + expectedHttpError: &apimiddleware.DefaultErrorJson{ + Code: http.StatusAccepted, + Message: "202 error", + }, + }, + { + name: "request failed", + expectedErrorMessage: "failed to send POST data to REST endpoint", + expectedHttpError: nil, + }, + } + + testCases := []struct { + name string + consensusVersion string + endpoint string + block *ethpb.GenericSignedBeaconBlock + }{ + { + name: "phase0", + consensusVersion: "phase0", + endpoint: "/eth/v1/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedPhase0Block(), + }, + }, + { + name: "altair", + consensusVersion: "altair", + endpoint: "/eth/v1/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedAltairBlock(), + }, + }, + { + name: "bellatrix", + consensusVersion: "bellatrix", + endpoint: "/eth/v1/beacon/blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBellatrixBlock(), + }, + }, + { + name: "blinded bellatrix", + consensusVersion: "bellatrix", + endpoint: "/eth/v1/beacon/blinded_blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedBellatrixBlock(), + }, + }, + { + name: "blinded capella", + consensusVersion: "capella", + endpoint: "/eth/v1/beacon/blinded_blocks", + block: ðpb.GenericSignedBeaconBlock{ + Block: generateSignedBlindedCapellaBlock(), + }, + }, + } + + for _, testSuite := range testSuites { + for _, testCase := range testCases { + t.Run(testSuite.name+"/"+testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + jsonRestHandler := mock.NewMockjsonRestHandler(ctrl) + + headers := map[string]string{"Eth-Consensus-Version": testCase.consensusVersion} + jsonRestHandler.EXPECT().PostRestJson( + testCase.endpoint, + headers, + gomock.Any(), + nil, + ).Return( + testSuite.expectedHttpError, + errors.New("foo error"), + ).Times(1) + + validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} + _, err := validatorClient.proposeBeaconBlock(testCase.block) + assert.ErrorContains(t, testSuite.expectedErrorMessage, err) + assert.ErrorContains(t, "foo error", err) + }) + } + } +} + +func TestProposeBeaconBlock_UnsupportedBlockType(t *testing.T) { + validatorClient := &beaconApiValidatorClient{} + _, err := validatorClient.proposeBeaconBlock(ðpb.GenericSignedBeaconBlock{}) + assert.ErrorContains(t, "unsupported block type", err) +} diff --git a/validator/client/beacon-api/test-helpers/BUILD.bazel b/validator/client/beacon-api/test-helpers/BUILD.bazel new file mode 100644 index 0000000000..f1a9406b7b --- /dev/null +++ b/validator/client/beacon-api/test-helpers/BUILD.bazel @@ -0,0 +1,9 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + testonly = True, + srcs = ["test_helpers.go"], + importpath = "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/test-helpers", + visibility = ["//validator:__subpackages__"], +) diff --git a/validator/client/beacon-api/test-helpers/test_helpers.go b/validator/client/beacon-api/test-helpers/test_helpers.go new file mode 100644 index 0000000000..9566d64fcf --- /dev/null +++ b/validator/client/beacon-api/test-helpers/test_helpers.go @@ -0,0 +1,21 @@ +package test_helpers + +func FillByteSlice(sliceLength int, value byte) []byte { + bytes := make([]byte, sliceLength) + + for index := range bytes { + bytes[index] = value + } + + return bytes +} + +func FillByteArraySlice(sliceLength int, value []byte) [][]byte { + bytes := make([][]byte, sliceLength) + + for index := range bytes { + bytes[index] = value + } + + return bytes +}