mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-08 18:51:19 +00:00
7143fe80bc
* WIP migrating keymanager api changes * gaz * fixing more tests * fixing unit tests * fixing deepsource * fixing visibility of package * fixing more package visability issues * gaz * fixing test * moving routes to proper location * removing whitespae for linting * Update validator/rpc/handlers_keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * radek's comments --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl>
402 lines
13 KiB
Go
402 lines
13 KiB
Go
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/golang/protobuf/ptypes/empty"
|
|
"github.com/gorilla/mux"
|
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
|
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
|
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
|
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
|
eth "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"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/accounts"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/accounts/iface"
|
|
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/client"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
|
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
|
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
|
|
mocks "github.com/prysmaticlabs/prysm/v4/validator/testing"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
func TestServer_SetVoluntaryExit(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
|
|
defaultWalletPath = setupWalletDir(t)
|
|
opts := []accounts.Option{
|
|
accounts.WithWalletDir(defaultWalletPath),
|
|
accounts.WithKeymanagerType(keymanager.Derived),
|
|
accounts.WithWalletPassword(strongPass),
|
|
accounts.WithSkipMnemonicConfirm(true),
|
|
}
|
|
acc, err := accounts.NewCLIManager(opts...)
|
|
require.NoError(t, err)
|
|
w, err := acc.WalletCreate(ctx)
|
|
require.NoError(t, err)
|
|
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
|
|
require.NoError(t, err)
|
|
|
|
m := &mock.MockValidator{Km: km}
|
|
vs, err := client.NewValidatorService(ctx, &client.Config{
|
|
Validator: m,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
dr, ok := km.(*derived.Keymanager)
|
|
require.Equal(t, true, ok)
|
|
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1)
|
|
require.NoError(t, err)
|
|
pubKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
|
require.NoError(t, err)
|
|
|
|
beaconClient := validatormock.NewMockValidatorClient(ctrl)
|
|
mockNodeClient := validatormock.NewMockNodeClient(ctrl)
|
|
// Any time in the past will suffice
|
|
genesisTime := ×tamppb.Timestamp{
|
|
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
|
}
|
|
|
|
beaconClient.EXPECT().ValidatorIndex(gomock.Any(), ð.ValidatorIndexRequest{PublicKey: pubKeys[0][:]}).
|
|
Times(3).
|
|
Return(ð.ValidatorIndexResponse{Index: 2}, nil)
|
|
|
|
beaconClient.EXPECT().DomainData(
|
|
gomock.Any(), // ctx
|
|
gomock.Any(), // epoch
|
|
).Times(3).
|
|
Return(ð.DomainResponse{SignatureDomain: make([]byte, common.HashLength)}, nil /*err*/)
|
|
|
|
mockNodeClient.EXPECT().
|
|
GetGenesis(gomock.Any(), gomock.Any()).
|
|
Times(3).
|
|
Return(ð.Genesis{GenesisTime: genesisTime}, nil)
|
|
|
|
s := &Server{
|
|
validatorService: vs,
|
|
beaconNodeValidatorClient: beaconClient,
|
|
wallet: w,
|
|
beaconNodeClient: mockNodeClient,
|
|
walletInitialized: w != nil,
|
|
}
|
|
|
|
type want struct {
|
|
epoch primitives.Epoch
|
|
validatorIndex uint64
|
|
signature []byte
|
|
}
|
|
|
|
type wantError struct {
|
|
expectedStatusCode int
|
|
expectedErrorMsg string
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
epoch int
|
|
pubkey string
|
|
w want
|
|
wError *wantError
|
|
mockSetup func(s *Server) error
|
|
}{
|
|
{
|
|
name: "Ok: with epoch",
|
|
epoch: 30000000,
|
|
pubkey: hexutil.Encode(pubKeys[0][:]),
|
|
w: want{
|
|
epoch: 30000000,
|
|
validatorIndex: 2,
|
|
signature: []uint8{175, 157, 5, 134, 253, 2, 193, 35, 176, 43, 217, 36, 39, 240, 24, 79, 207, 133, 150, 7, 237, 16, 54, 244, 64, 27, 244, 17, 8, 225, 140, 1, 172, 24, 35, 95, 178, 116, 172, 213, 113, 182, 193, 61, 192, 65, 162, 253, 19, 202, 111, 164, 195, 215, 0, 205, 95, 7, 30, 251, 244, 157, 210, 155, 238, 30, 35, 219, 177, 232, 174, 62, 218, 69, 23, 249, 180, 140, 60, 29, 190, 249, 229, 95, 235, 236, 81, 33, 60, 4, 201, 227, 70, 239, 167, 2},
|
|
},
|
|
},
|
|
{
|
|
name: "Ok: epoch not set",
|
|
pubkey: hexutil.Encode(pubKeys[0][:]),
|
|
w: want{
|
|
epoch: 0,
|
|
validatorIndex: 2,
|
|
signature: []uint8{},
|
|
},
|
|
},
|
|
{
|
|
name: "Error: Missing Public Key in URL Params",
|
|
epoch: 30000000,
|
|
wError: &wantError{
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
expectedErrorMsg: "pubkey is required in URL params",
|
|
},
|
|
},
|
|
{
|
|
name: "Error: Invalid Public Key Length",
|
|
epoch: 30000000,
|
|
pubkey: "0x1asd1231",
|
|
wError: &wantError{
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
expectedErrorMsg: "pubkey is invalid: invalid hex string",
|
|
},
|
|
},
|
|
{
|
|
name: "Error: No Wallet Found",
|
|
epoch: 30000000,
|
|
pubkey: hexutil.Encode(pubKeys[0][:]),
|
|
wError: &wantError{
|
|
expectedStatusCode: http.StatusServiceUnavailable,
|
|
expectedErrorMsg: "No wallet found",
|
|
},
|
|
mockSetup: func(s *Server) error {
|
|
s.wallet = nil
|
|
s.walletInitialized = false
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.mockSetup != nil {
|
|
require.NoError(t, tt.mockSetup(s))
|
|
}
|
|
req := httptest.NewRequest("POST", fmt.Sprintf("/eth/v1/validator/{pubkey}/voluntary_exit?epoch=%d", tt.epoch), nil)
|
|
req = mux.SetURLVars(req, map[string]string{"pubkey": tt.pubkey})
|
|
w := httptest.NewRecorder()
|
|
w.Body = &bytes.Buffer{}
|
|
|
|
s.SetVoluntaryExit(w, req)
|
|
if tt.wError != nil {
|
|
assert.Equal(t, tt.wError.expectedStatusCode, w.Code)
|
|
require.StringContains(t, tt.wError.expectedErrorMsg, w.Body.String())
|
|
return
|
|
} else {
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
resp := &SetVoluntaryExitResponse{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
|
|
if tt.w.epoch == 0 {
|
|
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
|
|
require.NoError(t, err)
|
|
tt.w.epoch, err = client.CurrentEpoch(genesisResponse.GenesisTime)
|
|
require.NoError(t, err)
|
|
req2 := httptest.NewRequest("POST", fmt.Sprintf("/eth/v1/validator/{pubkey}/voluntary_exit?epoch=%d", tt.epoch), nil)
|
|
req2 = mux.SetURLVars(req2, map[string]string{"pubkey": hexutil.Encode(pubKeys[0][:])})
|
|
w2 := httptest.NewRecorder()
|
|
w2.Body = &bytes.Buffer{}
|
|
s.SetVoluntaryExit(w2, req2)
|
|
if tt.wError != nil {
|
|
assert.Equal(t, tt.wError.expectedStatusCode, w2.Code)
|
|
require.StringContains(t, tt.wError.expectedErrorMsg, w2.Body.String())
|
|
} else {
|
|
assert.Equal(t, http.StatusOK, w2.Code)
|
|
resp2 := &SetVoluntaryExitResponse{}
|
|
require.NoError(t, json.Unmarshal(w2.Body.Bytes(), resp2))
|
|
tt.w.signature, err = hexutil.Decode(resp2.Data.Signature)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
}
|
|
if tt.wError == nil {
|
|
require.Equal(t, fmt.Sprintf("%d", tt.w.epoch), resp.Data.Message.Epoch)
|
|
require.Equal(t, fmt.Sprintf("%d", tt.w.validatorIndex), resp.Data.Message.ValidatorIndex)
|
|
require.NotEmpty(t, resp.Data.Signature)
|
|
bSig, err := hexutil.Decode(resp.Data.Signature)
|
|
require.NoError(t, err)
|
|
ok = bytes.Equal(tt.w.signature, bSig)
|
|
require.Equal(t, true, ok)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServer_ListRemoteKeys(t *testing.T) {
|
|
t.Run("wallet not ready", func(t *testing.T) {
|
|
s := Server{}
|
|
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
|
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
|
})
|
|
ctx := context.Background()
|
|
w := wallet.NewWalletForWeb3Signer()
|
|
root := make([]byte, fieldparams.RootLength)
|
|
root[0] = 1
|
|
bytevalue, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
|
|
require.NoError(t, err)
|
|
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
|
|
config := &remoteweb3signer.SetupConfig{
|
|
BaseEndpoint: "http://example.com",
|
|
GenesisValidatorsRoot: root,
|
|
ProvidedPublicKeys: pubkeys,
|
|
}
|
|
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
|
|
require.NoError(t, err)
|
|
vs, err := client.NewValidatorService(ctx, &client.Config{
|
|
Wallet: w,
|
|
Validator: &mock.MockValidator{
|
|
Km: km,
|
|
},
|
|
Web3SignerConfig: config,
|
|
})
|
|
require.NoError(t, err)
|
|
s := &Server{
|
|
walletInitialized: true,
|
|
wallet: w,
|
|
validatorService: vs,
|
|
}
|
|
expectedKeys, err := km.FetchValidatingPublicKeys(ctx)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/eth/v1/remotekeys", nil)
|
|
w := httptest.NewRecorder()
|
|
w.Body = &bytes.Buffer{}
|
|
s.ListRemoteKeys(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
resp := &ListRemoteKeysResponse{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
|
|
for i := 0; i < len(resp.Data); i++ {
|
|
require.DeepEqual(t, hexutil.Encode(expectedKeys[i][:]), resp.Data[i].Pubkey)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestServer_ImportRemoteKeys(t *testing.T) {
|
|
t.Run("wallet not ready", func(t *testing.T) {
|
|
s := Server{}
|
|
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
|
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
|
})
|
|
ctx := context.Background()
|
|
w := wallet.NewWalletForWeb3Signer()
|
|
root := make([]byte, fieldparams.RootLength)
|
|
root[0] = 1
|
|
config := &remoteweb3signer.SetupConfig{
|
|
BaseEndpoint: "http://example.com",
|
|
GenesisValidatorsRoot: root,
|
|
ProvidedPublicKeys: nil,
|
|
}
|
|
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
|
|
require.NoError(t, err)
|
|
vs, err := client.NewValidatorService(ctx, &client.Config{
|
|
Wallet: w,
|
|
Validator: &mock.MockValidator{
|
|
Km: km,
|
|
},
|
|
Web3SignerConfig: config,
|
|
})
|
|
require.NoError(t, err)
|
|
s := &Server{
|
|
walletInitialized: true,
|
|
wallet: w,
|
|
validatorService: vs,
|
|
}
|
|
pubkey := "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
|
remoteKeys := []*RemoteKey{
|
|
{
|
|
Pubkey: pubkey,
|
|
},
|
|
}
|
|
|
|
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
|
|
var body bytes.Buffer
|
|
b, err := json.Marshal(&ImportRemoteKeysRequest{RemoteKeys: remoteKeys})
|
|
require.NoError(t, err)
|
|
body.Write(b)
|
|
req := httptest.NewRequest("GET", "/eth/v1/remotekeys", &body)
|
|
w := httptest.NewRecorder()
|
|
w.Body = &bytes.Buffer{}
|
|
s.ImportRemoteKeys(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
expectedStatuses := []*keymanager.KeyStatus{
|
|
{
|
|
Status: remoteweb3signer.StatusImported,
|
|
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
|
|
},
|
|
}
|
|
resp := &RemoteKeysResponse{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
|
|
for i := 0; i < len(resp.Data); i++ {
|
|
require.DeepEqual(t, expectedStatuses[i], resp.Data[i])
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestServer_DeleteRemoteKeys(t *testing.T) {
|
|
t.Run("wallet not ready", func(t *testing.T) {
|
|
s := Server{}
|
|
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
|
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
|
})
|
|
ctx := context.Background()
|
|
w := wallet.NewWalletForWeb3Signer()
|
|
root := make([]byte, fieldparams.RootLength)
|
|
root[0] = 1
|
|
pkey := "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
|
bytevalue, err := hexutil.Decode(pkey)
|
|
require.NoError(t, err)
|
|
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
|
|
config := &remoteweb3signer.SetupConfig{
|
|
BaseEndpoint: "http://example.com",
|
|
GenesisValidatorsRoot: root,
|
|
ProvidedPublicKeys: pubkeys,
|
|
}
|
|
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
|
|
require.NoError(t, err)
|
|
vs, err := client.NewValidatorService(ctx, &client.Config{
|
|
Wallet: w,
|
|
Validator: &mock.MockValidator{
|
|
Km: km,
|
|
},
|
|
Web3SignerConfig: config,
|
|
})
|
|
require.NoError(t, err)
|
|
s := &Server{
|
|
walletInitialized: true,
|
|
wallet: w,
|
|
validatorService: vs,
|
|
}
|
|
|
|
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
|
|
var body bytes.Buffer
|
|
b, err := json.Marshal(&DeleteRemoteKeysRequest{
|
|
Pubkeys: []string{pkey},
|
|
})
|
|
require.NoError(t, err)
|
|
body.Write(b)
|
|
req := httptest.NewRequest("DELETE", "/eth/v1/remotekeys", &body)
|
|
w := httptest.NewRecorder()
|
|
w.Body = &bytes.Buffer{}
|
|
s.DeleteRemoteKeys(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
expectedStatuses := []*keymanager.KeyStatus{
|
|
{
|
|
Status: remoteweb3signer.StatusDeleted,
|
|
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pkey),
|
|
},
|
|
}
|
|
resp := &RemoteKeysResponse{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
|
|
for i := 0; i < len(resp.Data); i++ {
|
|
require.DeepEqual(t, expectedStatuses[i], resp.Data[i])
|
|
|
|
}
|
|
expectedKeys, err := km.FetchValidatingPublicKeys(ctx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(expectedKeys))
|
|
})
|
|
}
|