mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-22 03:30:35 +00:00
Add REST implementation for Validator's ValidatorIndex
(#11712)
* Add GetAttestationData * Add tests * Add many more tests and refactor * Fix logic * Address PR comments * Address PR comments * Add jsonRestHandler and decouple http logic from rest of the code * Add buildURL tests * Remove handlers_test.go * Improve tests * Implement `ValidatorIndex` of `beaconApiValidatorClient` using Beacon API * Implement getStateValidators * `validatorIndex`: Use `getStateValidators` Co-authored-by: Patrice Vignola <vignola.patrice@gmail.com>
This commit is contained in:
parent
7dc966bb3b
commit
0a5c65e29c
@ -73,6 +73,7 @@ gofmt -s -w "$mock_path/."
|
||||
beacon_api_mock_path="validator/client/beacon-api/mock"
|
||||
beacon_api_mocks=(
|
||||
"$beacon_api_mock_path/genesis_mock.go genesis.go"
|
||||
"$beacon_api_mock_path/json_rest_handler_mock.go json_rest_handler.go"
|
||||
)
|
||||
|
||||
for ((i = 0; i < ${#beacon_api_mocks[@]}; i++)); do
|
||||
|
@ -4,10 +4,14 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestation_data.go",
|
||||
"beacon_api_helpers.go",
|
||||
"beacon_api_validator_client.go",
|
||||
"domain_data.go",
|
||||
"genesis.go",
|
||||
"index.go",
|
||||
"json_rest_handler.go",
|
||||
"state_validators.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api",
|
||||
visibility = ["//validator:__subpackages__"],
|
||||
@ -31,11 +35,14 @@ go_test(
|
||||
name = "go_default_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"attestation_data_test.go",
|
||||
"beacon_api_helpers_test.go",
|
||||
"beacon_api_validator_client_test.go",
|
||||
"domain_data_test.go",
|
||||
"genesis_test.go",
|
||||
"handlers_test.go",
|
||||
"index_test.go",
|
||||
"json_rest_handler_test.go",
|
||||
"state_validators_test.go",
|
||||
"wait_for_chain_start_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
@ -44,6 +51,7 @@ go_test(
|
||||
"//api/gateway/apimiddleware:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
@ -51,6 +59,7 @@ go_test(
|
||||
"//validator/client/beacon-api/mock: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",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
107
validator/client/beacon-api/attestation_data.go
Normal file
107
validator/client/beacon-api/attestation_data.go
Normal file
@ -0,0 +1,107 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
func (c beaconApiValidatorClient) getAttestationData(
|
||||
reqSlot types.Slot,
|
||||
reqCommitteeIndex types.CommitteeIndex,
|
||||
) (*ethpb.AttestationData, error) {
|
||||
params := url.Values{}
|
||||
params.Add("slot", strconv.FormatUint(uint64(reqSlot), 10))
|
||||
params.Add("committee_index", strconv.FormatUint(uint64(reqCommitteeIndex), 10))
|
||||
|
||||
query := buildURL("/eth/v1/validator/attestation_data", params)
|
||||
produceAttestationDataResponseJson := rpcmiddleware.ProduceAttestationDataResponseJson{}
|
||||
_, err := c.jsonRestHandler.GetRestJsonResponse(query, &produceAttestationDataResponseJson)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get json response")
|
||||
}
|
||||
|
||||
if produceAttestationDataResponseJson.Data == nil {
|
||||
return nil, errors.New("attestation data is nil")
|
||||
}
|
||||
|
||||
attestationData := produceAttestationDataResponseJson.Data
|
||||
committeeIndex, err := strconv.ParseUint(attestationData.CommitteeIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attestation committee index: %s", attestationData.CommitteeIndex)
|
||||
}
|
||||
|
||||
if !validRoot(attestationData.BeaconBlockRoot) {
|
||||
return nil, errors.Errorf("invalid beacon block root: %s", attestationData.BeaconBlockRoot)
|
||||
}
|
||||
|
||||
beaconBlockRoot, err := hexutil.Decode(attestationData.BeaconBlockRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode beacon block root: %s", attestationData.BeaconBlockRoot)
|
||||
}
|
||||
|
||||
slot, err := strconv.ParseUint(attestationData.Slot, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attestation slot: %s", attestationData.Slot)
|
||||
}
|
||||
|
||||
if attestationData.Source == nil {
|
||||
return nil, errors.New("attestation source is nil")
|
||||
}
|
||||
|
||||
sourceEpoch, err := strconv.ParseUint(attestationData.Source.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attestation source epoch: %s", attestationData.Source.Epoch)
|
||||
}
|
||||
|
||||
if !validRoot(attestationData.Source.Root) {
|
||||
return nil, errors.Errorf("invalid attestation source root: %s", attestationData.Source.Root)
|
||||
}
|
||||
|
||||
sourceRoot, err := hexutil.Decode(attestationData.Source.Root)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode attestation source root: %s", attestationData.Source.Root)
|
||||
}
|
||||
|
||||
if attestationData.Target == nil {
|
||||
return nil, errors.New("attestation target is nil")
|
||||
}
|
||||
|
||||
targetEpoch, err := strconv.ParseUint(attestationData.Target.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse attestation target epoch: %s", attestationData.Target.Epoch)
|
||||
}
|
||||
|
||||
if !validRoot(attestationData.Target.Root) {
|
||||
return nil, errors.Errorf("invalid attestation target root: %s", attestationData.Target.Root)
|
||||
}
|
||||
|
||||
targetRoot, err := hexutil.Decode(attestationData.Target.Root)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode attestation target root: %s", attestationData.Target.Root)
|
||||
}
|
||||
|
||||
response := ðpb.AttestationData{
|
||||
BeaconBlockRoot: beaconBlockRoot,
|
||||
CommitteeIndex: types.CommitteeIndex(committeeIndex),
|
||||
Slot: types.Slot(slot),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: types.Epoch(sourceEpoch),
|
||||
Root: sourceRoot,
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: types.Epoch(targetEpoch),
|
||||
Root: targetRoot,
|
||||
},
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
243
validator/client/beacon-api/attestation_data_test.go
Normal file
243
validator/client/beacon-api/attestation_data_test.go
Normal file
@ -0,0 +1,243 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
|
||||
)
|
||||
|
||||
const attestationDataEndpoint = "/eth/v1/validator/attestation_data"
|
||||
|
||||
func TestGetAttestationData_ValidAttestation(t *testing.T) {
|
||||
expectedSlot := uint64(5)
|
||||
expectedCommitteeIndex := uint64(6)
|
||||
expectedBeaconBlockRoot := "0x0636045df9bdda3ab96592cf5389032c8ec3977f911e2b53509b348dfe164d4d"
|
||||
expectedSourceEpoch := uint64(7)
|
||||
expectedSourceRoot := "0xd4bcbdefc8156e85247681086e8050e5d2d5d1bf076a25f6decd99250f3a378d"
|
||||
expectedTargetEpoch := uint64(8)
|
||||
expectedTargetRoot := "0x246590e8e4c2a9bd13cc776ecc7025bc432219f076e80b27267b8fa0456dc821"
|
||||
|
||||
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", expectedCommitteeIndex, expectedSlot),
|
||||
&produceAttestationDataResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.ProduceAttestationDataResponseJson{
|
||||
Data: &rpcmiddleware.AttestationDataJson{
|
||||
Slot: strconv.FormatUint(expectedSlot, 10),
|
||||
CommitteeIndex: strconv.FormatUint(expectedCommitteeIndex, 10),
|
||||
BeaconBlockRoot: expectedBeaconBlockRoot,
|
||||
Source: &rpcmiddleware.CheckpointJson{
|
||||
Epoch: strconv.FormatUint(expectedSourceEpoch, 10),
|
||||
Root: expectedSourceRoot,
|
||||
},
|
||||
Target: &rpcmiddleware.CheckpointJson{
|
||||
Epoch: strconv.FormatUint(expectedTargetEpoch, 10),
|
||||
Root: expectedTargetRoot,
|
||||
},
|
||||
},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
resp, err := validatorClient.getAttestationData(types.Slot(expectedSlot), types.CommitteeIndex(expectedCommitteeIndex))
|
||||
assert.NoError(t, err)
|
||||
|
||||
require.NotNil(t, resp)
|
||||
assert.Equal(t, expectedBeaconBlockRoot, hexutil.Encode(resp.BeaconBlockRoot))
|
||||
assert.Equal(t, expectedCommitteeIndex, uint64(resp.CommitteeIndex))
|
||||
assert.Equal(t, expectedSlot, uint64(resp.Slot))
|
||||
|
||||
require.NotNil(t, resp.Source)
|
||||
assert.Equal(t, expectedSourceEpoch, uint64(resp.Source.Epoch))
|
||||
assert.Equal(t, expectedSourceRoot, hexutil.Encode(resp.Source.Root))
|
||||
|
||||
require.NotNil(t, resp.Target)
|
||||
assert.Equal(t, expectedTargetEpoch, uint64(resp.Target.Epoch))
|
||||
assert.Equal(t, expectedTargetRoot, hexutil.Encode(resp.Target.Root))
|
||||
}
|
||||
|
||||
func TestGetAttestationData_InvalidData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
generateData func() rpcmiddleware.ProduceAttestationDataResponseJson
|
||||
expectedErrorMessage string
|
||||
}{
|
||||
{
|
||||
name: "nil attestation data",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
return rpcmiddleware.ProduceAttestationDataResponseJson{
|
||||
Data: nil,
|
||||
}
|
||||
},
|
||||
expectedErrorMessage: "attestation data is nil",
|
||||
},
|
||||
{
|
||||
name: "invalid committee index",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.CommitteeIndex = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "failed to parse attestation committee index: foo",
|
||||
},
|
||||
{
|
||||
name: "invalid block root",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.BeaconBlockRoot = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "invalid beacon block root: foo",
|
||||
},
|
||||
{
|
||||
name: "invalid slot",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Slot = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "failed to parse attestation slot: foo",
|
||||
},
|
||||
{
|
||||
name: "nil source",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Source = nil
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "attestation source is nil",
|
||||
},
|
||||
{
|
||||
name: "invalid source epoch",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Source.Epoch = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "failed to parse attestation source epoch: foo",
|
||||
},
|
||||
{
|
||||
name: "invalid source root",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Source.Root = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "invalid attestation source root: foo",
|
||||
},
|
||||
{
|
||||
name: "nil target",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Target = nil
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "attestation target is nil",
|
||||
},
|
||||
{
|
||||
name: "invalid target epoch",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Target.Epoch = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "failed to parse attestation target epoch: foo",
|
||||
},
|
||||
{
|
||||
name: "invalid target root",
|
||||
generateData: func() rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
attestation := generateValidAttestation(1, 2)
|
||||
attestation.Data.Target.Root = "foo"
|
||||
return attestation
|
||||
},
|
||||
expectedErrorMessage: "invalid attestation target root: foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
produceAttestationDataResponseJson := rpcmiddleware.ProduceAttestationDataResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/validator/attestation_data?committee_index=2&slot=1",
|
||||
&produceAttestationDataResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
testCase.generateData(),
|
||||
).Times(1)
|
||||
|
||||
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
_, err := validatorClient.getAttestationData(1, 2)
|
||||
assert.ErrorContains(t, testCase.expectedErrorMessage, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAttestationData_JsonResponseError(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 response error"),
|
||||
).Times(1)
|
||||
|
||||
validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
_, err := validatorClient.getAttestationData(slot, committeeIndex)
|
||||
assert.ErrorContains(t, "failed to get json response", err)
|
||||
assert.ErrorContains(t, "some specific json response error", err)
|
||||
}
|
||||
|
||||
func generateValidAttestation(slot uint64, committeeIndex uint64) rpcmiddleware.ProduceAttestationDataResponseJson {
|
||||
return rpcmiddleware.ProduceAttestationDataResponseJson{
|
||||
Data: &rpcmiddleware.AttestationDataJson{
|
||||
Slot: strconv.FormatUint(slot, 10),
|
||||
CommitteeIndex: strconv.FormatUint(committeeIndex, 10),
|
||||
BeaconBlockRoot: "0x5ecf3bff35e39d5f75476d42950d549f81fa93038c46b6652ae89ae1f7ad834f",
|
||||
Source: &rpcmiddleware.CheckpointJson{
|
||||
Epoch: "3",
|
||||
Root: "0x9023c9e64f23c1d451d5073c641f5f69597c2ad7d82f6f16e67d703e0ce5db8b",
|
||||
},
|
||||
Target: &rpcmiddleware.CheckpointJson{
|
||||
Epoch: "4",
|
||||
Root: "0xb154d46803b15b458ca822466547b054bc124338c6ee1d9c433dcde8c4457cca",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
neturl "net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
@ -14,3 +16,11 @@ func validRoot(root string) bool {
|
||||
}
|
||||
return matchesRegex
|
||||
}
|
||||
|
||||
func buildURL(path string, queryParams ...neturl.Values) string {
|
||||
if len(queryParams) == 0 {
|
||||
return path
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s?%s", path, queryParams[0].Encode())
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
@ -53,3 +54,20 @@ func TestBeaconApiHelpers(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildURL_NoParams(t *testing.T) {
|
||||
wanted := "/aaa/bbb/ccc"
|
||||
actual := buildURL("/aaa/bbb/ccc")
|
||||
assert.Equal(t, wanted, actual)
|
||||
}
|
||||
|
||||
func TestBuildURL_WithParams(t *testing.T) {
|
||||
params := url.Values{}
|
||||
params.Add("xxxx", "1")
|
||||
params.Add("yyyy", "2")
|
||||
params.Add("zzzz", "3")
|
||||
|
||||
wanted := "/aaa/bbb/ccc?xxxx=1&yyyy=2&zzzz=3"
|
||||
actual := buildURL("/aaa/bbb/ccc", params)
|
||||
assert.Equal(t, wanted, actual)
|
||||
}
|
||||
|
@ -17,19 +17,20 @@ import (
|
||||
)
|
||||
|
||||
type beaconApiValidatorClient struct {
|
||||
url string
|
||||
httpClient http.Client
|
||||
fallbackClient iface.ValidatorClient
|
||||
genesisProvider genesisProvider
|
||||
jsonRestHandler jsonRestHandler
|
||||
fallbackClient iface.ValidatorClient
|
||||
}
|
||||
|
||||
func NewBeaconApiValidatorClient(url string, timeout time.Duration) *beaconApiValidatorClient {
|
||||
httpClient := http.Client{Timeout: timeout}
|
||||
func NewBeaconApiValidatorClient(host string, timeout time.Duration) *beaconApiValidatorClient {
|
||||
jsonRestHandler := beaconApiJsonRestHandler{
|
||||
httpClient: http.Client{Timeout: timeout},
|
||||
host: host,
|
||||
}
|
||||
|
||||
return &beaconApiValidatorClient{
|
||||
url: url,
|
||||
httpClient: httpClient,
|
||||
genesisProvider: beaconApiGenesisProvider{httpClient: httpClient, url: url},
|
||||
genesisProvider: beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler},
|
||||
jsonRestHandler: jsonRestHandler,
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,13 +67,12 @@ func (c *beaconApiValidatorClient) DomainData(_ context.Context, in *ethpb.Domai
|
||||
return c.getDomainData(in.Epoch, domainType)
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) GetAttestationData(ctx context.Context, in *ethpb.AttestationDataRequest) (*ethpb.AttestationData, error) {
|
||||
if c.fallbackClient != nil {
|
||||
return c.fallbackClient.GetAttestationData(ctx, in)
|
||||
func (c *beaconApiValidatorClient) GetAttestationData(_ context.Context, in *ethpb.AttestationDataRequest) (*ethpb.AttestationData, error) {
|
||||
if in == nil {
|
||||
return nil, errors.New("GetAttestationData received nil argument `in`")
|
||||
}
|
||||
|
||||
// TODO: Implement me
|
||||
panic("beaconApiValidatorClient.GetAttestationData is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
|
||||
return c.getAttestationData(in.Slot, in.CommitteeIndex)
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) GetBeaconBlock(ctx context.Context, in *ethpb.BlockRequest) (*ethpb.GenericBeaconBlock, error) {
|
||||
@ -237,13 +237,8 @@ func (c *beaconApiValidatorClient) SubscribeCommitteeSubnets(ctx context.Context
|
||||
panic("beaconApiValidatorClient.SubscribeCommitteeSubnets is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) ValidatorIndex(ctx context.Context, in *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) {
|
||||
if c.fallbackClient != nil {
|
||||
return c.fallbackClient.ValidatorIndex(ctx, in)
|
||||
}
|
||||
|
||||
// TODO: Implement me
|
||||
panic("beaconApiValidatorClient.ValidatorIndex is not implemented. To use a fallback client, create this validator with NewBeaconApiValidatorClientWithFallback instead.")
|
||||
func (c *beaconApiValidatorClient) ValidatorIndex(_ context.Context, in *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) {
|
||||
return c.validatorIndex(in)
|
||||
}
|
||||
|
||||
func (c *beaconApiValidatorClient) ValidatorStatus(ctx context.Context, in *ethpb.ValidatorStatusRequest) (*ethpb.ValidatorStatusResponse, error) {
|
||||
|
@ -5,12 +5,15 @@ package beacon_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v3/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
@ -18,7 +21,45 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
|
||||
)
|
||||
|
||||
// Check that the DomainData() returns whatever the internal getDomainData() returns
|
||||
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)
|
||||
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,
|
||||
nil,
|
||||
).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.DeepEqual(t, expectedErr, err)
|
||||
assert.DeepEqual(t, expectedResp, resp)
|
||||
}
|
||||
|
||||
func TestBeaconApiValidatorClient_DomainDataValid(t *testing.T) {
|
||||
const genesisValidatorRoot = "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
epoch := params.BeaconConfig().AltairForkEpoch
|
||||
@ -50,3 +91,35 @@ func TestBeaconApiValidatorClient_DomainDataError(t *testing.T) {
|
||||
_, err := validatorClient.DomainData(context.Background(), ðpb.DomainRequest{Epoch: epoch, Domain: domainType})
|
||||
assert.ErrorContains(t, fmt.Sprintf("invalid domain type: %s", hexutil.Encode(domainType)), 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)
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ package beacon_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -22,8 +21,7 @@ type genesisProvider interface {
|
||||
}
|
||||
|
||||
type beaconApiGenesisProvider struct {
|
||||
httpClient http.Client
|
||||
url string
|
||||
jsonRestHandler jsonRestHandler
|
||||
}
|
||||
|
||||
func (c beaconApiValidatorClient) waitForChainStart(ctx context.Context) (*ethpb.ChainStartResponse, error) {
|
||||
@ -65,29 +63,12 @@ func (c beaconApiValidatorClient) waitForChainStart(ctx context.Context) (*ethpb
|
||||
return chainStartResponse, nil
|
||||
}
|
||||
|
||||
// GetGenesis gets the genesis information from the beacon node via the /eth/v1/beacon/genesis endpoint
|
||||
func (c beaconApiGenesisProvider) GetGenesis() (*rpcmiddleware.GenesisResponse_GenesisJson, *apimiddleware.DefaultErrorJson, error) {
|
||||
resp, err := c.httpClient.Get(c.url + "/eth/v1/beacon/genesis")
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to query REST API genesis endpoint")
|
||||
}
|
||||
defer func() {
|
||||
if err = resp.Body.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
errorJson := &apimiddleware.DefaultErrorJson{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&errorJson); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to decode response body genesis error json")
|
||||
}
|
||||
|
||||
return nil, errorJson, errors.Errorf("error %d: %s", errorJson.Code, errorJson.Message)
|
||||
}
|
||||
|
||||
genesisJson := &rpcmiddleware.GenesisResponseJson{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&genesisJson); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to decode response body genesis json")
|
||||
errorJson, err := c.jsonRestHandler.GetRestJsonResponse("/eth/v1/beacon/genesis", genesisJson)
|
||||
if err != nil {
|
||||
return nil, errorJson, errors.Wrap(err, "failed to get json response")
|
||||
}
|
||||
|
||||
if genesisJson.Data == nil {
|
||||
|
@ -4,25 +4,40 @@
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
|
||||
)
|
||||
|
||||
func TestGetGenesis_ValidGenesis(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.GenesisResponseJson{
|
||||
Data: &rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "1234",
|
||||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
}))
|
||||
defer server.Close()
|
||||
},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}}
|
||||
genesisProvider := &beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
resp, httpError, err := genesisProvider.GetGenesis()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, (*apimiddleware.DefaultErrorJson)(nil), httpError)
|
||||
@ -32,71 +47,50 @@ func TestGetGenesis_ValidGenesis(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetGenesis_NilData(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(nil))
|
||||
defer server.Close()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}}
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.GenesisResponseJson{Data: nil},
|
||||
).Times(1)
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
_, httpError, err := genesisProvider.GetGenesis()
|
||||
assert.Equal(t, (*apimiddleware.DefaultErrorJson)(nil), httpError)
|
||||
assert.ErrorContains(t, "genesis data is nil", err)
|
||||
}
|
||||
|
||||
func TestGetGenesis_InvalidJsonGenesis(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("foo"))
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer server.Close()
|
||||
func TestGetGenesis_JsonResponseError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}}
|
||||
expectedHttpErrorJson := &apimiddleware.DefaultErrorJson{
|
||||
Message: "http error message",
|
||||
Code: 999,
|
||||
}
|
||||
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
expectedHttpErrorJson,
|
||||
errors.New("some specific json response error"),
|
||||
).Times(1)
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
_, httpError, err := genesisProvider.GetGenesis()
|
||||
assert.Equal(t, (*apimiddleware.DefaultErrorJson)(nil), httpError)
|
||||
assert.ErrorContains(t, "failed to decode response body genesis json", err)
|
||||
}
|
||||
|
||||
func TestGetGenesis_InvalidJsonError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(invalidJsonErrHandler))
|
||||
defer server.Close()
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}}
|
||||
_, httpError, err := genesisProvider.GetGenesis()
|
||||
assert.Equal(t, (*apimiddleware.DefaultErrorJson)(nil), httpError)
|
||||
assert.ErrorContains(t, "failed to decode response body genesis error json", err)
|
||||
}
|
||||
|
||||
func TestGetGenesis_404Error(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(notFoundErrHandler))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}}
|
||||
_, httpError, err := validatorClient.GetGenesis()
|
||||
require.NotNil(t, httpError)
|
||||
assert.Equal(t, http.StatusNotFound, httpError.Code)
|
||||
assert.Equal(t, "Not found", httpError.Message)
|
||||
assert.ErrorContains(t, "error 404: Not found", err)
|
||||
}
|
||||
|
||||
func TestGetGenesis_500Error(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(internalServerErrHandler))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: time.Second * 5}}
|
||||
_, httpError, err := validatorClient.GetGenesis()
|
||||
require.NotNil(t, httpError)
|
||||
assert.Equal(t, http.StatusInternalServerError, httpError.Code)
|
||||
assert.Equal(t, "Internal server error", httpError.Message)
|
||||
assert.ErrorContains(t, "error 500: Internal server error", err)
|
||||
}
|
||||
|
||||
func TestGetGenesis_Timeout(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "1234",
|
||||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
genesisProvider := &beaconApiGenesisProvider{url: server.URL, httpClient: http.Client{Timeout: 1}}
|
||||
_, httpError, err := genesisProvider.GetGenesis()
|
||||
assert.Equal(t, (*apimiddleware.DefaultErrorJson)(nil), httpError)
|
||||
assert.ErrorContains(t, "failed to query REST API genesis endpoint", err)
|
||||
assert.ErrorContains(t, "failed to get json response", err)
|
||||
assert.ErrorContains(t, "some specific json response error", err)
|
||||
assert.DeepEqual(t, expectedHttpErrorJson, httpError)
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
)
|
||||
|
||||
func internalServerErrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
internalErrorJson := &apimiddleware.DefaultErrorJson{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: "Internal server error",
|
||||
}
|
||||
|
||||
marshalledError, err := json.Marshal(internalErrorJson)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, err = w.Write(marshalledError)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func notFoundErrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
internalErrorJson := &apimiddleware.DefaultErrorJson{
|
||||
Code: http.StatusNotFound,
|
||||
Message: "Not found",
|
||||
}
|
||||
|
||||
marshalledError, err := json.Marshal(internalErrorJson)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, err = w.Write(marshalledError)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidErr999Handler(w http.ResponseWriter, r *http.Request) {
|
||||
internalErrorJson := &apimiddleware.DefaultErrorJson{
|
||||
Code: 999,
|
||||
Message: "Invalid error",
|
||||
}
|
||||
|
||||
marshalledError, err := json.Marshal(internalErrorJson)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.WriteHeader(999)
|
||||
_, err = w.Write(marshalledError)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidJsonErrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, err := w.Write([]byte("foo"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidJsonResultHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("foo"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createGenesisHandler(data *rpcmiddleware.GenesisResponse_GenesisJson) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
genesisResponseJson := &rpcmiddleware.GenesisResponseJson{Data: data}
|
||||
marshalledResponse, err := json.Marshal(genesisResponseJson)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = w.Write(marshalledResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
35
validator/client/beacon-api/index.go
Normal file
35
validator/client/beacon-api/index.go
Normal file
@ -0,0 +1,35 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
func (c beaconApiValidatorClient) validatorIndex(in *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) {
|
||||
stringPubKey := hexutil.Encode(in.PublicKey)
|
||||
|
||||
stateValidator, err := c.getStateValidators([]string{stringPubKey})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get validator state")
|
||||
}
|
||||
|
||||
if len(stateValidator.Data) == 0 {
|
||||
return nil, errors.Errorf("could not find validator index for public key `%s`", stringPubKey)
|
||||
}
|
||||
|
||||
stringValidatorIndex := stateValidator.Data[0].Index
|
||||
|
||||
index, err := strconv.ParseUint(stringValidatorIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse validator index")
|
||||
}
|
||||
|
||||
return ðpb.ValidatorIndexResponse{Index: types.ValidatorIndex(index)}, nil
|
||||
}
|
181
validator/client/beacon-api/index_test.go
Normal file
181
validator/client/beacon-api/index_test.go
Normal file
@ -0,0 +1,181 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/pkg/errors"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
|
||||
)
|
||||
|
||||
const stringPubKey = "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"
|
||||
|
||||
func getPubKeyAndURL(t *testing.T, stringPubkey string) ([]byte, string) {
|
||||
baseUrl := "/eth/v1/beacon/states/head/validators"
|
||||
url := fmt.Sprintf("%s?id=%s", baseUrl, stringPubKey)
|
||||
|
||||
pubKey, err := hexutil.Decode(stringPubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
return pubKey, url
|
||||
}
|
||||
|
||||
func TestIndex_Nominal(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
pubKey, url := getPubKeyAndURL(t, stringPubKey)
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.StateValidatorsResponseJson{
|
||||
Data: []*rpcmiddleware.ValidatorContainerJson{
|
||||
{
|
||||
Index: "55293",
|
||||
Status: "active_ongoing",
|
||||
Validator: &rpcmiddleware.ValidatorJson{
|
||||
PublicKey: stringPubKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
|
||||
validatorIndex, err := validatorClient.ValidatorIndex(
|
||||
context.Background(),
|
||||
ðpb.ValidatorIndexRequest{
|
||||
PublicKey: pubKey,
|
||||
},
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, types.ValidatorIndex(55293), validatorIndex.Index)
|
||||
}
|
||||
|
||||
func TestIndex_UnexistingValidator(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
pubKey, url := getPubKeyAndURL(t, stringPubKey)
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.StateValidatorsResponseJson{
|
||||
Data: []*rpcmiddleware.ValidatorContainerJson{},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
|
||||
_, err := validatorClient.ValidatorIndex(
|
||||
context.Background(),
|
||||
ðpb.ValidatorIndexRequest{
|
||||
PublicKey: pubKey,
|
||||
},
|
||||
)
|
||||
|
||||
wanted := "could not find validator index for public key `0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13`"
|
||||
assert.ErrorContains(t, wanted, err)
|
||||
}
|
||||
|
||||
func TestIndex_BadIndexError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
pubKey, url := getPubKeyAndURL(t, stringPubKey)
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.StateValidatorsResponseJson{
|
||||
Data: []*rpcmiddleware.ValidatorContainerJson{
|
||||
{
|
||||
Index: "This is not an index",
|
||||
Status: "active_ongoing",
|
||||
Validator: &rpcmiddleware.ValidatorJson{
|
||||
PublicKey: stringPubKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
|
||||
_, err := validatorClient.ValidatorIndex(
|
||||
context.Background(),
|
||||
ðpb.ValidatorIndexRequest{
|
||||
PublicKey: pubKey,
|
||||
},
|
||||
)
|
||||
|
||||
assert.ErrorContains(t, "failed to parse validator index", err)
|
||||
}
|
||||
|
||||
func TestIndex_JsonResponseError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
pubKey, url := getPubKeyAndURL(t, stringPubKey)
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
errors.New("some specific json error"),
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
|
||||
_, err := validatorClient.ValidatorIndex(
|
||||
context.Background(),
|
||||
ðpb.ValidatorIndexRequest{
|
||||
PublicKey: pubKey,
|
||||
},
|
||||
)
|
||||
|
||||
assert.ErrorContains(t, "failed to get validator state", err)
|
||||
}
|
55
validator/client/beacon-api/json_rest_handler.go
Normal file
55
validator/client/beacon-api/json_rest_handler.go
Normal file
@ -0,0 +1,55 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware"
|
||||
)
|
||||
|
||||
type jsonRestHandler interface {
|
||||
GetRestJsonResponse(query string, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error)
|
||||
}
|
||||
|
||||
type beaconApiJsonRestHandler struct {
|
||||
httpClient http.Client
|
||||
host string
|
||||
}
|
||||
|
||||
// GetRestJsonResponse sends a GET requests to apiEndpoint and decodes the response body as a JSON object into responseJson.
|
||||
// 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) GetRestJsonResponse(apiEndpoint string, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) {
|
||||
if responseJson == nil {
|
||||
return nil, errors.New("responseJson is nil")
|
||||
}
|
||||
|
||||
url := c.host + apiEndpoint
|
||||
resp, err := c.httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query REST API %s", url)
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
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 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)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
192
validator/client/beacon-api/json_rest_handler_test.go
Normal file
192
validator/client/beacon-api/json_rest_handler_test.go
Normal file
@ -0,0 +1,192 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
)
|
||||
|
||||
func TestGetRestJsonResponse_Valid(t *testing.T) {
|
||||
const endpoint = "/example/rest/api/endpoint"
|
||||
|
||||
genesisJson := &rpcmiddleware.GenesisResponseJson{
|
||||
Data: &rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "123",
|
||||
GenesisValidatorsRoot: "0x456",
|
||||
GenesisForkVersion: "0x789",
|
||||
},
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
|
||||
// Make sure the url parameters match
|
||||
assert.Equal(t, "abc", r.URL.Query().Get("arg1"))
|
||||
assert.Equal(t, "def", r.URL.Query().Get("arg2"))
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
responseJson := &rpcmiddleware.GenesisResponseJson{}
|
||||
_, err := jsonRestHandler.GetRestJsonResponse(endpoint+"?arg1=abc&arg2=def", responseJson)
|
||||
assert.NoError(t, err)
|
||||
assert.DeepEqual(t, genesisJson, responseJson)
|
||||
}
|
||||
|
||||
func TestGetRestJsonResponse_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 interface{}
|
||||
}{
|
||||
{
|
||||
name: "nil response json",
|
||||
funcHandler: invalidJsonResponseHandler,
|
||||
expectedErrorMessage: "responseJson is nil",
|
||||
timeout: time.Second * 5,
|
||||
responseJson: 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{},
|
||||
},
|
||||
{
|
||||
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,
|
||||
responseJson: &rpcmiddleware.GenesisResponseJson{},
|
||||
},
|
||||
{
|
||||
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,
|
||||
responseJson: &rpcmiddleware.GenesisResponseJson{},
|
||||
},
|
||||
{
|
||||
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,
|
||||
responseJson: &rpcmiddleware.GenesisResponseJson{},
|
||||
},
|
||||
{
|
||||
name: "bad error json formatting",
|
||||
funcHandler: invalidJsonErrHandler,
|
||||
expectedErrorMessage: "failed to decode error json",
|
||||
timeout: time.Second * 5,
|
||||
responseJson: &rpcmiddleware.GenesisResponseJson{},
|
||||
},
|
||||
{
|
||||
name: "bad response json formatting",
|
||||
funcHandler: invalidJsonResponseHandler,
|
||||
expectedErrorMessage: "failed to decode response json",
|
||||
timeout: time.Second * 5,
|
||||
responseJson: &rpcmiddleware.GenesisResponseJson{},
|
||||
},
|
||||
{
|
||||
name: "timeout",
|
||||
funcHandler: httpErrorJsonHandler(http.StatusNotFound, "Not found"),
|
||||
expectedErrorMessage: "failed to query REST API",
|
||||
timeout: 1,
|
||||
responseJson: &rpcmiddleware.GenesisResponseJson{},
|
||||
},
|
||||
}
|
||||
|
||||
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.GetRestJsonResponse(endpoint, 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, r *http.Request) {
|
||||
errorJson := &apimiddleware.DefaultErrorJson{
|
||||
Code: statusCode,
|
||||
Message: errorMessage,
|
||||
}
|
||||
|
||||
marshalledError, err := json.Marshal(errorJson)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.WriteHeader(statusCode)
|
||||
_, err = w.Write(marshalledError)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func invalidJsonErrHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, err := w.Write([]byte("foo"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidJsonResponseHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("foo"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
# gazelle:build_tags use_beacon_api
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["genesis_mock.go"],
|
||||
srcs = [
|
||||
"genesis_mock.go",
|
||||
"json_rest_handler_mock.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock",
|
||||
visibility = ["//validator:__subpackages__"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/gateway/apimiddleware:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
|
50
validator/client/beacon-api/mock/json_rest_handler_mock.go
generated
Normal file
50
validator/client/beacon-api/mock/json_rest_handler_mock.go
generated
Normal file
@ -0,0 +1,50 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: validator/client/beacon-api/json_rest_handler.go
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
apimiddleware "github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware"
|
||||
)
|
||||
|
||||
// MockjsonRestHandler is a mock of jsonRestHandler interface.
|
||||
type MockjsonRestHandler struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockjsonRestHandlerMockRecorder
|
||||
}
|
||||
|
||||
// MockjsonRestHandlerMockRecorder is the mock recorder for MockjsonRestHandler.
|
||||
type MockjsonRestHandlerMockRecorder struct {
|
||||
mock *MockjsonRestHandler
|
||||
}
|
||||
|
||||
// NewMockjsonRestHandler creates a new mock instance.
|
||||
func NewMockjsonRestHandler(ctrl *gomock.Controller) *MockjsonRestHandler {
|
||||
mock := &MockjsonRestHandler{ctrl: ctrl}
|
||||
mock.recorder = &MockjsonRestHandlerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockjsonRestHandler) EXPECT() *MockjsonRestHandlerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetRestJsonResponse mocks base method.
|
||||
func (m *MockjsonRestHandler) GetRestJsonResponse(query string, responseJson interface{}) (*apimiddleware.DefaultErrorJson, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetRestJsonResponse", query, responseJson)
|
||||
ret0, _ := ret[0].(*apimiddleware.DefaultErrorJson)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetRestJsonResponse indicates an expected call of GetRestJsonResponse.
|
||||
func (mr *MockjsonRestHandlerMockRecorder) GetRestJsonResponse(query, responseJson interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRestJsonResponse", reflect.TypeOf((*MockjsonRestHandler)(nil).GetRestJsonResponse), query, responseJson)
|
||||
}
|
37
validator/client/beacon-api/state_validators.go
Normal file
37
validator/client/beacon-api/state_validators.go
Normal file
@ -0,0 +1,37 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
neturl "net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
)
|
||||
|
||||
func (c *beaconApiValidatorClient) getStateValidators(stringPubkeys []string) (*rpcmiddleware.StateValidatorsResponseJson, error) {
|
||||
params := neturl.Values{}
|
||||
|
||||
for _, stringPubkey := range stringPubkeys {
|
||||
params.Add("id", stringPubkey)
|
||||
}
|
||||
|
||||
url := buildURL(
|
||||
"/eth/v1/beacon/states/head/validators",
|
||||
params,
|
||||
)
|
||||
|
||||
stateValidatorsJson := &rpcmiddleware.StateValidatorsResponseJson{}
|
||||
|
||||
_, err := c.jsonRestHandler.GetRestJsonResponse(url, stateValidatorsJson)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get json response")
|
||||
}
|
||||
|
||||
if stateValidatorsJson.Data == nil {
|
||||
return nil, errors.New("stateValidatorsJson.Data is nil")
|
||||
}
|
||||
|
||||
return stateValidatorsJson, nil
|
||||
}
|
133
validator/client/beacon-api/state_validators_test.go
Normal file
133
validator/client/beacon-api/state_validators_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
//go:build use_beacon_api
|
||||
// +build use_beacon_api
|
||||
|
||||
package beacon_api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/pkg/errors"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
|
||||
)
|
||||
|
||||
func TestGetStateValidators_Nominal(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
url := strings.Join([]string{
|
||||
"/eth/v1/beacon/states/head/validators?",
|
||||
"id=0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13&", // active_ongoing
|
||||
"id=0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526&", // active_exiting
|
||||
"id=0x424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242&", // does not exist
|
||||
"id=0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5", // exited_slashed
|
||||
}, "")
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
wanted := []*rpcmiddleware.ValidatorContainerJson{
|
||||
{
|
||||
Index: "55293",
|
||||
Status: "active_ongoing",
|
||||
Validator: &rpcmiddleware.ValidatorJson{
|
||||
PublicKey: "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13",
|
||||
},
|
||||
},
|
||||
{
|
||||
Index: "55294",
|
||||
Status: "active_exiting",
|
||||
Validator: &rpcmiddleware.ValidatorJson{
|
||||
PublicKey: "0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526",
|
||||
},
|
||||
},
|
||||
{
|
||||
Index: "55295",
|
||||
Status: "exited_slashed",
|
||||
Validator: &rpcmiddleware.ValidatorJson{
|
||||
PublicKey: "0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.StateValidatorsResponseJson{
|
||||
Data: wanted,
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
actual, err := validatorClient.getStateValidators([]string{
|
||||
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing
|
||||
"0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526", // active_exiting
|
||||
"0x424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242", // does not exist
|
||||
"0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5", // exited_slashed
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, wanted, actual.Data)
|
||||
}
|
||||
|
||||
func TestGetStateValidators_GetRestJsonResponseOnError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
url := "/eth/v1/beacon/states/head/validators?id=0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
errors.New("an error"),
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
_, err := validatorClient.getStateValidators([]string{
|
||||
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing
|
||||
})
|
||||
assert.ErrorContains(t, "an error", err)
|
||||
assert.ErrorContains(t, "failed to get json response", err)
|
||||
}
|
||||
|
||||
func TestGetStateValidators_DataIsNil(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
url := "/eth/v1/beacon/states/head/validators?id=0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13"
|
||||
|
||||
stateValidatorsResponseJson := rpcmiddleware.StateValidatorsResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
url,
|
||||
&stateValidatorsResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.StateValidatorsResponseJson{
|
||||
Data: nil,
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := beaconApiValidatorClient{jsonRestHandler: jsonRestHandler}
|
||||
_, err := validatorClient.getStateValidators([]string{
|
||||
"0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing
|
||||
})
|
||||
assert.ErrorContains(t, "stateValidatorsJson.Data is nil", err)
|
||||
}
|
@ -5,27 +5,44 @@ package beacon_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/prysmaticlabs/prysm/v3/api/gateway/apimiddleware"
|
||||
rpcmiddleware "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v3/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v3/validator/client/beacon-api/mock"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func TestWaitForChainStart_ValidGenesis(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.GenesisResponseJson{
|
||||
Data: &rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "1234",
|
||||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
genesisProvider := beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
validatorClient := beaconApiValidatorClient{genesisProvider: genesisProvider}
|
||||
resp, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
@ -38,132 +55,131 @@ func TestWaitForChainStart_ValidGenesis(t *testing.T) {
|
||||
assert.DeepEqual(t, expectedRoot, resp.GenesisValidatorsRoot)
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_NilData(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(nil))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "failed to get genesis data", err)
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_InvalidTime(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
func TestWaitForChainStart_BadGenesis(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data *rpcmiddleware.GenesisResponse_GenesisJson
|
||||
errorMessage string
|
||||
}{
|
||||
{
|
||||
name: "nil data",
|
||||
data: nil,
|
||||
errorMessage: "failed to get genesis data",
|
||||
},
|
||||
{
|
||||
name: "invalid time",
|
||||
data: &rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "foo",
|
||||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "failed to parse genesis time", err)
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_EmptyTime(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "",
|
||||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "failed to parse genesis time", err)
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_InvalidRoot(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
},
|
||||
errorMessage: "failed to parse genesis time: foo",
|
||||
},
|
||||
{
|
||||
name: "invalid root",
|
||||
data: &rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "1234",
|
||||
GenesisValidatorsRoot: "0xzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "invalid genesis validators root", err)
|
||||
},
|
||||
errorMessage: "invalid genesis validators root: ",
|
||||
},
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_EmptyRoot(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "1234",
|
||||
GenesisValidatorsRoot: "",
|
||||
}))
|
||||
defer server.Close()
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.GenesisResponseJson{
|
||||
Data: testCase.data,
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
genesisProvider := beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
validatorClient := beaconApiValidatorClient{genesisProvider: genesisProvider}
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "invalid genesis validators root", err)
|
||||
assert.ErrorContains(t, testCase.errorMessage, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_InvalidJsonGenesis(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("foo"))
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer server.Close()
|
||||
func TestWaitForChainStart_JsonResponseError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
errors.New("some specific json error"),
|
||||
).Times(1)
|
||||
|
||||
genesisProvider := beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
validatorClient := beaconApiValidatorClient{genesisProvider: genesisProvider}
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "failed to get genesis data", err)
|
||||
assert.ErrorContains(t, "some specific json error", err)
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_InternalServerError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(internalServerErrHandler))
|
||||
defer server.Close()
|
||||
// For WaitForChainStart, error 404 just means that we keep retrying until the information becomes available
|
||||
func TestWaitForChainStart_JsonResponseError404(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "500: Internal server error", err)
|
||||
}
|
||||
genesisResponseJson := rpcmiddleware.GenesisResponseJson{}
|
||||
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
|
||||
|
||||
func TestWaitForChainStart_NotFoundErrorContextCancelled(t *testing.T) {
|
||||
// WaitForChainStart blocks until the error is not 404, but it needs to listen to context cancellations
|
||||
server := httptest.NewServer(http.HandlerFunc(notFoundErrHandler))
|
||||
defer server.Close()
|
||||
// First, mock a request that receives a 404 error (which means that the genesis data is not available yet)
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
&apimiddleware.DefaultErrorJson{
|
||||
Code: http.StatusNotFound,
|
||||
Message: "404 error",
|
||||
},
|
||||
errors.New("404 error"),
|
||||
).Times(1)
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
|
||||
// Create a context that can be canceled
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Cancel the context after 1 second
|
||||
go func(ctx context.Context) {
|
||||
time.Sleep(time.Second)
|
||||
cancel()
|
||||
}(ctx)
|
||||
|
||||
_, err := validatorClient.WaitForChainStart(ctx, &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "context canceled", err)
|
||||
}
|
||||
|
||||
// This test makes sure that we handle even errors not specified in the Beacon API spec
|
||||
func TestWaitForChainStart_UnknownError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(invalidErr999Handler))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "999: Invalid error", err)
|
||||
}
|
||||
|
||||
// Make sure that we fail gracefully if the error json is not valid json
|
||||
func TestWaitForChainStart_InvalidJsonError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(invalidJsonErrHandler))
|
||||
defer server.Close()
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, time.Second*5)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "failed to get genesis data", err)
|
||||
}
|
||||
|
||||
func TestWaitForChainStart_Timeout(t *testing.T) {
|
||||
server := httptest.NewServer(createGenesisHandler(&rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
// After receiving a 404 error, mock a request that actually has genesis data available
|
||||
jsonRestHandler.EXPECT().GetRestJsonResponse(
|
||||
"/eth/v1/beacon/genesis",
|
||||
&genesisResponseJson,
|
||||
).Return(
|
||||
nil,
|
||||
nil,
|
||||
).SetArg(
|
||||
1,
|
||||
rpcmiddleware.GenesisResponseJson{
|
||||
Data: &rpcmiddleware.GenesisResponse_GenesisJson{
|
||||
GenesisTime: "1234",
|
||||
GenesisValidatorsRoot: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
}))
|
||||
defer server.Close()
|
||||
},
|
||||
},
|
||||
).Times(1)
|
||||
|
||||
validatorClient := NewBeaconApiValidatorClient(server.URL, 1)
|
||||
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "failed to get genesis data", err)
|
||||
genesisProvider := beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler}
|
||||
validatorClient := beaconApiValidatorClient{genesisProvider: genesisProvider}
|
||||
resp, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
require.NotNil(t, resp)
|
||||
assert.Equal(t, true, resp.Started)
|
||||
assert.Equal(t, uint64(1234), resp.GenesisTime)
|
||||
|
||||
expectedRoot, err := hexutil.Decode("0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2")
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, expectedRoot, resp.GenesisValidatorsRoot)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user