mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
HTTP validator API: beacon and account endpoints (#13191)
* fixing squashing changes, migrates beacon , account, and auth endpoints on validator client * adding accounts endpoints * fixing tests and query endpoints * adding auth endpoint and fixing unit tests * removing unused files and updating node file to skip gRPC * ineffectual assignment fix * rolling back a change to fix e2e * fixing issues with ui * updating with webui version 2.0.5 * updating package name flag in readme * removing restore assets functions * adding nomemcopy flag to see if vulenerability scan passes * making data non compressed to avoid copy vulnerability * Update beacon-chain/rpc/eth/shared/structs_validator.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * updating site_data, and skipping static analysis on file * adding back deprecation comment notice * updating workflows to ignore generated * addressing radek comments * missed a conversion --------- Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
This commit is contained in:
@@ -794,10 +794,7 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
|
||||
maxCallSize := c.cliCtx.Uint64(cmd.GrpcMaxCallRecvMsgSizeFlag.Name)
|
||||
|
||||
registrations := []gateway.PbHandlerRegistration{
|
||||
validatorpb.RegisterAuthHandler,
|
||||
pb.RegisterHealthHandler,
|
||||
validatorpb.RegisterAccountsHandler,
|
||||
validatorpb.RegisterBeaconHandler,
|
||||
}
|
||||
gwmux := gwruntime.NewServeMux(
|
||||
gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{
|
||||
@@ -812,7 +809,7 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
|
||||
},
|
||||
}),
|
||||
gwruntime.WithMarshalerOption(
|
||||
"text/event-stream", &gwruntime.EventSourceJSONPb{},
|
||||
"text/event-stream", &gwruntime.EventSourceJSONPb{}, // TODO: remove this
|
||||
),
|
||||
gwruntime.WithForwardResponseOption(gateway.HttpResponseModifier),
|
||||
)
|
||||
@@ -825,19 +822,13 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
|
||||
h(w, req)
|
||||
} else {
|
||||
// Finally, we handle with the web server.
|
||||
// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork.
|
||||
web.Handler(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
// remove "/accounts/", "/v2/" after WebUI DEPRECATED
|
||||
pbHandler := &gateway.PbMux{
|
||||
Registrations: registrations,
|
||||
Patterns: []string{
|
||||
"/accounts/",
|
||||
"/v2/",
|
||||
},
|
||||
Mux: gwmux,
|
||||
Mux: gwmux,
|
||||
}
|
||||
opts := []gateway.Option{
|
||||
gateway.WithMuxHandler(muxHandler),
|
||||
|
||||
@@ -3,10 +3,12 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"accounts.go",
|
||||
"auth_token.go",
|
||||
"beacon.go",
|
||||
"handler_wallet.go",
|
||||
"handlers_accounts.go",
|
||||
"handlers_auth.go",
|
||||
"handlers_beacon.go",
|
||||
"handlers_health.go",
|
||||
"handlers_keymanager.go",
|
||||
"handlers_slashing.go",
|
||||
@@ -20,6 +22,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",
|
||||
@@ -40,7 +43,6 @@ go_library(
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//validator/accounts:go_default_library",
|
||||
"//validator/accounts/petnames:go_default_library",
|
||||
@@ -61,7 +63,6 @@ go_library(
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_fsnotify_fsnotify//:go_default_library",
|
||||
"@com_github_golang_jwt_jwt_v4//:go_default_library",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library",
|
||||
"@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library",
|
||||
@@ -88,10 +89,12 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"accounts_test.go",
|
||||
"auth_token_test.go",
|
||||
"beacon_test.go",
|
||||
"handler_wallet_test.go",
|
||||
"handlers_accounts_test.go",
|
||||
"handlers_auth_test.go",
|
||||
"handlers_beacon_test.go",
|
||||
"handlers_health_test.go",
|
||||
"handlers_keymanager_test.go",
|
||||
"handlers_slashing_test.go",
|
||||
@@ -100,7 +103,9 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api:go_default_library",
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//cmd/validator/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
@@ -114,7 +119,6 @@ go_test(
|
||||
"//io/file:go_default_library",
|
||||
"//io/logs/mock:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/validator-mock:go_default_library",
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/pagination"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/petnames"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/local"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ListAccounts allows retrieval of validating keys and their petnames
|
||||
// for a user's wallet via RPC.
|
||||
// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork.
|
||||
func (s *Server) ListAccounts(ctx context.Context, req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) {
|
||||
if s.validatorService == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Validator service not yet initialized")
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Wallet not yet initialized")
|
||||
}
|
||||
if int(req.PageSize) > cmd.Get().MaxRPCPageSize {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Requested page size %d can not be greater than max size %d",
|
||||
req.PageSize, cmd.Get().MaxRPCPageSize)
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accs := make([]*pb.Account, len(keys))
|
||||
for i := 0; i < len(keys); i++ {
|
||||
accs[i] = &pb.Account{
|
||||
ValidatingPublicKey: keys[i][:],
|
||||
AccountName: petnames.DeterministicName(keys[i][:], "-"),
|
||||
}
|
||||
if s.wallet.KeymanagerKind() == keymanager.Derived {
|
||||
accs[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
|
||||
}
|
||||
}
|
||||
if req.All {
|
||||
return &pb.ListAccountsResponse{
|
||||
Accounts: accs,
|
||||
TotalSize: int32(len(keys)),
|
||||
NextPageToken: "",
|
||||
}, nil
|
||||
}
|
||||
start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), len(keys))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Could not paginate results: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return &pb.ListAccountsResponse{
|
||||
Accounts: accs[start:end],
|
||||
TotalSize: int32(len(keys)),
|
||||
NextPageToken: nextPageToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BackupAccounts creates a zip file containing EIP-2335 keystores for the user's
|
||||
// specified public keys by encrypting them with the specified password.
|
||||
// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork.
|
||||
func (s *Server) BackupAccounts(
|
||||
ctx context.Context, req *pb.BackupAccountsRequest,
|
||||
) (*pb.BackupAccountsResponse, error) {
|
||||
if s.validatorService == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Validator service not yet initialized")
|
||||
}
|
||||
if req.PublicKeys == nil || len(req.PublicKeys) < 1 {
|
||||
return nil, status.Error(codes.InvalidArgument, "No public keys specified to backup")
|
||||
}
|
||||
if req.BackupPassword == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Backup password cannot be empty")
|
||||
}
|
||||
|
||||
if s.wallet == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
|
||||
}
|
||||
var err error
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKeys := make([]bls.PublicKey, len(req.PublicKeys))
|
||||
for i, key := range req.PublicKeys {
|
||||
pubKey, err := bls.PublicKeyFromBytes(key)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "%#x Not a valid BLS public key: %v", key, err)
|
||||
}
|
||||
pubKeys[i] = pubKey
|
||||
}
|
||||
|
||||
var keystoresToBackup []*keymanager.Keystore
|
||||
switch km := km.(type) {
|
||||
case *local.Keymanager:
|
||||
keystoresToBackup, err = km.ExtractKeystores(ctx, pubKeys, req.BackupPassword)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not backup accounts for local keymanager: %v", err)
|
||||
}
|
||||
case *derived.Keymanager:
|
||||
keystoresToBackup, err = km.ExtractKeystores(ctx, pubKeys, req.BackupPassword)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not backup accounts for derived keymanager: %v", err)
|
||||
}
|
||||
default:
|
||||
return nil, status.Error(codes.FailedPrecondition, "Only HD or IMPORTED wallets can backup accounts")
|
||||
}
|
||||
if len(keystoresToBackup) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "No keystores to backup")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
writer := zip.NewWriter(buf)
|
||||
for i, k := range keystoresToBackup {
|
||||
encodedFile, err := json.MarshalIndent(k, "", "\t")
|
||||
if err != nil {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "could not marshal keystore to JSON file: %v", err)
|
||||
}
|
||||
f, err := writer.Create(fmt.Sprintf("keystore-%d.json", i))
|
||||
if err != nil {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "Could not write keystore file to zip: %v", err)
|
||||
}
|
||||
if _, err = f.Write(encodedFile); err != nil {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "Could not write keystore file contents")
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
return &pb.BackupAccountsResponse{
|
||||
ZipFile: buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VoluntaryExit performs a voluntary exit for the validator keys specified in a request.
|
||||
// DEPRECATE: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. There is a similar endpoint that is still used /eth/v1alpha1/validator/exit.
|
||||
func (s *Server) VoluntaryExit(
|
||||
ctx context.Context, req *pb.VoluntaryExitRequest,
|
||||
) (*pb.VoluntaryExitResponse, error) {
|
||||
if s.validatorService == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Validator service not yet initialized")
|
||||
}
|
||||
if len(req.PublicKeys) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "No public keys specified to delete")
|
||||
}
|
||||
if s.wallet == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formattedKeys := make([]string, len(req.PublicKeys))
|
||||
for i, key := range req.PublicKeys {
|
||||
formattedKeys[i] = fmt.Sprintf("%#x", key)
|
||||
}
|
||||
cfg := accounts.PerformExitCfg{
|
||||
ValidatorClient: s.beaconNodeValidatorClient,
|
||||
NodeClient: s.beaconNodeClient,
|
||||
Keymanager: km,
|
||||
RawPubKeys: req.PublicKeys,
|
||||
FormattedPubKeys: formattedKeys,
|
||||
}
|
||||
rawExitedKeys, _, err := accounts.PerformVoluntaryExit(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not perform voluntary exit: %v", err)
|
||||
}
|
||||
return &pb.VoluntaryExitResponse{
|
||||
ExitedKeys: rawExitedKeys,
|
||||
}, nil
|
||||
}
|
||||
@@ -17,36 +17,15 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/file"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
const (
|
||||
authTokenFileName = "auth-token"
|
||||
AuthTokenFileName = "auth-token"
|
||||
)
|
||||
|
||||
// Initialize returns metadata regarding whether the caller has authenticated and has a wallet.
|
||||
// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork. Web APIs won't require JWT in the future.
|
||||
// Still used by Keymanager API until web ui is fully removed.
|
||||
func (s *Server) Initialize(_ context.Context, _ *emptypb.Empty) (*pb.InitializeAuthResponse, error) {
|
||||
walletExists, err := wallet.Exists(s.walletDir)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not check if wallet exists")
|
||||
}
|
||||
authTokenPath := filepath.Join(s.walletDir, authTokenFileName)
|
||||
return &pb.InitializeAuthResponse{
|
||||
HasSignedUp: file.Exists(authTokenPath),
|
||||
HasWallet: walletExists,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAuthToken generates a new jwt key, token and writes them
|
||||
// to a file in the specified directory. Also, it logs out a prepared URL
|
||||
// for the user to navigate to and authenticate with the Prysm web interface.
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func CreateAuthToken(walletDirPath, validatorWebAddr string) error {
|
||||
jwtKey, err := createRandomJWTSecret()
|
||||
if err != nil {
|
||||
@@ -56,7 +35,7 @@ func CreateAuthToken(walletDirPath, validatorWebAddr string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authTokenPath := filepath.Join(walletDirPath, authTokenFileName)
|
||||
authTokenPath := filepath.Join(walletDirPath, AuthTokenFileName)
|
||||
log.Infof("Generating auth token and saving it to %s", authTokenPath)
|
||||
if err := saveAuthToken(walletDirPath, jwtKey, token); err != nil {
|
||||
return err
|
||||
@@ -70,9 +49,8 @@ func CreateAuthToken(walletDirPath, validatorWebAddr string) error {
|
||||
// user via stdout and the validator client should then attempt to open the default
|
||||
// browser. The web interface authenticates by looking for this token in the query parameters
|
||||
// of the URL. This token is then used as the bearer token for jwt auth.
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func (s *Server) initializeAuthToken(walletDir string) (string, error) {
|
||||
authTokenFile := filepath.Join(walletDir, authTokenFileName)
|
||||
authTokenFile := filepath.Join(walletDir, AuthTokenFileName)
|
||||
if file.Exists(authTokenFile) {
|
||||
// #nosec G304
|
||||
f, err := os.Open(authTokenFile)
|
||||
@@ -106,7 +84,6 @@ func (s *Server) initializeAuthToken(walletDir string) (string, error) {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func (s *Server) refreshAuthTokenFromFileChanges(ctx context.Context, authTokenPath string) {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
@@ -142,7 +119,6 @@ func (s *Server) refreshAuthTokenFromFileChanges(ctx context.Context, authTokenP
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func logValidatorWebAuth(validatorWebAddr, token string, tokenPath string) {
|
||||
webAuthURLTemplate := "http://%s/initialize?token=%s"
|
||||
webAuthURL := fmt.Sprintf(
|
||||
@@ -158,9 +134,8 @@ func logValidatorWebAuth(validatorWebAddr, token string, tokenPath string) {
|
||||
log.Infof("Validator CLient JWT for RPC and REST authentication set at:%s", tokenPath)
|
||||
}
|
||||
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func saveAuthToken(walletDirPath string, jwtKey []byte, token string) error {
|
||||
hashFilePath := filepath.Join(walletDirPath, authTokenFileName)
|
||||
hashFilePath := filepath.Join(walletDirPath, AuthTokenFileName)
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
if _, err := bytesBuf.Write([]byte(fmt.Sprintf("%x", jwtKey))); err != nil {
|
||||
return err
|
||||
@@ -177,7 +152,6 @@ func saveAuthToken(walletDirPath string, jwtKey []byte, token string) error {
|
||||
return file.WriteFile(hashFilePath, bytesBuf.Bytes())
|
||||
}
|
||||
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func readAuthTokenFile(r io.Reader) (secret []byte, token string, err error) {
|
||||
br := bufio.NewReader(r)
|
||||
var jwtKeyHex string
|
||||
@@ -198,7 +172,6 @@ func readAuthTokenFile(r io.Reader) (secret []byte, token string, err error) {
|
||||
}
|
||||
|
||||
// Creates a JWT token string using the JWT key.
|
||||
// DEPRECATED: associated to Initialize Web UI API
|
||||
func createTokenString(jwtKey []byte) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{})
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestServer_RefreshJWTSecretOnFileChange(t *testing.T) {
|
||||
currentSecret := srv.jwtSecret
|
||||
require.Equal(t, true, len(currentSecret) > 0)
|
||||
|
||||
authTokenPath := filepath.Join(walletDir, authTokenFileName)
|
||||
authTokenPath := filepath.Join(walletDir, AuthTokenFileName)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -81,7 +81,7 @@ func TestServer_RefreshJWTSecretOnFileChange(t *testing.T) {
|
||||
newSecret := srv.jwtSecret
|
||||
require.Equal(t, true, len(newSecret) > 0)
|
||||
require.Equal(t, true, !bytes.Equal(currentSecret, newSecret))
|
||||
err = os.Remove(authTokenFileName)
|
||||
err = os.Remove(AuthTokenFileName)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
274
validator/rpc/handlers_accounts.go
Normal file
274
validator/rpc/handlers_accounts.go
Normal file
@@ -0,0 +1,274 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/pagination"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
httputil "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/petnames"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/local"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ListAccounts allows retrieval of validating keys and their petnames
|
||||
// for a user's wallet via RPC.
|
||||
func (s *Server) ListAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.web.accounts.ListAccounts")
|
||||
defer span.End()
|
||||
if s.validatorService == nil {
|
||||
httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
httputil.HandleError(w, "Prysm Wallet not initialized. Please create a new wallet.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
pageSize := r.URL.Query().Get("page_size")
|
||||
var ps int64
|
||||
if pageSize != "" {
|
||||
psi, err := strconv.ParseInt(pageSize, 10, 32)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Failed to parse page_size").Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ps = psi
|
||||
}
|
||||
pageToken := r.URL.Query().Get("page_token")
|
||||
publicKeys := r.URL.Query()["public_keys"]
|
||||
pubkeys := make([][]byte, len(publicKeys))
|
||||
for i, key := range publicKeys {
|
||||
k, ok := shared.ValidateHex(w, fmt.Sprintf("PublicKeys[%d]", i), key, fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pubkeys[i] = bytesutil.SafeCopyBytes(k)
|
||||
}
|
||||
if int(ps) > cmd.Get().MaxRPCPageSize {
|
||||
httputil.HandleError(w, fmt.Sprintf("Requested page size %d can not be greater than max size %d",
|
||||
ps, cmd.Get().MaxRPCPageSize), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
keys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Errorf("Could not retrieve public keys: %v", err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
accs := make([]*Account, len(keys))
|
||||
for i := 0; i < len(keys); i++ {
|
||||
accs[i] = &Account{
|
||||
ValidatingPublicKey: hexutil.Encode(keys[i][:]),
|
||||
AccountName: petnames.DeterministicName(keys[i][:], "-"),
|
||||
}
|
||||
if s.wallet.KeymanagerKind() == keymanager.Derived {
|
||||
accs[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
|
||||
}
|
||||
}
|
||||
if r.URL.Query().Get("all") == "true" {
|
||||
httputil.WriteJson(w, &ListAccountsResponse{
|
||||
Accounts: accs,
|
||||
TotalSize: int32(len(keys)),
|
||||
NextPageToken: "",
|
||||
})
|
||||
return
|
||||
}
|
||||
start, end, nextPageToken, err := pagination.StartAndEndPage(pageToken, int(ps), len(keys))
|
||||
if err != nil {
|
||||
httputil.HandleError(w, fmt.Errorf("Could not paginate results: %v",
|
||||
err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &ListAccountsResponse{
|
||||
Accounts: accs[start:end],
|
||||
TotalSize: int32(len(keys)),
|
||||
NextPageToken: nextPageToken,
|
||||
})
|
||||
}
|
||||
|
||||
// BackupAccounts creates a zip file containing EIP-2335 keystores for the user's
|
||||
// specified public keys by encrypting them with the specified password.
|
||||
func (s *Server) BackupAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.web.accounts.ListAccounts")
|
||||
defer span.End()
|
||||
if s.validatorService == nil {
|
||||
httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
httputil.HandleError(w, "Prysm Wallet not initialized. Please create a new wallet.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
var req BackupAccountsRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.PublicKeys == nil || len(req.PublicKeys) < 1 {
|
||||
httputil.HandleError(w, "No public keys specified to backup", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.BackupPassword == "" {
|
||||
httputil.HandleError(w, "Backup password cannot be empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
pubKeys := make([]bls.PublicKey, len(req.PublicKeys))
|
||||
for i, key := range req.PublicKeys {
|
||||
byteskey, ok := shared.ValidateHex(w, "pubkey", key, fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pubKey, err := bls.PublicKeyFromBytes(byteskey)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, fmt.Sprintf("%s Not a valid BLS public key", key)).Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
pubKeys[i] = pubKey
|
||||
}
|
||||
|
||||
var keystoresToBackup []*keymanager.Keystore
|
||||
switch km := km.(type) {
|
||||
case *local.Keymanager:
|
||||
keystoresToBackup, err = km.ExtractKeystores(ctx, pubKeys, req.BackupPassword)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Could not backup accounts for local keymanager").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
case *derived.Keymanager:
|
||||
keystoresToBackup, err = km.ExtractKeystores(ctx, pubKeys, req.BackupPassword)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Could not backup accounts for derived keymanager").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
httputil.HandleError(w, "Only HD or IMPORTED wallets can backup accounts", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(keystoresToBackup) == 0 {
|
||||
httputil.HandleError(w, "No keystores to backup", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
writer := zip.NewWriter(buf)
|
||||
for i, k := range keystoresToBackup {
|
||||
encodedFile, err := json.MarshalIndent(k, "", "\t")
|
||||
if err != nil {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
httputil.HandleError(w, "could not marshal keystore to JSON file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
f, err := writer.Create(fmt.Sprintf("keystore-%d.json", i))
|
||||
if err != nil {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
httputil.HandleError(w, "Could not write keystore file to zip", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err = f.Write(encodedFile); err != nil {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
httputil.HandleError(w, "Could not write keystore file contents", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close zip file after writing")
|
||||
}
|
||||
httputil.WriteJson(w, &BackupAccountsResponse{
|
||||
ZipFile: base64.StdEncoding.EncodeToString(buf.Bytes()), // convert to base64 string for processing
|
||||
})
|
||||
}
|
||||
|
||||
// VoluntaryExit performs a voluntary exit for the validator keys specified in a request.
|
||||
func (s *Server) VoluntaryExit(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.web.accounts.VoluntaryExit")
|
||||
defer span.End()
|
||||
if s.validatorService == nil {
|
||||
httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
httputil.HandleError(w, "Prysm Wallet not initialized. Please create a new wallet.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
var req VoluntaryExitRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(req.PublicKeys) == 0 {
|
||||
httputil.HandleError(w, "No public keys specified to delete", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
pubKeys := make([][]byte, len(req.PublicKeys))
|
||||
for i, key := range req.PublicKeys {
|
||||
byteskey, ok := shared.ValidateHex(w, "pubkey", key, fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pubKeys[i] = byteskey
|
||||
}
|
||||
cfg := accounts.PerformExitCfg{
|
||||
ValidatorClient: s.beaconNodeValidatorClient,
|
||||
NodeClient: s.beaconNodeClient,
|
||||
Keymanager: km,
|
||||
RawPubKeys: pubKeys,
|
||||
FormattedPubKeys: req.PublicKeys,
|
||||
}
|
||||
rawExitedKeys, _, err := accounts.PerformVoluntaryExit(ctx, cfg)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Could not perform voluntary exit").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &VoluntaryExitResponse{
|
||||
ExitedKeys: rawExitedKeys,
|
||||
})
|
||||
}
|
||||
@@ -4,17 +4,21 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/validator/flags"
|
||||
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"
|
||||
@@ -66,42 +70,74 @@ func TestServer_ListAccounts(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
resp, err := s.ListAccounts(ctx, &pb.ListAccountsRequest{
|
||||
PageSize: int32(numAccounts),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf(api.WebUrlPrefix+"accounts?page_size=%d", int32(numAccounts)), nil)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ListAccounts(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ListAccountsResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, len(resp.Accounts), numAccounts)
|
||||
|
||||
tests := []struct {
|
||||
req *pb.ListAccountsRequest
|
||||
res *pb.ListAccountsResponse
|
||||
PageSize int
|
||||
PageToken string
|
||||
All bool
|
||||
res *ListAccountsResponse
|
||||
}{
|
||||
{
|
||||
req: &pb.ListAccountsRequest{
|
||||
PageSize: 5,
|
||||
},
|
||||
res: &pb.ListAccountsResponse{
|
||||
|
||||
PageSize: 5,
|
||||
res: &ListAccountsResponse{
|
||||
Accounts: resp.Accounts[0:5],
|
||||
NextPageToken: "1",
|
||||
TotalSize: int32(numAccounts),
|
||||
},
|
||||
},
|
||||
{
|
||||
req: &pb.ListAccountsRequest{
|
||||
PageSize: 5,
|
||||
PageToken: "1",
|
||||
},
|
||||
res: &pb.ListAccountsResponse{
|
||||
|
||||
PageSize: 5,
|
||||
PageToken: "1",
|
||||
res: &ListAccountsResponse{
|
||||
Accounts: resp.Accounts[5:10],
|
||||
NextPageToken: "2",
|
||||
TotalSize: int32(numAccounts),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
All: true,
|
||||
res: &ListAccountsResponse{
|
||||
Accounts: resp.Accounts,
|
||||
NextPageToken: "",
|
||||
TotalSize: int32(numAccounts),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
res, err := s.ListAccounts(context.Background(), test.req)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, res, test.res)
|
||||
url := api.WebUrlPrefix + "accounts"
|
||||
if test.PageSize != 0 || test.PageToken != "" || test.All {
|
||||
url = url + "?"
|
||||
}
|
||||
if test.All {
|
||||
url = url + "all=true"
|
||||
} else {
|
||||
if test.PageSize != 0 {
|
||||
url = url + fmt.Sprintf("page_size=%d", test.PageSize)
|
||||
}
|
||||
if test.PageToken != "" {
|
||||
url = url + fmt.Sprintf("&page_token=%s", test.PageToken)
|
||||
}
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, url, nil)
|
||||
wr = httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ListAccounts(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp = &ListAccountsResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
assert.DeepEqual(t, resp, test.res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,32 +175,45 @@ func TestServer_BackupAccounts(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
resp, err := s.ListAccounts(ctx, &pb.ListAccountsRequest{
|
||||
PageSize: int32(numAccounts),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/v2/validator/accounts?page_size=%d", int32(numAccounts)), nil)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ListAccounts(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ListAccountsResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, len(resp.Accounts), numAccounts)
|
||||
|
||||
pubKeys := make([][]byte, numAccounts)
|
||||
pubKeys := make([]string, numAccounts)
|
||||
for i, aa := range resp.Accounts {
|
||||
pubKeys[i] = aa.ValidatingPublicKey
|
||||
}
|
||||
// We now attempt to backup all public keys from the wallet.
|
||||
res, err := s.BackupAccounts(context.Background(), &pb.BackupAccountsRequest{
|
||||
request := &BackupAccountsRequest{
|
||||
PublicKeys: pubKeys,
|
||||
BackupPassword: s.wallet.Password(),
|
||||
})
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
req = httptest.NewRequest(http.MethodPost, api.WebUrlPrefix+"accounts/backup", &buf)
|
||||
wr = httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
// We now attempt to backup all public keys from the wallet.
|
||||
s.BackupAccounts(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
res := &BackupAccountsResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), res))
|
||||
// decode the base64 string
|
||||
decodedBytes, err := base64.StdEncoding.DecodeString(res.ZipFile)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res.ZipFile)
|
||||
|
||||
// Open a zip archive for reading.
|
||||
buf := bytes.NewReader(res.ZipFile)
|
||||
r, err := zip.NewReader(buf, int64(len(res.ZipFile)))
|
||||
bu := bytes.NewReader(decodedBytes)
|
||||
r, err := zip.NewReader(bu, int64(len(decodedBytes)))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(pubKeys), len(r.File))
|
||||
|
||||
// Iterate through the files in the archive, checking they
|
||||
// match the keystores we wanted to backup.
|
||||
// match the keystores we wanted to back up.
|
||||
for i, f := range r.File {
|
||||
keystoreFile, err := f.Open()
|
||||
require.NoError(t, err)
|
||||
@@ -178,7 +227,7 @@ func TestServer_BackupAccounts(t *testing.T) {
|
||||
require.NoError(t, keystoreFile.Close())
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, keystore.Pubkey, fmt.Sprintf("%x", pubKeys[i]))
|
||||
assert.Equal(t, "0x"+keystore.Pubkey, pubKeys[i])
|
||||
require.NoError(t, keystoreFile.Close())
|
||||
}
|
||||
}
|
||||
@@ -255,13 +304,25 @@ func TestServer_VoluntaryExit(t *testing.T) {
|
||||
pubKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
rawPubKeys := make([][]byte, len(pubKeys))
|
||||
rawPubKeys := make([]string, len(pubKeys))
|
||||
for i, key := range pubKeys {
|
||||
rawPubKeys[i] = key[:]
|
||||
rawPubKeys[i] = hexutil.Encode(key[:])
|
||||
}
|
||||
res, err := s.VoluntaryExit(ctx, &pb.VoluntaryExitRequest{
|
||||
request := &VoluntaryExitRequest{
|
||||
PublicKeys: rawPubKeys,
|
||||
})
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, rawPubKeys, res.ExitedKeys)
|
||||
req := httptest.NewRequest(http.MethodPost, api.WebUrlPrefix+"accounts/voluntary-exit", &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.VoluntaryExit(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
res := &VoluntaryExitResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), res))
|
||||
for i := range res.ExitedKeys {
|
||||
require.Equal(t, rawPubKeys[i], hexutil.Encode(res.ExitedKeys[i]))
|
||||
}
|
||||
|
||||
}
|
||||
28
validator/rpc/handlers_auth.go
Normal file
28
validator/rpc/handlers_auth.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/file"
|
||||
httputil "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Initialize returns metadata regarding whether the caller has authenticated and has a wallet.
|
||||
func (s *Server) Initialize(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "validator.web.Initialize")
|
||||
defer span.End()
|
||||
walletExists, err := wallet.Exists(s.walletDir)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Could not check if wallet exists").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
authTokenPath := filepath.Join(s.walletDir, AuthTokenFileName)
|
||||
httputil.WriteJson(w, &InitializeAuthResponse{
|
||||
HasSignedUp: file.Exists(authTokenPath),
|
||||
HasWallet: walletExists,
|
||||
})
|
||||
}
|
||||
60
validator/rpc/handlers_auth_test.go
Normal file
60
validator/rpc/handlers_auth_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
)
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
// Step 1: Create a temporary directory
|
||||
localWalletDir := setupWalletDir(t)
|
||||
|
||||
// Step 2: Optionally create a temporary 'auth-token' file
|
||||
authTokenPath := filepath.Join(localWalletDir, AuthTokenFileName)
|
||||
_, err := os.Create(authTokenPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create an instance of the Server with the temporary directory
|
||||
opts := []accounts.Option{
|
||||
accounts.WithWalletDir(localWalletDir),
|
||||
accounts.WithKeymanagerType(keymanager.Derived),
|
||||
accounts.WithWalletPassword(strongPass),
|
||||
accounts.WithSkipMnemonicConfirm(true),
|
||||
}
|
||||
acc, err := accounts.NewCLIManager(opts...)
|
||||
require.NoError(t, err)
|
||||
_, err = acc.WalletCreate(context.Background())
|
||||
require.NoError(t, err)
|
||||
server := &Server{walletDir: localWalletDir}
|
||||
|
||||
// Step 4: Create an HTTP request and response recorder
|
||||
req := httptest.NewRequest(http.MethodGet, "/initialize", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Step 5: Call the Initialize function
|
||||
server.Initialize(w, req)
|
||||
|
||||
// Step 6: Assert expectations
|
||||
result := w.Result()
|
||||
defer func() {
|
||||
err := result.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
var response InitializeAuthResponse
|
||||
err = json.NewDecoder(result.Body).Decode(&response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert the expected response
|
||||
require.Equal(t, true, response.HasSignedUp)
|
||||
require.Equal(t, true, response.HasWallet)
|
||||
}
|
||||
208
validator/rpc/handlers_beacon.go
Normal file
208
validator/rpc/handlers_beacon.go
Normal file
@@ -0,0 +1,208 @@
|
||||
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"
|
||||
httputil "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")
|
||||
httputil.WriteJson(w, &BeaconStatusResponse{
|
||||
BeaconNodeEndpoint: s.nodeGatewayEndpoint,
|
||||
Connected: false,
|
||||
Syncing: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
genesis, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
httputil.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 {
|
||||
httputil.HandleError(w, errors.Wrap(err, "GetChainHead").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &BeaconStatusResponse{
|
||||
BeaconNodeEndpoint: s.beaconClientEndpoint,
|
||||
Connected: true,
|
||||
Syncing: syncStatus.Syncing,
|
||||
GenesisTime: fmt.Sprintf("%d", genesisTime),
|
||||
DepositContractAddress: hexutil.Encode(address),
|
||||
ChainHead: shared.ChainHeadResponseFromConsensus(chainHead),
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
httputil.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 {
|
||||
httputil.HandleError(w, errors.Wrap(err, "GetValidatorPerformance call failed").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, shared.ValidatorPerformanceResponseFromConsensus(validatorPerformance))
|
||||
}
|
||||
|
||||
// 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")
|
||||
var ps int64
|
||||
if pageSize != "" {
|
||||
psi, err := strconv.ParseInt(pageSize, 10, 32)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Failed to parse page_size").Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ps = psi
|
||||
}
|
||||
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 {
|
||||
httputil.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 {
|
||||
httputil.HandleError(w, errors.Wrap(err, "ListValidatorBalances call failed").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
response, err := shared.ValidatorBalancesResponseFromConsensus(listValidatorBalances)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Failed to convert to json").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.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 {
|
||||
httputil.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 {
|
||||
httputil.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 {
|
||||
httputil.HandleError(w, errors.Wrap(err, "ListValidators call failed").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
response, err := shared.ValidatorsResponseFromConsensus(validators)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errors.Wrap(err, "Failed to convert to json").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.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 {
|
||||
httputil.HandleError(w, errors.Wrap(err, "ListPeers call failed").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, peers)
|
||||
}
|
||||
105
validator/rpc/handlers_beacon_test.go
Normal file
105
validator/rpc/handlers_beacon_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"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,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/v2/validator/beacon/status"), nil)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
srv.GetBeaconStatus(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &BeaconStatusResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
want := &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,
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/v2/validator/beacon/status"), nil)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
srv.GetBeaconStatus(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &BeaconStatusResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
|
||||
want := &BeaconStatusResponse{
|
||||
BeaconNodeEndpoint: "",
|
||||
Connected: true,
|
||||
Syncing: true,
|
||||
GenesisTime: fmt.Sprintf("%d", time.Unix(0, 0).Unix()),
|
||||
DepositContractAddress: "0x68656c6c6f",
|
||||
ChainHead: &shared.ChainHead{
|
||||
HeadSlot: "0",
|
||||
HeadEpoch: "1",
|
||||
HeadBlockRoot: "0x",
|
||||
FinalizedSlot: "0",
|
||||
FinalizedEpoch: "0",
|
||||
FinalizedBlockRoot: "0x",
|
||||
JustifiedSlot: "0",
|
||||
JustifiedEpoch: "0",
|
||||
JustifiedBlockRoot: "0x",
|
||||
PreviousJustifiedSlot: "0",
|
||||
PreviousJustifiedEpoch: "0",
|
||||
PreviousJustifiedBlockRoot: "0x",
|
||||
OptimisticStatus: false,
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -90,7 +91,12 @@ func (s *Server) ImportKeystores(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var req ImportKeystoresRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -182,10 +188,16 @@ func (s *Server) DeleteKeystores(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
var req DeleteKeystoresRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Pubkeys) == 0 {
|
||||
httputil.WriteJson(w, &DeleteKeystoresResponse{Data: make([]*keymanager.KeyStatus, 0)})
|
||||
return
|
||||
@@ -447,10 +459,16 @@ func (s *Server) ImportRemoteKeys(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var req ImportRemoteKeysRequest
|
||||
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
adder, ok := km.(keymanager.PublicKeyAdder)
|
||||
if !ok {
|
||||
statuses := make([]*keymanager.KeyStatus, len(req.RemoteKeys))
|
||||
@@ -501,8 +519,14 @@ func (s *Server) DeleteRemoteKeys(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.HandleError(w, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var req DeleteRemoteKeysRequest
|
||||
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -592,11 +616,18 @@ func (s *Server) SetFeeRecipientByPubkey(w http.ResponseWriter, r *http.Request)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
|
||||
var req SetFeeRecipientByPubkeyRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ethAddress, valid := shared.ValidateHex(w, "Ethereum Address", req.Ethaddress, fieldparams.FeeRecipientLength)
|
||||
if !valid {
|
||||
return
|
||||
@@ -757,7 +788,12 @@ func (s *Server) SetGasLimit(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var req SetGasLimitRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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,29 @@ 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 !strings.Contains(r.URL.Path, api.WebUrlPrefix+"initialize") && // ignore some routes
|
||||
!strings.Contains(r.URL.Path, api.WebUrlPrefix+"health/logs") &&
|
||||
strings.Contains(r.URL.Path, api.WebUrlPrefix) {
|
||||
reqToken := r.Header.Get("Authorization")
|
||||
if reqToken == "" {
|
||||
http.Error(w, "unauthorized: no Authorization header passed. Please use an Authorization header with the jwt created in the prysm wallet", 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)
|
||||
|
||||
@@ -14,11 +14,11 @@ 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"
|
||||
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/accounts/wallet"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/client"
|
||||
iface "github.com/prysmaticlabs/prysm/v4/validator/client/iface"
|
||||
@@ -183,9 +183,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
|
||||
go func() {
|
||||
@@ -204,7 +201,7 @@ func (s *Server) Start() {
|
||||
return
|
||||
}
|
||||
validatorWebAddr := fmt.Sprintf("%s:%d", s.validatorGatewayHost, s.validatorGatewayPort)
|
||||
authTokenPath := filepath.Join(s.walletDir, authTokenFileName)
|
||||
authTokenPath := filepath.Join(s.walletDir, AuthTokenFileName)
|
||||
logValidatorWebAuth(validatorWebAddr, token, authTokenPath)
|
||||
go s.refreshAuthTokenFromFileChanges(s.ctx, authTokenPath)
|
||||
}
|
||||
@@ -216,6 +213,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)
|
||||
@@ -231,18 +230,30 @@ func (s *Server) InitializeRoutes() error {
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/feerecipient", s.SetFeeRecipientByPubkey).Methods(http.MethodPost)
|
||||
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)
|
||||
// auth endpoint
|
||||
s.router.HandleFunc(api.WebUrlPrefix+"initialize", s.Initialize).Methods(http.MethodGet)
|
||||
// accounts endpoints
|
||||
s.router.HandleFunc(api.WebUrlPrefix+"accounts", s.ListAccounts).Methods(http.MethodGet)
|
||||
s.router.HandleFunc(api.WebUrlPrefix+"accounts/backup", s.BackupAccounts).Methods(http.MethodPost)
|
||||
s.router.HandleFunc(api.WebUrlPrefix+"accounts/voluntary-exit", s.VoluntaryExit).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
|
||||
}
|
||||
|
||||
@@ -5,12 +5,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
var _ pb.AuthServer = (*Server)(nil)
|
||||
|
||||
func TestServer_InitializeRoutes(t *testing.T) {
|
||||
s := Server{
|
||||
router: mux.NewRouter(),
|
||||
@@ -33,6 +30,15 @@ func TestServer_InitializeRoutes(t *testing.T) {
|
||||
"/v2/validator/wallet/recover": {http.MethodPost},
|
||||
"/v2/validator/slashing-protection/export": {http.MethodGet},
|
||||
"/v2/validator/slashing-protection/import": {http.MethodPost},
|
||||
"/v2/validator/accounts": {http.MethodGet},
|
||||
"/v2/validator/accounts/backup": {http.MethodPost},
|
||||
"/v2/validator/accounts/voluntary-exit": {http.MethodPost},
|
||||
"/v2/validator/beacon/balances": {http.MethodGet},
|
||||
"/v2/validator/beacon/peers": {http.MethodGet},
|
||||
"/v2/validator/beacon/status": {http.MethodGet},
|
||||
"/v2/validator/beacon/summary": {http.MethodGet},
|
||||
"/v2/validator/beacon/validators": {http.MethodGet},
|
||||
"/v2/validator/initialize": {http.MethodGet},
|
||||
}
|
||||
gotRouteList := make(map[string][]string)
|
||||
err = s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
|
||||
@@ -90,6 +90,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 +149,38 @@ type ImportSlashingProtectionRequest struct {
|
||||
type ExportSlashingProtectionResponse struct {
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
type BackupAccountsRequest struct {
|
||||
PublicKeys []string `json:"public_keys"`
|
||||
BackupPassword string `json:"backup_password"`
|
||||
}
|
||||
|
||||
type VoluntaryExitRequest struct {
|
||||
PublicKeys []string `json:"public_keys"`
|
||||
}
|
||||
|
||||
type BackupAccountsResponse struct {
|
||||
ZipFile string `json:"zip_file"`
|
||||
}
|
||||
|
||||
type ListAccountsResponse struct {
|
||||
Accounts []*Account `json:"accounts"`
|
||||
NextPageToken string `json:"next_page_token"`
|
||||
TotalSize int32 `json:"total_size"`
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
ValidatingPublicKey string `json:"validating_public_key"`
|
||||
AccountName string `json:"account_name"`
|
||||
DepositTxData string `json:"deposit_tx_data"`
|
||||
DerivationPath string `json:"derivation_path"`
|
||||
}
|
||||
|
||||
type VoluntaryExitResponse struct {
|
||||
ExitedKeys [][]byte `protobuf:"bytes,1,rep,name=exited_keys,json=exitedKeys,proto3" json:"exited_keys,omitempty"`
|
||||
}
|
||||
|
||||
type InitializeAuthResponse struct {
|
||||
HasSignedUp bool `json:"has_signed_up"`
|
||||
HasWallet bool `json:"has_wallet"`
|
||||
}
|
||||
|
||||
11
validator/web/README.md
Normal file
11
validator/web/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
## Note on future releases
|
||||
** There is no longer an automated PR workflow for releasing the web ui due to its frozen state **
|
||||
This is due to this PR removal of build content:https://github.com/prysmaticlabs/prysm/pull/12719
|
||||
|
||||
in order to update the `site_data.go` follow the following steps to update the specific release of https://github.com/prysmaticlabs/prysm-web-ui/releases
|
||||
1. download and install https://github.com/kevinburke/go-bindata. (working as of version `4.0.2`) This tool will be used to generate the site_data.go file.
|
||||
2. download the specific release from https://github.com/prysmaticlabs/prysm-web-ui/releases
|
||||
3. run `go-bindata -pkg web -nometadata -modtime 0 -o site_data.go prysm-web-ui/` . `prysm-web-ui/` represents the extracted folder from the release.
|
||||
4. copy and replace the site_data.go in this package.
|
||||
5. Open a PR
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
const prefix = "external/prysm_web_ui/prysm-web-ui"
|
||||
const prefix = "prysm-web-ui"
|
||||
|
||||
// Handler serves web requests from the bundled site data.
|
||||
// DEPRECATED: Prysm Web UI and associated endpoints will be fully removed in a future hard fork.
|
||||
@@ -24,20 +24,28 @@ var Handler = func(res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
p = path.Join(prefix, p)
|
||||
|
||||
if d, ok := site[p]; ok {
|
||||
if d, ok := _bindata[p]; ok {
|
||||
m := mime.TypeByExtension(path.Ext(p))
|
||||
res.Header().Add("Content-Type", m)
|
||||
res.WriteHeader(200)
|
||||
if _, err := res.Write(d); err != nil {
|
||||
asset, err := d()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to unwrap asset data for http response")
|
||||
}
|
||||
if _, err := res.Write(asset.bytes); err != nil {
|
||||
log.WithError(err).Error("Failed to write http response")
|
||||
}
|
||||
} else if d, ok := site[path.Join(prefix, "index.html")]; ok {
|
||||
} else if d, ok := _bindata[path.Join(prefix, "index.html")]; ok {
|
||||
// Angular routing expects that routes are rewritten to serve index.html. For example, if
|
||||
// requesting /login, this should serve the single page app index.html.
|
||||
m := mime.TypeByExtension(".html")
|
||||
res.Header().Add("Content-Type", m)
|
||||
res.WriteHeader(200)
|
||||
if _, err := res.Write(d); err != nil {
|
||||
asset, err := d()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to unwrap asset data for http response")
|
||||
}
|
||||
if _, err := res.Write(asset.bytes); err != nil {
|
||||
log.WithError(err).Error("Failed to write http response")
|
||||
}
|
||||
} else { // If index.html is not present, serve 404. This should never happen.
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user