From 34c6f624f18b5faed41ebe38beeab26d85e4ac71 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 16 Nov 2023 10:02:42 -0600 Subject: [PATCH] fixing squashing changes, migrates beacon , account, and auth endpoints on validator client --- api/BUILD.bazel | 5 +- api/constants.go | 3 + beacon-chain/rpc/eth/shared/BUILD.bazel | 1 + beacon-chain/rpc/eth/shared/structs.go | 104 +++++++++ .../rpc/eth/shared/structs_validator.go | 145 ++++++++++++ .../v1alpha1/validator-client/web_api.proto | 218 ------------------ .../evaluators/api_gateway_v1alpha1.go | 39 ++-- validator/rpc/BUILD.bazel | 8 +- validator/rpc/beacon.go | 88 ------- validator/rpc/beacon_test.go | 71 ------ .../rpc/{accounts.go => handlers_accounts.go} | 0 ...unts_test.go => handlers_accounts_test.go} | 0 validator/rpc/handlers_beacon.go | 214 +++++++++++++++++ validator/rpc/handlers_beacon_test.go | 80 +++++++ validator/rpc/intercepter.go | 23 ++ validator/rpc/server.go | 28 ++- validator/rpc/structs.go | 54 +++++ 17 files changed, 677 insertions(+), 404 deletions(-) create mode 100644 api/constants.go create mode 100644 beacon-chain/rpc/eth/shared/structs_validator.go rename validator/rpc/{accounts.go => handlers_accounts.go} (100%) rename validator/rpc/{accounts_test.go => handlers_accounts_test.go} (100%) create mode 100644 validator/rpc/handlers_beacon.go create mode 100644 validator/rpc/handlers_beacon_test.go diff --git a/api/BUILD.bazel b/api/BUILD.bazel index f6da6a95df5b..d073e46f07a7 100644 --- a/api/BUILD.bazel +++ b/api/BUILD.bazel @@ -2,7 +2,10 @@ load("@prysm//tools/go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = ["headers.go"], + srcs = [ + "constants.go", + "headers.go", + ], importpath = "github.com/prysmaticlabs/prysm/v4/api", visibility = ["//visibility:public"], ) diff --git a/api/constants.go b/api/constants.go new file mode 100644 index 000000000000..3266cd934d98 --- /dev/null +++ b/api/constants.go @@ -0,0 +1,3 @@ +package api + +const WebUrlPrefix = "/v2/validator/" diff --git a/beacon-chain/rpc/eth/shared/BUILD.bazel b/beacon-chain/rpc/eth/shared/BUILD.bazel index 6ec854f27c6b..85e4972b4627 100644 --- a/beacon-chain/rpc/eth/shared/BUILD.bazel +++ b/beacon-chain/rpc/eth/shared/BUILD.bazel @@ -8,6 +8,7 @@ go_library( "structs.go", "structs_blocks.go", "structs_blocks_conversions.go", + "structs_validator.go", ], importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared", visibility = ["//visibility:public"], diff --git a/beacon-chain/rpc/eth/shared/structs.go b/beacon-chain/rpc/eth/shared/structs.go index b4cc3a40d762..45a014de4349 100644 --- a/beacon-chain/rpc/eth/shared/structs.go +++ b/beacon-chain/rpc/eth/shared/structs.go @@ -637,3 +637,107 @@ type SyncDetails struct { type SyncDetailsContainer struct { Data *SyncDetails `json:"data"` } + +// ChainHead is the response for api endpoint /beacon/chainhead +type ChainHead struct { + HeadSlot string `json:"head_slot"` + HeadEpoch string `json:"head_epoch"` + HeadBlockRoot string `json:"head_block_root"` + FinalizedSlot string `json:"finalized_slot"` + FinalizedEpoch string `json:"finalized_epoch"` + FinalizedBlockRoot string `json:"finalized_block_root"` + JustifiedSlot string `json:"justified_slot"` + JustifiedEpoch string `json:"justified_epoch"` + JustifiedBlockRoot string `json:"justified_block_root"` + PreviousJustifiedSlot string `json:"previous_justified_slot"` + PreviousJustifiedEpoch string `json:"previous_justified_epoch"` + PreviousJustifiedBlockRoot string `json:"previous_justified_block_root"` + OptimisticStatus bool `json:"optimistic_status"` +} + +func ChainHeadResponseFromConsensus(e *eth.ChainHead) (*ChainHead, error) { + if e == nil { + return nil, errors.New("ChainHead is empty") + } + return &ChainHead{ + HeadSlot: fmt.Sprintf("%d", e.HeadSlot), + HeadEpoch: fmt.Sprintf("%d", e.HeadEpoch), + HeadBlockRoot: hexutil.Encode(e.HeadBlockRoot), + FinalizedSlot: fmt.Sprintf("%d", e.FinalizedSlot), + FinalizedEpoch: fmt.Sprintf("%d", e.FinalizedEpoch), + FinalizedBlockRoot: hexutil.Encode(e.FinalizedBlockRoot), + JustifiedSlot: fmt.Sprintf("%d", e.JustifiedSlot), + JustifiedEpoch: fmt.Sprintf("%d", e.JustifiedEpoch), + JustifiedBlockRoot: hexutil.Encode(e.JustifiedBlockRoot), + PreviousJustifiedSlot: fmt.Sprintf("%d", e.PreviousJustifiedSlot), + PreviousJustifiedEpoch: fmt.Sprintf("%d", e.PreviousJustifiedEpoch), + PreviousJustifiedBlockRoot: hexutil.Encode(e.PreviousJustifiedBlockRoot), + OptimisticStatus: e.OptimisticStatus, + }, nil +} + +func (m *ChainHead) ToConsensus() (*eth.ChainHead, error) { + headSlot, err := strconv.ParseUint(m.HeadSlot, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "HeadSlot") + } + headEpoch, err := strconv.ParseUint(m.HeadEpoch, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "HeadEpoch") + } + headBlockRoot, err := DecodeHexWithLength(m.HeadBlockRoot, fieldparams.RootLength) + if err != nil { + return nil, NewDecodeError(err, "HeadBlockRoot") + } + finalizedSlot, err := strconv.ParseUint(m.FinalizedSlot, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "FinalizedSlot") + } + finalizedEpoch, err := strconv.ParseUint(m.FinalizedEpoch, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "FinalizedEpoch") + } + finalizedBlockRoot, err := DecodeHexWithLength(m.FinalizedBlockRoot, fieldparams.RootLength) + if err != nil { + return nil, NewDecodeError(err, "FinalizedBlockRoot") + } + justifiedSlot, err := strconv.ParseUint(m.JustifiedSlot, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "JustifiedSlot") + } + justifiedEpoch, err := strconv.ParseUint(m.JustifiedEpoch, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "JustifiedEpoch") + } + justifiedBlockRoot, err := DecodeHexWithLength(m.JustifiedBlockRoot, fieldparams.RootLength) + if err != nil { + return nil, NewDecodeError(err, "JustifiedBlockRoot") + } + previousjustifiedSlot, err := strconv.ParseUint(m.PreviousJustifiedSlot, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "PreviousJustifiedSlot") + } + previousjustifiedEpoch, err := strconv.ParseUint(m.PreviousJustifiedEpoch, 10, 64) + if err != nil { + return nil, NewDecodeError(err, "PreviousJustifiedEpoch") + } + previousjustifiedBlockRoot, err := DecodeHexWithLength(m.PreviousJustifiedBlockRoot, fieldparams.RootLength) + if err != nil { + return nil, NewDecodeError(err, "PreviousJustifiedBlockRoot") + } + return ð.ChainHead{ + HeadSlot: primitives.Slot(headSlot), + HeadEpoch: primitives.Epoch(headEpoch), + HeadBlockRoot: headBlockRoot, + FinalizedSlot: primitives.Slot(finalizedSlot), + FinalizedEpoch: primitives.Epoch(finalizedEpoch), + FinalizedBlockRoot: finalizedBlockRoot, + JustifiedSlot: primitives.Slot(justifiedSlot), + JustifiedEpoch: primitives.Epoch(justifiedEpoch), + JustifiedBlockRoot: justifiedBlockRoot, + PreviousJustifiedSlot: primitives.Slot(previousjustifiedSlot), + PreviousJustifiedEpoch: primitives.Epoch(previousjustifiedEpoch), + PreviousJustifiedBlockRoot: previousjustifiedBlockRoot, + OptimisticStatus: m.OptimisticStatus, + }, nil +} diff --git a/beacon-chain/rpc/eth/shared/structs_validator.go b/beacon-chain/rpc/eth/shared/structs_validator.go new file mode 100644 index 000000000000..08fbf43220f3 --- /dev/null +++ b/beacon-chain/rpc/eth/shared/structs_validator.go @@ -0,0 +1,145 @@ +package shared + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" +) + +type ValidatorPerformanceResponse struct { + CurrentEffectiveBalances []uint64 `json:"current_effective_balances"` + InclusionSlots []uint64 `json:"inclusion_slots"` + InclusionDistances []uint64 `json:"inclusion_distances"` + CorrectlyVotedSource []bool `json:"correctly_voted_source"` + CorrectlyVotedTarget []bool `json:"correctly_voted_target"` + CorrectlyVotedHead []bool `json:"correctly_voted_head"` + BalancesBeforeEpochTransition []uint64 `json:"balances_before_epoch_transition"` + BalancesAfterEpochTransition []uint64 `json:"balances_after_epoch_transition"` + MissingValidators []string `json:"missing_validators"` + AverageActiveValidatorBalance float32 `json:"average_active_validator_balance"` + PublicKeys []string `json:"public_keys"` + InactivityScores []uint64 `json:"inactivity_scores"` +} + +func ValidatorPerformanceResponseFromConsensus(e *eth.ValidatorPerformanceResponse) (*ValidatorPerformanceResponse, error) { + if e == nil { + return nil, errors.New("ValidatorPerformanceResponse is empty") + } + inclusionSlots := make([]uint64, len(e.InclusionSlots)) + for i, index := range e.InclusionSlots { + inclusionSlots[i] = uint64(index) + } + inclusionDistances := make([]uint64, len(e.InclusionDistances)) + for i, index := range e.InclusionDistances { + inclusionDistances[i] = uint64(index) + } + missingValidators := make([]string, len(e.MissingValidators)) + for i, key := range e.MissingValidators { + missingValidators[i] = hexutil.Encode(key) + } + publicKeys := make([]string, len(e.PublicKeys)) + for i, key := range e.PublicKeys { + publicKeys[i] = hexutil.Encode(key) + } + return &ValidatorPerformanceResponse{ + CurrentEffectiveBalances: e.CurrentEffectiveBalances, + InclusionSlots: inclusionSlots, + InclusionDistances: inclusionDistances, + CorrectlyVotedSource: e.CorrectlyVotedSource, + CorrectlyVotedTarget: e.CorrectlyVotedTarget, + CorrectlyVotedHead: e.CorrectlyVotedHead, + BalancesBeforeEpochTransition: e.BalancesBeforeEpochTransition, + BalancesAfterEpochTransition: e.BalancesAfterEpochTransition, + MissingValidators: missingValidators, + AverageActiveValidatorBalance: e.AverageActiveValidatorBalance, + PublicKeys: publicKeys, + InactivityScores: e.InactivityScores, + }, nil +} + +type ValidatorBalancesResponse struct { + Epoch uint64 `json:"epoch"` + Balances []*ValidatorBalance `json:"balances"` + NextPageToken string `json:"next_page_token"` + TotalSize int32 `json:"total_size,omitempty"` +} + +type ValidatorBalance struct { + PublicKey string `json:"public_key"` + Index uint64 `json:"index"` + Balance uint64 `json:"balance"` + Status string `json:"status"` +} + +func ValidatorBalancesResponseFromConsensus(e *eth.ValidatorBalances) (*ValidatorBalancesResponse, error) { + if e == nil { + return nil, errors.New("ValidatorBalances is empty") + } + balances := make([]*ValidatorBalance, len(e.Balances)) + for i, balance := range e.Balances { + balances[i] = &ValidatorBalance{ + PublicKey: hexutil.Encode(balance.PublicKey), + Index: uint64(balance.Index), + Balance: balance.Balance, + Status: balance.Status, + } + } + return &ValidatorBalancesResponse{ + Epoch: uint64(e.Epoch), + Balances: balances, + NextPageToken: e.NextPageToken, + TotalSize: e.TotalSize, + }, nil +} + +type ValidatorsResponse struct { + Epoch uint64 `json:"epoch"` + ValidatorList []*ValidatorContainer `json:"validator_list"` + NextPageToken string `json:"next_page_token"` + TotalSize int32 `json:"total_size"` +} + +type ValidatorContainer struct { + Index uint64 `json:"index"` + Validator *Validator `json:"validator"` +} + +type Validator struct { + PublicKey string `json:"public_key,omitempty"` + WithdrawalCredentials string `json:"withdrawal_credentials"` + EffectiveBalance uint64 `json:"effective_balance"` + Slashed bool `json:"slashed"` + ActivationEligibilityEpoch uint64 `json:"activation_eligibility_epoch"` + ActivationEpoch uint64 `json:"activation_epoch"` + ExitEpoch uint64 `json:"exit_epoch"` + WithdrawableEpoch uint64 `json:"withdrawable_epoch"` +} + +func ValidatorsResponseFromConsensus(e *eth.Validators) (*ValidatorsResponse, error) { + if e == nil { + return nil, errors.New("VValidatorsResponse is empty") + } + validatorList := make([]*ValidatorContainer, len(e.ValidatorList)) + for i, validatorContainer := range e.ValidatorList { + val := validatorContainer.Validator + validatorList[i] = &ValidatorContainer{ + Index: uint64(validatorContainer.Index), + Validator: &Validator{ + PublicKey: hexutil.Encode(val.PublicKey), + WithdrawalCredentials: hexutil.Encode(val.WithdrawalCredentials), + EffectiveBalance: val.EffectiveBalance, + Slashed: val.Slashed, + ActivationEligibilityEpoch: uint64(val.ActivationEligibilityEpoch), + ActivationEpoch: uint64(val.ActivationEpoch), + ExitEpoch: uint64(val.ExitEpoch), + WithdrawableEpoch: uint64(val.WithdrawableEpoch), + }, + } + } + return &ValidatorsResponse{ + Epoch: uint64(e.Epoch), + ValidatorList: validatorList, + NextPageToken: e.NextPageToken, + TotalSize: e.TotalSize, + }, nil +} diff --git a/proto/prysm/v1alpha1/validator-client/web_api.proto b/proto/prysm/v1alpha1/validator-client/web_api.proto index 8d5817984939..9331806469f4 100644 --- a/proto/prysm/v1alpha1/validator-client/web_api.proto +++ b/proto/prysm/v1alpha1/validator-client/web_api.proto @@ -1,9 +1,6 @@ syntax = "proto3"; package ethereum.validator.accounts.v2; -import "proto/prysm/v1alpha1/beacon_chain.proto"; -import "proto/prysm/v1alpha1/node.proto"; -import "google/api/annotations.proto"; import "google/protobuf/empty.proto"; option csharp_namespace = "Ethereum.Validator.Accounts.V2"; @@ -13,88 +10,6 @@ option java_outer_classname = "WebProto"; option java_package = "org.ethereum.validator.accounts.v2"; option php_namespace = "Ethereum\\Validator\\Accounts\\V2"; -// Account related commands will either need to be done through the Keymanager APIs https://ethereum.github.io/keymanager-APIs/ -// or through validator client CLI commands -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -service Accounts { - rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/accounts" - }; - } - rpc BackupAccounts(BackupAccountsRequest) returns (BackupAccountsResponse) { - option deprecated = true; - option (google.api.http) = { - post: "/v2/validator/accounts/backup", - body: "*" - }; - } - rpc VoluntaryExit(VoluntaryExitRequest) returns (VoluntaryExitResponse) { - option deprecated = true; - option (google.api.http) = { - post: "/v2/validator/accounts/voluntary-exit", - body: "*" - }; - } -} - - -// Validator metrics should be viewed in the grafana dashboards and other relevant beacon node information through beacon APIs. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -service Beacon { - rpc GetBeaconStatus(google.protobuf.Empty) returns (BeaconStatusResponse) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/status" - }; - } - rpc GetValidatorParticipation( - ethereum.eth.v1alpha1.GetValidatorParticipationRequest - ) returns (ethereum.eth.v1alpha1.ValidatorParticipationResponse) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/participation" - }; - } - rpc GetValidatorPerformance( - ethereum.eth.v1alpha1.ValidatorPerformanceRequest - ) returns (ethereum.eth.v1alpha1.ValidatorPerformanceResponse) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/summary" - }; - } - rpc GetValidators( - ethereum.eth.v1alpha1.ListValidatorsRequest - ) returns (ethereum.eth.v1alpha1.Validators) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/validators" - }; - } - rpc GetValidatorBalances( - ethereum.eth.v1alpha1.ListValidatorBalancesRequest - ) returns (ethereum.eth.v1alpha1.ValidatorBalances) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/balances" - }; - } - rpc GetValidatorQueue(google.protobuf.Empty) returns (ethereum.eth.v1alpha1.ValidatorQueue) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/queue" - }; - } - rpc GetPeers(google.protobuf.Empty) returns (ethereum.eth.v1alpha1.Peers) { - option deprecated = true; - option (google.api.http) = { - get: "/v2/validator/beacon/peers" - }; - } -} - // Web APIs such as the Keymanager APIs will no longer validate JWTs on the endpoint. Users should no longer expose the validator APIs to the public. // option deprecated = true; can't be added yet as it's used for keymanager API // DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. @@ -106,77 +21,6 @@ service Auth { } } -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message ListAccountsRequest { - option deprecated = true; - // Whether or not to return the raw RLP deposit tx data. - bool get_deposit_tx_data = 1; - - // The maximum number of accounts to return in the response. - // This field is optional. - int32 page_size = 2; - - // A pagination token returned from a previous call to `ListAccounts` - // that indicates where this listing should continue from. - // This field is optional. - string page_token = 3; - - // Whether to return all available accounts in a single response. - bool all = 4; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message ListAccountsResponse { - option deprecated = true; - repeated Account accounts = 1; - - // A pagination token returned from a previous call to `ListAccounts` - // that indicates from where listing should continue. - // This field is optional. - string next_page_token = 2; - - // Total count matching the request. - int32 total_size = 3; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message Account { - option deprecated = true; - // The validating public key. - bytes validating_public_key = 1; - // The human readable account name. - string account_name = 2; - // The deposit data transaction RLP bytes. - bytes deposit_tx_data = 3; - // The derivation path (if using HD wallet). - string derivation_path = 4; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message AccountRequest { - option deprecated = true; - // A list of validator public keys. - repeated bytes public_keys = 1; - // A list of validator indices. - repeated uint64 indices = 2; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message ImportAccountsRequest { - option deprecated = true; - // JSON-encoded keystore files to import during wallet creation. - repeated string keystores_imported = 1; - - // Password to unlock imported keystore files. - string keystores_password = 2; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message ImportAccountsResponse { - option deprecated = true; - repeated bytes imported_public_keys = 1; -} - // DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. message InitializeAuthRequest { option deprecated = true; @@ -189,65 +33,3 @@ message InitializeAuthResponse { bool has_signed_up = 1; bool has_wallet = 2; } - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message BeaconStatusResponse { - option deprecated = true; - // The host address of the beacon node the validator - // client is connected to. - string beacon_node_endpoint = 1; - // Whether the connection is active. - bool connected = 2; - // Whether the beacon node is currently synchronizing to chain head. - bool syncing = 3; - // The chain genesis time. - uint64 genesis_time = 4; - // Address of the validator deposit contract in the eth1 chain. - bytes deposit_contract_address = 5; - // The head of the chain from the beacon node. - ethereum.eth.v1alpha1.ChainHead chain_head = 6; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message VoluntaryExitRequest { - option deprecated = true; - // List of public keys to voluntarily exit. - repeated bytes public_keys = 1; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message VoluntaryExitResponse { - option deprecated = true; - // List of keys that successfully exited. - repeated bytes exited_keys = 1; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message BackupAccountsRequest { - option deprecated = true; - // List of public keys to backup. - repeated bytes public_keys = 1; - - string backup_password = 2; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message BackupAccountsResponse { - option deprecated = true; - // Zip file containing backed up keystores. - bytes zip_file = 1; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message DeleteAccountsRequest { - option deprecated = true; - // List of public keys to delete. - repeated bytes public_keys_to_delete = 1; -} - -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -message DeleteAccountsResponse { - option deprecated = true; - // List of public keys successfully deleted. - repeated bytes deleted_keys = 1; -} \ No newline at end of file diff --git a/testing/endtoend/evaluators/api_gateway_v1alpha1.go b/testing/endtoend/evaluators/api_gateway_v1alpha1.go index 61a93d7a13ec..9ced04121fd7 100644 --- a/testing/endtoend/evaluators/api_gateway_v1alpha1.go +++ b/testing/endtoend/evaluators/api_gateway_v1alpha1.go @@ -9,6 +9,7 @@ import ( "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" e2e "github.com/prysmaticlabs/prysm/v4/testing/endtoend/params" "github.com/prysmaticlabs/prysm/v4/testing/endtoend/policies" @@ -363,24 +364,13 @@ func withCompareValidators(beaconNodeIdx int, conn *grpc.ClientConn) error { // Compares a regular beacon chain head GET request with no arguments gRPC and gRPC gateway. func withCompareChainHead(beaconNodeIdx int, conn *grpc.ClientConn) error { - type chainHeadResponseJSON struct { - HeadSlot string `json:"headSlot"` - HeadEpoch string `json:"headEpoch"` - HeadBlockRoot string `json:"headBlockRoot"` - FinalizedSlot string `json:"finalizedSlot"` - FinalizedEpoch string `json:"finalizedEpoch"` - FinalizedBlockRoot string `json:"finalizedBlockRoot"` - JustifiedSlot string `json:"justifiedSlot"` - JustifiedEpoch string `json:"justifiedEpoch"` - JustifiedBlockRoot string `json:"justifiedBlockRoot"` - } beaconClient := ethpb.NewBeaconChainClient(conn) ctx := context.Background() resp, err := beaconClient.GetChainHead(ctx, &empty.Empty{}) if err != nil { return err } - respJSON := &chainHeadResponseJSON{} + respJSON := &shared.ChainHead{} if err := doGatewayJSONRequest( "/beacon/chainhead", beaconNodeIdx, @@ -432,20 +422,41 @@ func withCompareChainHead(beaconNodeIdx int, conn *grpc.ClientConn) error { ) } if respJSON.JustifiedSlot != fmt.Sprintf("%d", resp.JustifiedSlot) { + return fmt.Errorf( + "HTTP gateway justified slot %s does not match gRPC %d", + respJSON.JustifiedSlot, + resp.JustifiedSlot, + ) + } + if respJSON.JustifiedEpoch != fmt.Sprintf("%d", resp.JustifiedEpoch) { + return fmt.Errorf( + "HTTP gateway justified epoch %s does not match gRPC %d", + respJSON.JustifiedEpoch, + resp.JustifiedEpoch, + ) + } + if respJSON.JustifiedBlockRoot != base64.StdEncoding.EncodeToString(resp.JustifiedBlockRoot) { + return fmt.Errorf( + "HTTP gateway justified block root %s does not match gRPC %s", + respJSON.JustifiedBlockRoot, + resp.JustifiedBlockRoot, + ) + } + if respJSON.PreviousJustifiedSlot != fmt.Sprintf("%d", resp.PreviousJustifiedSlot) { return fmt.Errorf( "HTTP gateway justified slot %s does not match gRPC %d", respJSON.FinalizedSlot, resp.FinalizedSlot, ) } - if respJSON.JustifiedEpoch != fmt.Sprintf("%d", resp.JustifiedEpoch) { + if respJSON.PreviousJustifiedEpoch != fmt.Sprintf("%d", resp.PreviousJustifiedEpoch) { return fmt.Errorf( "HTTP gateway justified epoch %s does not match gRPC %d", respJSON.FinalizedEpoch, resp.FinalizedEpoch, ) } - if respJSON.JustifiedBlockRoot != base64.StdEncoding.EncodeToString(resp.JustifiedBlockRoot) { + if respJSON.PreviousJustifiedBlockRoot != base64.StdEncoding.EncodeToString(resp.PreviousJustifiedBlockRoot) { return fmt.Errorf( "HTTP gateway justified block root %s does not match gRPC %s", respJSON.JustifiedBlockRoot, diff --git a/validator/rpc/BUILD.bazel b/validator/rpc/BUILD.bazel index c0495264cfe9..c23b49fd63c6 100644 --- a/validator/rpc/BUILD.bazel +++ b/validator/rpc/BUILD.bazel @@ -3,9 +3,10 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "accounts.go", "auth_token.go", "beacon.go", + "handlers_accounts.go", + "handlers_beacon.go", "handler_wallet.go", "handlers_health.go", "handlers_keymanager.go", @@ -20,6 +21,7 @@ go_library( "//visibility:public", ], deps = [ + "//api:go_default_library", "//api/grpc:go_default_library", "//api/pagination:go_default_library", "//async/event:go_default_library", @@ -81,6 +83,7 @@ go_library( "@org_golang_google_grpc//metadata:go_default_library", "@org_golang_google_grpc//reflection:go_default_library", "@org_golang_google_grpc//status:go_default_library", + "@org_golang_google_protobuf//runtime/protoimpl:go_default_library", "@org_golang_google_protobuf//types/known/emptypb:go_default_library", ], ) @@ -88,9 +91,10 @@ go_library( go_test( name = "go_default_test", srcs = [ - "accounts_test.go", "auth_token_test.go", "beacon_test.go", + "handlers_accounts_test.go", + "handlers_beacon_test.go", "handler_wallet_test.go", "handlers_health_test.go", "handlers_keymanager_test.go", diff --git a/validator/rpc/beacon.go b/validator/rpc/beacon.go index 3c41b7048b93..d79bb44e0e55 100644 --- a/validator/rpc/beacon.go +++ b/validator/rpc/beacon.go @@ -1,10 +1,6 @@ package rpc import ( - "context" - "time" - - "github.com/golang/protobuf/ptypes/empty" middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcretry "github.com/grpc-ecosystem/go-grpc-middleware/retry" grpcopentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" @@ -12,14 +8,12 @@ import ( "github.com/pkg/errors" grpcutil "github.com/prysmaticlabs/prysm/v4/api/grpc" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" - validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" "github.com/prysmaticlabs/prysm/v4/validator/client" beaconChainClientFactory "github.com/prysmaticlabs/prysm/v4/validator/client/beacon-chain-client-factory" nodeClientFactory "github.com/prysmaticlabs/prysm/v4/validator/client/node-client-factory" validatorClientFactory "github.com/prysmaticlabs/prysm/v4/validator/client/validator-client-factory" validatorHelpers "github.com/prysmaticlabs/prysm/v4/validator/helpers" "google.golang.org/grpc" - "google.golang.org/protobuf/types/known/emptypb" ) // Initialize a client connect to a beacon node gRPC endpoint. @@ -62,85 +56,3 @@ func (s *Server) registerBeaconClient() error { s.beaconNodeValidatorClient = validatorClientFactory.NewValidatorClient(conn) return nil } - -// GetBeaconStatus retrieves information about the beacon node gRPC connection -// and certain chain metadata, such as the genesis time, the chain head, and the -// deposit contract address. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetBeaconStatus(ctx context.Context, _ *empty.Empty) (*validatorpb.BeaconStatusResponse, error) { - syncStatus, err := s.beaconNodeClient.GetSyncStatus(ctx, &emptypb.Empty{}) - if err != nil { - //nolint:nilerr - return &validatorpb.BeaconStatusResponse{ - BeaconNodeEndpoint: s.nodeGatewayEndpoint, - Connected: false, - Syncing: false, - }, nil - } - genesis, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{}) - if err != nil { - return nil, err - } - genesisTime := uint64(time.Unix(genesis.GenesisTime.Seconds, 0).Unix()) - address := genesis.DepositContractAddress - chainHead, err := s.beaconChainClient.GetChainHead(ctx, &emptypb.Empty{}) - if err != nil { - return nil, err - } - return &validatorpb.BeaconStatusResponse{ - BeaconNodeEndpoint: s.beaconClientEndpoint, - Connected: true, - Syncing: syncStatus.Syncing, - GenesisTime: genesisTime, - DepositContractAddress: address, - ChainHead: chainHead, - }, nil -} - -// GetValidatorParticipation is a wrapper around the /eth/v1alpha1 endpoint of the same name. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetValidatorParticipation( - ctx context.Context, req *ethpb.GetValidatorParticipationRequest, -) (*ethpb.ValidatorParticipationResponse, error) { - return s.beaconChainClient.GetValidatorParticipation(ctx, req) -} - -// GetValidatorPerformance is a wrapper around the /eth/v1alpha1 endpoint of the same name. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetValidatorPerformance( - ctx context.Context, req *ethpb.ValidatorPerformanceRequest, -) (*ethpb.ValidatorPerformanceResponse, error) { - return s.beaconChainClient.GetValidatorPerformance(ctx, req) -} - -// GetValidatorBalances is a wrapper around the /eth/v1alpha1 endpoint of the same name. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetValidatorBalances( - ctx context.Context, req *ethpb.ListValidatorBalancesRequest, -) (*ethpb.ValidatorBalances, error) { - return s.beaconChainClient.ListValidatorBalances(ctx, req) -} - -// GetValidators is a wrapper around the /eth/v1alpha1 endpoint of the same name. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetValidators( - ctx context.Context, req *ethpb.ListValidatorsRequest, -) (*ethpb.Validators, error) { - return s.beaconChainClient.ListValidators(ctx, req) -} - -// GetValidatorQueue is a wrapper around the /eth/v1alpha1 endpoint of the same name. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetValidatorQueue( - ctx context.Context, _ *empty.Empty, -) (*ethpb.ValidatorQueue, error) { - return s.beaconChainClient.GetValidatorQueue(ctx, &emptypb.Empty{}) -} - -// GetPeers is a wrapper around the /eth/v1alpha1 endpoint of the same name. -// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. -func (s *Server) GetPeers( - ctx context.Context, _ *empty.Empty, -) (*ethpb.Peers, error) { - return s.beaconNodeClient.ListPeers(ctx, &emptypb.Empty{}) -} diff --git a/validator/rpc/beacon_test.go b/validator/rpc/beacon_test.go index b7aab331b710..56c64f683ed0 100644 --- a/validator/rpc/beacon_test.go +++ b/validator/rpc/beacon_test.go @@ -3,83 +3,12 @@ package rpc import ( "context" "testing" - "time" - "github.com/golang/mock/gomock" - "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" - ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" - pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" "github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/require" - validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock" "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/types/known/timestamppb" ) -func TestGetBeaconStatus_NotConnected(t *testing.T) { - ctrl := gomock.NewController(t) - nodeClient := validatormock.NewMockNodeClient(ctrl) - nodeClient.EXPECT().GetSyncStatus( - gomock.Any(), // ctx - gomock.Any(), - ).Return(nil /*response*/, errors.New("uh oh")) - srv := &Server{ - beaconNodeClient: nodeClient, - } - ctx := context.Background() - resp, err := srv.GetBeaconStatus(ctx, &empty.Empty{}) - require.NoError(t, err) - want := &pb.BeaconStatusResponse{ - BeaconNodeEndpoint: "", - Connected: false, - Syncing: false, - } - assert.DeepEqual(t, want, resp) -} - -func TestGetBeaconStatus_OK(t *testing.T) { - ctrl := gomock.NewController(t) - nodeClient := validatormock.NewMockNodeClient(ctrl) - beaconChainClient := validatormock.NewMockBeaconChainClient(ctrl) - nodeClient.EXPECT().GetSyncStatus( - gomock.Any(), // ctx - gomock.Any(), - ).Return(ðpb.SyncStatus{Syncing: true}, nil) - timeStamp := timestamppb.New(time.Unix(0, 0)) - nodeClient.EXPECT().GetGenesis( - gomock.Any(), // ctx - gomock.Any(), - ).Return(ðpb.Genesis{ - GenesisTime: timeStamp, - DepositContractAddress: []byte("hello"), - }, nil) - beaconChainClient.EXPECT().GetChainHead( - gomock.Any(), // ctx - gomock.Any(), - ).Return(ðpb.ChainHead{ - HeadEpoch: 1, - }, nil) - srv := &Server{ - beaconNodeClient: nodeClient, - beaconChainClient: beaconChainClient, - } - ctx := context.Background() - resp, err := srv.GetBeaconStatus(ctx, &empty.Empty{}) - require.NoError(t, err) - want := &pb.BeaconStatusResponse{ - BeaconNodeEndpoint: "", - Connected: true, - Syncing: true, - GenesisTime: uint64(time.Unix(0, 0).Unix()), - DepositContractAddress: []byte("hello"), - ChainHead: ðpb.ChainHead{ - HeadEpoch: 1, - }, - } - assert.DeepEqual(t, want, resp) -} - func TestGrpcHeaders(t *testing.T) { s := &Server{ ctx: context.Background(), diff --git a/validator/rpc/accounts.go b/validator/rpc/handlers_accounts.go similarity index 100% rename from validator/rpc/accounts.go rename to validator/rpc/handlers_accounts.go diff --git a/validator/rpc/accounts_test.go b/validator/rpc/handlers_accounts_test.go similarity index 100% rename from validator/rpc/accounts_test.go rename to validator/rpc/handlers_accounts_test.go diff --git a/validator/rpc/handlers_beacon.go b/validator/rpc/handlers_beacon.go new file mode 100644 index 000000000000..7887124439f0 --- /dev/null +++ b/validator/rpc/handlers_beacon.go @@ -0,0 +1,214 @@ +package rpc + +import ( + "encoding/base64" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" + "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" + http2 "github.com/prysmaticlabs/prysm/v4/network/http" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + "go.opencensus.io/trace" + "google.golang.org/protobuf/types/known/emptypb" +) + +// GetBeaconStatus retrieves information about the beacon node gRPC connection +// and certain chain metadata, such as the genesis time, the chain head, and the +// deposit contract address. +func (s *Server) GetBeaconStatus(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.web.beacon.GetBeaconStatus") + defer span.End() + syncStatus, err := s.beaconNodeClient.GetSyncStatus(ctx, &emptypb.Empty{}) + if err != nil { + log.WithError(err).Error("beacon node call to get sync status failed") + http2.WriteJson(w, &BeaconStatusResponse{ + BeaconNodeEndpoint: s.nodeGatewayEndpoint, + Connected: false, + Syncing: false, + }) + return + } + genesis, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{}) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "GetGenesis call failed").Error(), http.StatusInternalServerError) + return + } + genesisTime := uint64(time.Unix(genesis.GenesisTime.Seconds, 0).Unix()) + address := genesis.DepositContractAddress + chainHead, err := s.beaconChainClient.GetChainHead(ctx, &emptypb.Empty{}) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "GetChainHead").Error(), http.StatusInternalServerError) + return + } + chJson, err := shared.ChainHeadResponseFromConsensus(chainHead) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to convert to json").Error(), http.StatusInternalServerError) + return + } + http2.WriteJson(w, &BeaconStatusResponse{ + BeaconNodeEndpoint: s.beaconClientEndpoint, + Connected: true, + Syncing: syncStatus.Syncing, + GenesisTime: fmt.Sprintf("%d", genesisTime), + DepositContractAddress: hexutil.Encode(address), + ChainHead: chJson, + }) +} + +// GetValidatorPerformance is a wrapper around the /eth/v1alpha1 endpoint of the same name. +func (s *Server) GetValidatorPerformance(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.web.beacon.GetValidatorPerformance") + defer span.End() + publicKeys := r.URL.Query()["public_keys"] + pubkeys := make([][]byte, len(publicKeys)) + for i, key := range publicKeys { + var pk []byte + if strings.HasPrefix(key, "0x") { + k, ok := shared.ValidateHex(w, fmt.Sprintf("PublicKeys[%d]", i), key, fieldparams.BLSPubkeyLength) + if !ok { + return + } + pk = bytesutil.SafeCopyBytes(k) + } else { + data, err := base64.StdEncoding.DecodeString(key) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to decode base64").Error(), http.StatusBadRequest) + return + } + pk = bytesutil.SafeCopyBytes(data) + } + pubkeys[i] = pk + } + + req := ðpb.ValidatorPerformanceRequest{ + PublicKeys: pubkeys, + } + validatorPerformance, err := s.beaconChainClient.GetValidatorPerformance(ctx, req) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "GetValidatorPerformance call failed").Error(), http.StatusInternalServerError) + return + } + response, err := shared.ValidatorPerformanceResponseFromConsensus(validatorPerformance) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to convert to json").Error(), http.StatusInternalServerError) + return + } + http2.WriteJson(w, response) +} + +// GetValidatorBalances is a wrapper around the /eth/v1alpha1 endpoint of the same name. +func (s *Server) GetValidatorBalances(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.web.beacon.GetValidatorBalances") + defer span.End() + pageSize := r.URL.Query().Get("page_size") + ps, err := strconv.ParseInt(pageSize, 10, 32) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to parse page_size").Error(), http.StatusBadRequest) + return + } + pageToken := r.URL.Query().Get("page_token") + publicKeys := r.URL.Query()["public_keys"] + pubkeys := make([][]byte, len(publicKeys)) + for i, key := range publicKeys { + var pk []byte + if strings.HasPrefix(key, "0x") { + k, ok := shared.ValidateHex(w, fmt.Sprintf("PublicKeys[%d]", i), key, fieldparams.BLSPubkeyLength) + if !ok { + return + } + pk = bytesutil.SafeCopyBytes(k) + } else { + data, err := base64.StdEncoding.DecodeString(key) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to decode base64").Error(), http.StatusBadRequest) + return + } + pk = bytesutil.SafeCopyBytes(data) + } + pubkeys[i] = pk + } + req := ðpb.ListValidatorBalancesRequest{ + PublicKeys: pubkeys, + PageSize: int32(ps), + PageToken: pageToken, + } + listValidatorBalances, err := s.beaconChainClient.ListValidatorBalances(ctx, req) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "ListValidatorBalances call failed").Error(), http.StatusInternalServerError) + return + } + response, err := shared.ValidatorBalancesResponseFromConsensus(listValidatorBalances) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to convert to json").Error(), http.StatusInternalServerError) + return + } + http2.WriteJson(w, response) +} + +// GetValidators is a wrapper around the /eth/v1alpha1 endpoint of the same name. +func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.web.beacon.GetValidators") + defer span.End() + pageSize := r.URL.Query().Get("page_size") + ps, err := strconv.ParseInt(pageSize, 10, 32) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to parse page_size").Error(), http.StatusBadRequest) + return + } + pageToken := r.URL.Query().Get("page_token") + publicKeys := r.URL.Query()["public_keys"] + pubkeys := make([][]byte, len(publicKeys)) + for i, key := range publicKeys { + var pk []byte + if strings.HasPrefix(key, "0x") { + k, ok := shared.ValidateHex(w, fmt.Sprintf("PublicKeys[%d]", i), key, fieldparams.BLSPubkeyLength) + if !ok { + return + } + pk = bytesutil.SafeCopyBytes(k) + } else { + data, err := base64.StdEncoding.DecodeString(key) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to decode base64").Error(), http.StatusBadRequest) + return + } + pk = bytesutil.SafeCopyBytes(data) + } + pubkeys[i] = pk + } + req := ðpb.ListValidatorsRequest{ + PublicKeys: pubkeys, + PageSize: int32(ps), + PageToken: pageToken, + } + validators, err := s.beaconChainClient.ListValidators(ctx, req) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "ListValidators call failed").Error(), http.StatusInternalServerError) + return + } + response, err := shared.ValidatorsResponseFromConsensus(validators) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "Failed to convert to json").Error(), http.StatusInternalServerError) + return + } + http2.WriteJson(w, response) +} + +// GetPeers is a wrapper around the /eth/v1alpha1 endpoint of the same name. +func (s *Server) GetPeers(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.web.beacon.GetPeers") + defer span.End() + peers, err := s.beaconNodeClient.ListPeers(ctx, &emptypb.Empty{}) + if err != nil { + http2.HandleError(w, errors.Wrap(err, "ListPeers call failed").Error(), http.StatusInternalServerError) + return + } + http2.WriteJson(w, peers) +} diff --git a/validator/rpc/handlers_beacon_test.go b/validator/rpc/handlers_beacon_test.go new file mode 100644 index 000000000000..e4c1059b1641 --- /dev/null +++ b/validator/rpc/handlers_beacon_test.go @@ -0,0 +1,80 @@ +package rpc + +import ( + "context" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/golang/protobuf/ptypes/empty" + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" + "github.com/prysmaticlabs/prysm/v4/testing/assert" + "github.com/prysmaticlabs/prysm/v4/testing/require" + validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestGetBeaconStatus_NotConnected(t *testing.T) { + ctrl := gomock.NewController(t) + nodeClient := validatormock.NewMockNodeClient(ctrl) + nodeClient.EXPECT().GetSyncStatus( + gomock.Any(), // ctx + gomock.Any(), + ).Return(nil /*response*/, errors.New("uh oh")) + srv := &Server{ + beaconNodeClient: nodeClient, + } + ctx := context.Background() + resp, err := srv.GetBeaconStatus(ctx, &empty.Empty{}) + require.NoError(t, err) + want := &pb.BeaconStatusResponse{ + BeaconNodeEndpoint: "", + Connected: false, + Syncing: false, + } + assert.DeepEqual(t, want, resp) +} + +func TestGetBeaconStatus_OK(t *testing.T) { + ctrl := gomock.NewController(t) + nodeClient := validatormock.NewMockNodeClient(ctrl) + beaconChainClient := validatormock.NewMockBeaconChainClient(ctrl) + nodeClient.EXPECT().GetSyncStatus( + gomock.Any(), // ctx + gomock.Any(), + ).Return(ðpb.SyncStatus{Syncing: true}, nil) + timeStamp := timestamppb.New(time.Unix(0, 0)) + nodeClient.EXPECT().GetGenesis( + gomock.Any(), // ctx + gomock.Any(), + ).Return(ðpb.Genesis{ + GenesisTime: timeStamp, + DepositContractAddress: []byte("hello"), + }, nil) + beaconChainClient.EXPECT().GetChainHead( + gomock.Any(), // ctx + gomock.Any(), + ).Return(ðpb.ChainHead{ + HeadEpoch: 1, + }, nil) + srv := &Server{ + beaconNodeClient: nodeClient, + beaconChainClient: beaconChainClient, + } + ctx := context.Background() + resp, err := srv.GetBeaconStatus(ctx, &empty.Empty{}) + require.NoError(t, err) + want := &pb.BeaconStatusResponse{ + BeaconNodeEndpoint: "", + Connected: true, + Syncing: true, + GenesisTime: uint64(time.Unix(0, 0).Unix()), + DepositContractAddress: []byte("hello"), + ChainHead: ðpb.ChainHead{ + HeadEpoch: 1, + }, + } + assert.DeepEqual(t, want, resp) +} diff --git a/validator/rpc/intercepter.go b/validator/rpc/intercepter.go index 47e30bc0a36a..c266e7e04912 100644 --- a/validator/rpc/intercepter.go +++ b/validator/rpc/intercepter.go @@ -3,9 +3,11 @@ package rpc import ( "context" "fmt" + "net/http" "strings" "github.com/golang-jwt/jwt/v4" + "github.com/prysmaticlabs/prysm/v4/api" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -33,6 +35,27 @@ func (s *Server) JWTInterceptor() grpc.UnaryServerInterceptor { } } +// JwtHttpInterceptor is an HTTP handler to authorize a route. +func (s *Server) JwtHttpInterceptor(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // if it's not initialize or has a web prefix + if r.URL.Path != api.WebUrlPrefix+"initialize" && strings.HasPrefix(r.URL.Path, api.WebUrlPrefix) { + reqToken := r.Header.Get("Authorization") + if reqToken != "" { + http.Error(w, "unauthorized: no Authorization header passed.", http.StatusUnauthorized) + return + } + token := strings.Split(reqToken, "Bearer ")[1] + _, err := jwt.Parse(token, s.validateJWT) + if err != nil { + http.Error(w, fmt.Errorf("unauthorized:could not parse JWT token: %v", err).Error(), http.StatusUnauthorized) + return + } + } + next.ServeHTTP(w, r) + }) +} + // Authorize the token received is valid. func (s *Server) authorize(ctx context.Context) error { md, ok := metadata.FromIncomingContext(ctx) diff --git a/validator/rpc/server.go b/validator/rpc/server.go index fad759bba4fc..2e144f95d979 100644 --- a/validator/rpc/server.go +++ b/validator/rpc/server.go @@ -14,6 +14,7 @@ import ( grpcopentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/api" "github.com/prysmaticlabs/prysm/v4/async/event" "github.com/prysmaticlabs/prysm/v4/io/logs" "github.com/prysmaticlabs/prysm/v4/monitoring/tracing" @@ -184,7 +185,6 @@ func (s *Server) Start() { // Register services available for the gRPC server. reflection.Register(s.grpcServer) validatorpb.RegisterAuthServer(s.grpcServer, s) - validatorpb.RegisterBeaconServer(s.grpcServer, s) validatorpb.RegisterAccountsServer(s.grpcServer, s) // routes needs to be set before the server calls the server function @@ -216,6 +216,8 @@ func (s *Server) InitializeRoutes() error { if s.router == nil { return errors.New("no router found on server") } + // Adding Auth Interceptor for the routes below + s.router.Use(s.JwtHttpInterceptor) // Register all services, HandleFunc calls, etc. // ... s.router.HandleFunc("/eth/v1/keystores", s.ListKeystores).Methods(http.MethodGet) @@ -232,17 +234,23 @@ func (s *Server) InitializeRoutes() error { s.router.HandleFunc("/eth/v1/validator/{pubkey}/feerecipient", s.DeleteFeeRecipientByPubkey).Methods(http.MethodDelete) s.router.HandleFunc("/eth/v1/validator/{pubkey}/voluntary_exit", s.SetVoluntaryExit).Methods(http.MethodPost) // web health endpoints - s.router.HandleFunc("/v2/validator/health/version", s.GetVersion).Methods(http.MethodGet) - s.router.HandleFunc("/v2/validator/health/logs/validator/stream", s.StreamValidatorLogs).Methods(http.MethodGet) - s.router.HandleFunc("/v2/validator/health/logs/beacon/stream", s.StreamBeaconLogs).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"health/version", s.GetVersion).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"health/logs/validator/stream", s.StreamValidatorLogs).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"health/logs/beacon/stream", s.StreamBeaconLogs).Methods(http.MethodGet) + // Beacon calls + s.router.HandleFunc(api.WebUrlPrefix+"beacon/status", s.GetBeaconStatus).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"beacon/summary", s.GetValidatorPerformance).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"beacon/validators", s.GetValidators).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"beacon/balances", s.GetValidatorBalances).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"beacon/peers", s.GetPeers).Methods(http.MethodGet) // web wallet endpoints - s.router.HandleFunc("/v2/validator/wallet", s.WalletConfig).Methods(http.MethodGet) - s.router.HandleFunc("/v2/validator/wallet/create", s.CreateWallet).Methods(http.MethodPost) - s.router.HandleFunc("/v2/validator/wallet/keystores/validate", s.ValidateKeystores).Methods(http.MethodPost) - s.router.HandleFunc("/v2/validator/wallet/recover", s.RecoverWallet).Methods(http.MethodPost) + s.router.HandleFunc(api.WebUrlPrefix+"wallet", s.WalletConfig).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"wallet/create", s.CreateWallet).Methods(http.MethodPost) + s.router.HandleFunc(api.WebUrlPrefix+"wallet/keystores/validate", s.ValidateKeystores).Methods(http.MethodPost) + s.router.HandleFunc(api.WebUrlPrefix+"wallet/recover", s.RecoverWallet).Methods(http.MethodPost) // slashing protection endpoints - s.router.HandleFunc("/v2/validator/slashing-protection/export", s.ExportSlashingProtection).Methods(http.MethodGet) - s.router.HandleFunc("/v2/validator/slashing-protection/import", s.ImportSlashingProtection).Methods(http.MethodPost) + s.router.HandleFunc(api.WebUrlPrefix+"slashing-protection/export", s.ExportSlashingProtection).Methods(http.MethodGet) + s.router.HandleFunc(api.WebUrlPrefix+"slashing-protection/import", s.ImportSlashingProtection).Methods(http.MethodPost) log.Info("Initialized REST API routes") return nil } diff --git a/validator/rpc/structs.go b/validator/rpc/structs.go index 4f0d2306657d..4de8073e5a51 100644 --- a/validator/rpc/structs.go +++ b/validator/rpc/structs.go @@ -3,6 +3,7 @@ package rpc import ( "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared" "github.com/prysmaticlabs/prysm/v4/validator/keymanager" + "google.golang.org/protobuf/runtime/protoimpl" ) // local keymanager api @@ -90,6 +91,15 @@ type SetFeeRecipientByPubkeyRequest struct { Ethaddress string `json:"ethaddress"` } +type BeaconStatusResponse struct { + BeaconNodeEndpoint string `json:"beacon_node_endpoint"` + Connected bool `json:"connected"` + Syncing bool `json:"syncing"` + GenesisTime string `json:"genesis_time"` + DepositContractAddress string `json:"deposit_contract_address"` + ChainHead *shared.ChainHead `json:"chain_head"` +} + // KeymanagerKind is a type of key manager for the wallet type KeymanagerKind string @@ -140,3 +150,47 @@ type ImportSlashingProtectionRequest struct { type ExportSlashingProtectionResponse struct { File string `json:"file"` } + +type ListAccountsRequest struct { + GetDepositTxData bool `protobuf:"varint,1,opt,name=get_deposit_tx_data,json=getDepositTxData,proto3" json:"get_deposit_tx_data,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + All bool `protobuf:"varint,4,opt,name=all,proto3" json:"all,omitempty"` +} + +// Deprecated: Marked as deprecated in proto/prysm/v1alpha1/validator-client/web_api.proto. +type BackupAccountsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PublicKeys [][]byte `protobuf:"bytes,1,rep,name=public_keys,json=publicKeys,proto3" json:"public_keys,omitempty"` + BackupPassword string `protobuf:"bytes,2,opt,name=backup_password,json=backupPassword,proto3" json:"backup_password,omitempty"` +} + +// Deprecated: Marked as deprecated in proto/prysm/v1alpha1/validator-client/web_api.proto. +type VoluntaryExitRequest struct { + PublicKeys [][]byte `protobuf:"bytes,1,rep,name=public_keys,json=publicKeys,proto3" json:"public_keys,omitempty"` +} + +// Deprecated: Marked as deprecated in proto/prysm/v1alpha1/validator-client/web_api.proto. +type BackupAccountsResponse struct { + ZipFile []byte `protobuf:"bytes,1,opt,name=zip_file,json=zipFile,proto3" json:"zip_file,omitempty"` +} + +type ListAccountsResponse struct { + Accounts []*Account `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + TotalSize int32 `protobuf:"varint,3,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` +} + +type Account struct { + ValidatingPublicKey []byte `protobuf:"bytes,1,opt,name=validating_public_key,json=validatingPublicKey,proto3" json:"validating_public_key,omitempty"` + AccountName string `protobuf:"bytes,2,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"` + DepositTxData []byte `protobuf:"bytes,3,opt,name=deposit_tx_data,json=depositTxData,proto3" json:"deposit_tx_data,omitempty"` + DerivationPath string `protobuf:"bytes,4,opt,name=derivation_path,json=derivationPath,proto3" json:"derivation_path,omitempty"` +} + +type VoluntaryExitResponse struct { + ExitedKeys [][]byte `protobuf:"bytes,1,rep,name=exited_keys,json=exitedKeys,proto3" json:"exited_keys,omitempty"` +}