HTTP Validator API: /eth/v1/validator/{pubkey}/feerecipient (#13085)

* migrating fee recipient endpoints to pure http implementation

* fixing linting

* fixing type name

* fixing after merging develop

* fixing linting and tests

* Update validator/rpc/structs.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/rpc/structs.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/rpc/structs.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
james-prysm
2023-10-24 11:55:45 -05:00
committed by GitHub
parent a2f60364ae
commit 2a067d5d03
14 changed files with 683 additions and 1478 deletions

View File

@@ -17,7 +17,6 @@ func (f *ValidatorEndpointFactory) IsNil() bool {
func (*ValidatorEndpointFactory) Paths() []string {
return []string{
"/eth/v1/keystores",
"/eth/v1/validator/{pubkey}/feerecipient",
}
}
@@ -31,10 +30,6 @@ func (*ValidatorEndpointFactory) Create(path string) (*apimiddleware.Endpoint, e
endpoint.PostResponse = &ImportKeystoresResponseJson{}
endpoint.DeleteRequest = &DeleteKeystoresRequestJson{}
endpoint.DeleteResponse = &DeleteKeystoresResponseJson{}
case "/eth/v1/validator/{pubkey}/feerecipient":
endpoint.GetResponse = &GetFeeRecipientByPubkeyResponseJson{}
endpoint.PostRequest = &SetFeeRecipientByPubkeyRequestJson{}
endpoint.DeleteRequest = &DeleteFeeRecipientByPubkeyRequestJson{}
default:
return nil, errors.New("invalid path")
}

View File

@@ -32,20 +32,3 @@ type DeleteKeystoresResponseJson struct {
Statuses []*StatusJson `json:"data"`
SlashingProtection string `json:"slashing_protection"`
}
type FeeRecipientJson struct {
Pubkey string `json:"pubkey" hex:"true"`
Ethaddress string `json:"ethaddress" address:"true"`
}
type GetFeeRecipientByPubkeyResponseJson struct {
Data *FeeRecipientJson `json:"data"`
}
type SetFeeRecipientByPubkeyRequestJson struct {
Ethaddress string `json:"ethaddress" hex:"true"`
}
type DeleteFeeRecipientByPubkeyRequestJson struct {
Pubkey string `json:"pubkey" hex:"true"`
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
"github.com/pkg/errors"
@@ -242,6 +243,178 @@ func (s *Server) DeleteRemoteKeys(w http.ResponseWriter, r *http.Request) {
http2.WriteJson(w, RemoteKeysResponse{Data: deleter.DeletePublicKeys(req.Pubkeys)})
}
// ListFeeRecipientByPubkey returns the public key to eth address mapping object to the end user.
func (s *Server) ListFeeRecipientByPubkey(w http.ResponseWriter, r *http.Request) {
_, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.ListFeeRecipientByPubkey")
defer span.End()
if s.validatorService == nil {
http2.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
return
}
rawPubkey := mux.Vars(r)["pubkey"]
if rawPubkey == "" {
http2.HandleError(w, "pubkey is required in URL params", http.StatusBadRequest)
return
}
pubkey, valid := shared.ValidateHex(w, "pubkey", rawPubkey, fieldparams.BLSPubkeyLength)
if !valid {
return
}
finalResp := &GetFeeRecipientByPubkeyResponse{
Data: &FeeRecipient{
Pubkey: rawPubkey,
},
}
proposerSettings := s.validatorService.ProposerSettings()
// If fee recipient is defined for this specific pubkey in proposer configuration, use it
if proposerSettings != nil && proposerSettings.ProposeConfig != nil {
proposerOption, found := proposerSettings.ProposeConfig[bytesutil.ToBytes48(pubkey)]
if found && proposerOption.FeeRecipientConfig != nil {
finalResp.Data.Ethaddress = proposerOption.FeeRecipientConfig.FeeRecipient.String()
http2.WriteJson(w, finalResp)
return
}
}
// If fee recipient is defined in default configuration, use it
if proposerSettings != nil && proposerSettings.DefaultConfig != nil && proposerSettings.DefaultConfig.FeeRecipientConfig != nil {
finalResp.Data.Ethaddress = proposerSettings.DefaultConfig.FeeRecipientConfig.FeeRecipient.String()
http2.WriteJson(w, finalResp)
return
}
http2.HandleError(w, "No fee recipient set", http.StatusBadRequest)
}
// SetFeeRecipientByPubkey updates the eth address mapped to the public key.
func (s *Server) SetFeeRecipientByPubkey(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.SetFeeRecipientByPubkey")
defer span.End()
if s.validatorService == nil {
http2.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
return
}
rawPubkey := mux.Vars(r)["pubkey"]
if rawPubkey == "" {
http2.HandleError(w, "pubkey is required in URL params", http.StatusBadRequest)
return
}
pubkey, valid := shared.ValidateHex(w, "pubkey", rawPubkey, fieldparams.BLSPubkeyLength)
if !valid {
return
}
var req SetFeeRecipientByPubkeyRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http2.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
}
feeRecipient := common.BytesToAddress(ethAddress)
settings := s.validatorService.ProposerSettings()
switch {
case settings == nil:
settings = &validatorServiceConfig.ProposerSettings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*validatorServiceConfig.ProposerOption{
bytesutil.ToBytes48(pubkey): {
FeeRecipientConfig: &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
},
BuilderConfig: nil,
},
},
DefaultConfig: nil,
}
case settings.ProposeConfig == nil:
var builderConfig *validatorServiceConfig.BuilderConfig
if settings.DefaultConfig != nil && settings.DefaultConfig.BuilderConfig != nil {
builderConfig = settings.DefaultConfig.BuilderConfig.Clone()
}
settings.ProposeConfig = map[[fieldparams.BLSPubkeyLength]byte]*validatorServiceConfig.ProposerOption{
bytesutil.ToBytes48(pubkey): {
FeeRecipientConfig: &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
},
BuilderConfig: builderConfig,
},
}
default:
proposerOption, found := settings.ProposeConfig[bytesutil.ToBytes48(pubkey)]
if found && proposerOption != nil {
proposerOption.FeeRecipientConfig = &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
}
} else {
var builderConfig = &validatorServiceConfig.BuilderConfig{}
if settings.DefaultConfig != nil && settings.DefaultConfig.BuilderConfig != nil {
builderConfig = settings.DefaultConfig.BuilderConfig.Clone()
}
settings.ProposeConfig[bytesutil.ToBytes48(pubkey)] = &validatorServiceConfig.ProposerOption{
FeeRecipientConfig: &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
},
BuilderConfig: builderConfig,
}
}
}
// save the settings
if err := s.validatorService.SetProposerSettings(ctx, settings); err != nil {
http2.HandleError(w, "Could not set proposer settings: "+err.Error(), http.StatusInternalServerError)
return
}
// override the 200 success with 202 according to the specs
w.WriteHeader(http.StatusAccepted)
}
// DeleteFeeRecipientByPubkey updates the eth address mapped to the public key to the default fee recipient listed
func (s *Server) DeleteFeeRecipientByPubkey(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.DeleteFeeRecipientByPubkey")
defer span.End()
if s.validatorService == nil {
http2.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
return
}
rawPubkey := mux.Vars(r)["pubkey"]
if rawPubkey == "" {
http2.HandleError(w, "pubkey is required in URL params", http.StatusBadRequest)
return
}
pubkey, valid := shared.ValidateHex(w, "pubkey", rawPubkey, fieldparams.BLSPubkeyLength)
if !valid {
return
}
settings := s.validatorService.ProposerSettings()
if settings != nil && settings.ProposeConfig != nil {
proposerOption, found := settings.ProposeConfig[bytesutil.ToBytes48(pubkey)]
if found {
proposerOption.FeeRecipientConfig = nil
}
}
// save the settings
if err := s.validatorService.SetProposerSettings(ctx, settings); err != nil {
http2.HandleError(w, "Could not set proposer settings: "+err.Error(), http.StatusInternalServerError)
return
}
// override the 200 success with 204 according to the specs
w.WriteHeader(http.StatusNoContent)
}
// GetGasLimit returns the gas limit measured in gwei defined for the custom mev builder by public key
func (s *Server) GetGasLimit(w http.ResponseWriter, r *http.Request) {
_, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.GetGasLimit")
@@ -292,7 +465,6 @@ func (s *Server) SetGasLimit(w http.ResponseWriter, r *http.Request) {
http2.HandleError(w, "Validator service not ready", http.StatusServiceUnavailable)
return
}
rawPubkey := mux.Vars(r)["pubkey"]
if rawPubkey == "" {
http2.HandleError(w, "pubkey is required in URL params", http.StatusBadRequest)
@@ -352,7 +524,6 @@ func (s *Server) SetGasLimit(w http.ResponseWriter, r *http.Request) {
http2.HandleError(w, "Could not set proposer settings: "+err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
}
@@ -365,7 +536,6 @@ func (s *Server) DeleteGasLimit(w http.ResponseWriter, r *http.Request) {
http2.HandleError(w, "Validator service not ready", http.StatusServiceUnavailable)
return
}
rawPubkey := mux.Vars(r)["pubkey"]
if rawPubkey == "" {
http2.HandleError(w, "pubkey is required in URL params", http.StatusBadRequest)

View File

@@ -834,3 +834,426 @@ func TestServer_DeleteRemoteKeys(t *testing.T) {
require.Equal(t, 0, len(expectedKeys))
})
}
func TestServer_ListFeeRecipientByPubkey(t *testing.T) {
ctx := context.Background()
pubkey := "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"
byteval, err := hexutil.Decode(pubkey)
require.NoError(t, err)
type want struct {
EthAddress string
}
tests := []struct {
name string
args *validatorserviceconfig.ProposerSettings
want *want
cached *eth.FeeRecipientByPubKeyResponse
}{
{
name: "ProposerSettings.ProposeConfig.FeeRecipientConfig defined for pubkey (and ProposerSettings.DefaultConfig.FeeRecipientConfig defined)",
args: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): {
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
},
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
},
},
},
want: &want{
EthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
},
{
name: "ProposerSettings.ProposeConfig.FeeRecipientConfig NOT defined for pubkey and ProposerSettings.DefaultConfig.FeeRecipientConfig defined",
args: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{},
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
},
want: &want{
EthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &mock.Validator{}
err := m.SetProposerSettings(ctx, tt.args)
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
}
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.ListFeeRecipientByPubkey(w, req)
assert.Equal(t, http.StatusOK, w.Code)
resp := &GetFeeRecipientByPubkeyResponse{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
assert.Equal(t, tt.want.EthAddress, resp.Data.Ethaddress)
})
}
}
func TestServer_ListFeeRecipientByPubKey_NoFeeRecipientSet(t *testing.T) {
ctx := context.Background()
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: &mock.Validator{},
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
}
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.ListFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusOK, w.Code)
require.StringContains(t, "No fee recipient set", w.Body.String())
}
func TestServer_ListFeeRecipientByPubkey_ValidatorServiceNil(t *testing.T) {
s := &Server{}
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": "0x00"})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.SetFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusOK, w.Code)
require.StringContains(t, "Validator service not ready", w.Body.String())
}
func TestServer_ListFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
s := &Server{
validatorService: &client.ValidatorService{},
}
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": "0x00"})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.SetFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusOK, w.Code)
require.StringContains(t, "Invalid pubkey", w.Body.String())
}
func TestServer_FeeRecipientByPubkey(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
beaconClient := validatormock.NewMockValidatorClient(ctrl)
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
pubkey := "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"
byteval, err := hexutil.Decode(pubkey)
require.NoError(t, err)
type want struct {
valEthAddress string
defaultEthaddress string
}
type beaconResp struct {
resp *eth.FeeRecipientByPubKeyResponse
error error
}
tests := []struct {
name string
args string
proposerSettings *validatorserviceconfig.ProposerSettings
want *want
wantErr bool
beaconReturn *beaconResp
}{
{
name: "ProposerSetting is nil",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: nil,
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: nil,
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil AND ProposerSetting.Defaultconfig is defined",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: nil,
DefaultConfig: &validatorserviceconfig.ProposerOption{},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is defined for pubkey",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): {},
},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig not defined for pubkey",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil for pubkey",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): nil,
},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil for pubkey AND DefaultConfig is not nil",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): nil,
},
DefaultConfig: &validatorserviceconfig.ProposerOption{},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &mock.Validator{}
err := m.SetProposerSettings(ctx, tt.proposerSettings)
require.NoError(t, err)
validatorDB := dbtest.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{})
// save a default here
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
ValDB: validatorDB,
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: beaconClient,
valDB: validatorDB,
}
request := &SetFeeRecipientByPubkeyRequest{
Ethaddress: tt.args,
}
var buf bytes.Buffer
err = json.NewEncoder(&buf).Encode(request)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), &buf)
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.SetFeeRecipientByPubkey(w, req)
assert.Equal(t, http.StatusAccepted, w.Code)
assert.Equal(t, tt.want.valEthAddress, s.validatorService.ProposerSettings().ProposeConfig[bytesutil.ToBytes48(byteval)].FeeRecipientConfig.FeeRecipient.Hex())
})
}
}
func TestServer_SetFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
s := &Server{
validatorService: &client.ValidatorService{},
}
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": "0x00"})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.SetFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusAccepted, w.Code)
require.StringContains(t, "Invalid pubkey", w.Body.String())
}
func TestServer_SetFeeRecipientByPubkey_InvalidFeeRecipient(t *testing.T) {
pubkey := "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"
s := &Server{
validatorService: &client.ValidatorService{},
}
request := &SetFeeRecipientByPubkeyRequest{
Ethaddress: "0x00",
}
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(request)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), &buf)
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.SetFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusAccepted, w.Code)
require.StringContains(t, "Invalid Ethereum Address", w.Body.String())
}
func TestServer_DeleteFeeRecipientByPubkey(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
pubkey := "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"
byteval, err := hexutil.Decode(pubkey)
require.NoError(t, err)
type want struct {
EthAddress string
}
tests := []struct {
name string
proposerSettings *validatorserviceconfig.ProposerSettings
want *want
wantErr bool
}{
{
name: "Happy Path Test",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): {
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x055Fb65722E7b2455012BFEBf6177F1D2e9738D5"),
},
},
},
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
},
want: &want{
EthAddress: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9").Hex(),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &mock.Validator{}
err := m.SetProposerSettings(ctx, tt.proposerSettings)
require.NoError(t, err)
validatorDB := dbtest.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{})
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
ValDB: validatorDB,
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
valDB: validatorDB,
}
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.DeleteFeeRecipientByPubkey(w, req)
assert.Equal(t, http.StatusNoContent, w.Code)
assert.Equal(t, true, s.validatorService.ProposerSettings().ProposeConfig[bytesutil.ToBytes48(byteval)].FeeRecipientConfig == nil)
})
}
}
func TestServer_DeleteFeeRecipientByPubkey_ValidatorServiceNil(t *testing.T) {
s := &Server{}
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": "0x1234567878903438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.DeleteFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusNoContent, w.Code)
require.StringContains(t, "Validator service not ready", w.Body.String())
}
func TestServer_DeleteFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
s := &Server{
validatorService: &client.ValidatorService{},
}
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/validator/{pubkey}/feerecipient"), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": "0x123"})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.DeleteFeeRecipientByPubkey(w, req)
assert.NotEqual(t, http.StatusNoContent, w.Code)
require.StringContains(t, "pubkey is invalid", w.Body.String())
}

View File

@@ -227,6 +227,9 @@ func (s *Server) InitializeRoutes() error {
s.router.HandleFunc("/eth/v1/validator/{pubkey}/gas_limit", s.GetGasLimit).Methods(http.MethodGet)
s.router.HandleFunc("/eth/v1/validator/{pubkey}/gas_limit", s.SetGasLimit).Methods(http.MethodPost)
s.router.HandleFunc("/eth/v1/validator/{pubkey}/gas_limit", s.DeleteGasLimit).Methods(http.MethodDelete)
s.router.HandleFunc("/eth/v1/validator/{pubkey}/feerecipient", s.ListFeeRecipientByPubkey).Methods(http.MethodGet)
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)
// ...
log.Info("Initialized REST API routes")

View File

@@ -21,6 +21,7 @@ func TestServer_InitializeRoutes(t *testing.T) {
wantRouteList := map[string][]string{
"/eth/v1/remotekeys": {http.MethodGet, http.MethodPost, http.MethodDelete},
"/eth/v1/validator/{pubkey}/gas_limit": {http.MethodGet, http.MethodPost, http.MethodDelete},
"/eth/v1/validator/{pubkey}/feerecipient": {http.MethodGet, http.MethodPost, http.MethodDelete},
"/eth/v1/validator/{pubkey}/voluntary_exit": {http.MethodPost},
}
gotRouteList := make(map[string][]string)

View File

@@ -6,21 +6,15 @@ import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/ptypes/empty"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
validatorServiceConfig "github.com/prysmaticlabs/prysm/v4/config/validator/service"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
slashingprotection "github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history"
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
@@ -260,172 +254,3 @@ func (s *Server) slashingProtectionHistoryForDeletedKeys(
}
return slashingprotection.ExportStandardProtectionJSON(ctx, s.valDB, filteredKeys...)
}
// ListFeeRecipientByPubkey returns the public key to eth address mapping object to the end user.
func (s *Server) ListFeeRecipientByPubkey(ctx context.Context, req *ethpbservice.PubkeyRequest) (*ethpbservice.GetFeeRecipientByPubkeyResponse, error) {
if s.validatorService == nil {
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready")
}
validatorKey := req.Pubkey
if err := validatePublicKey(validatorKey); err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
finalResp := &ethpbservice.GetFeeRecipientByPubkeyResponse{
Data: &ethpbservice.GetFeeRecipientByPubkeyResponse_FeeRecipient{
Pubkey: validatorKey,
},
}
proposerSettings := s.validatorService.ProposerSettings()
// If fee recipient is defined for this specific pubkey in proposer configuration, use it
if proposerSettings != nil && proposerSettings.ProposeConfig != nil {
proposerOption, found := proposerSettings.ProposeConfig[bytesutil.ToBytes48(validatorKey)]
if found && proposerOption.FeeRecipientConfig != nil {
finalResp.Data.Ethaddress = proposerOption.FeeRecipientConfig.FeeRecipient.Bytes()
return finalResp, nil
}
}
// If fee recipient is defined in default configuration, use it
if proposerSettings != nil && proposerSettings.DefaultConfig != nil && proposerSettings.DefaultConfig.FeeRecipientConfig != nil {
finalResp.Data.Ethaddress = proposerSettings.DefaultConfig.FeeRecipientConfig.FeeRecipient.Bytes()
return finalResp, nil
}
// Else, use the one defined in beacon node TODO: remove this with db removal
resp, err := s.beaconNodeValidatorClient.GetFeeRecipientByPubKey(ctx, &eth.FeeRecipientByPubKeyRequest{
PublicKey: validatorKey,
})
if err != nil {
return nil, status.Error(codes.Internal, "Failed to retrieve default fee recipient from beacon node")
}
if resp != nil && len(resp.FeeRecipient) != 0 {
finalResp.Data.Ethaddress = resp.FeeRecipient
return finalResp, nil
}
return nil, status.Error(codes.InvalidArgument, "No fee recipient set")
}
// SetFeeRecipientByPubkey updates the eth address mapped to the public key.
func (s *Server) SetFeeRecipientByPubkey(ctx context.Context, req *ethpbservice.SetFeeRecipientByPubkeyRequest) (*empty.Empty, error) {
if s.validatorService == nil {
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready")
}
validatorKey := req.Pubkey
feeRecipient := common.BytesToAddress(req.Ethaddress)
if err := validatePublicKey(validatorKey); err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
encoded := hexutil.Encode(req.Ethaddress)
if !common.IsHexAddress(encoded) {
return nil, status.Error(
codes.InvalidArgument, "Fee recipient is not a valid Ethereum address")
}
settings := s.validatorService.ProposerSettings()
switch {
case settings == nil:
settings = &validatorServiceConfig.ProposerSettings{
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*validatorServiceConfig.ProposerOption{
bytesutil.ToBytes48(validatorKey): {
FeeRecipientConfig: &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
},
BuilderConfig: nil,
},
},
DefaultConfig: nil,
}
case settings.ProposeConfig == nil:
var builderConfig *validatorServiceConfig.BuilderConfig
if settings.DefaultConfig != nil && settings.DefaultConfig.BuilderConfig != nil {
builderConfig = settings.DefaultConfig.BuilderConfig.Clone()
}
settings.ProposeConfig = map[[fieldparams.BLSPubkeyLength]byte]*validatorServiceConfig.ProposerOption{
bytesutil.ToBytes48(validatorKey): {
FeeRecipientConfig: &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
},
BuilderConfig: builderConfig,
},
}
default:
proposerOption, found := settings.ProposeConfig[bytesutil.ToBytes48(validatorKey)]
if found && proposerOption != nil {
proposerOption.FeeRecipientConfig = &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
}
} else {
var builderConfig = &validatorServiceConfig.BuilderConfig{}
if settings.DefaultConfig != nil && settings.DefaultConfig.BuilderConfig != nil {
builderConfig = settings.DefaultConfig.BuilderConfig.Clone()
}
settings.ProposeConfig[bytesutil.ToBytes48(validatorKey)] = &validatorServiceConfig.ProposerOption{
FeeRecipientConfig: &validatorServiceConfig.FeeRecipientConfig{
FeeRecipient: feeRecipient,
},
BuilderConfig: builderConfig,
}
}
}
// save the settings
if err := s.validatorService.SetProposerSettings(ctx, settings); err != nil {
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set proposer settings: %v", err)
}
// override the 200 success with 202 according to the specs
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", "202")); err != nil {
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set custom success code header: %v", err)
}
return &empty.Empty{}, nil
}
// DeleteFeeRecipientByPubkey updates the eth address mapped to the public key to the default fee recipient listed
func (s *Server) DeleteFeeRecipientByPubkey(ctx context.Context, req *ethpbservice.PubkeyRequest) (*empty.Empty, error) {
if s.validatorService == nil {
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready")
}
validatorKey := req.Pubkey
if err := validatePublicKey(validatorKey); err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
settings := s.validatorService.ProposerSettings()
if settings != nil && settings.ProposeConfig != nil {
proposerOption, found := settings.ProposeConfig[bytesutil.ToBytes48(validatorKey)]
if found {
proposerOption.FeeRecipientConfig = nil
}
}
// save the settings
if err := s.validatorService.SetProposerSettings(ctx, settings); err != nil {
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set proposer settings: %v", err)
}
// override the 200 success with 204 according to the specs
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", "204")); err != nil {
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set custom success code header: %v", err)
}
return &empty.Empty{}, nil
}
func validatePublicKey(pubkey []byte) error {
if len(pubkey) != fieldparams.BLSPubkeyLength {
return status.Errorf(
codes.InvalidArgument, "Provided public key in path is not byte length %d and not a valid bls public key", fieldparams.BLSPubkeyLength)
}
return nil
}

View File

@@ -4,40 +4,29 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"testing"
"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/google/uuid"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v4/config/validator/service"
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
validatorpb "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"
"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/db/kv"
dbtest "github.com/prysmaticlabs/prysm/v4/validator/db/testing"
"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"
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
mocks "github.com/prysmaticlabs/prysm/v4/validator/testing"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
"google.golang.org/grpc"
)
func TestServer_ListKeystores(t *testing.T) {
@@ -538,452 +527,3 @@ func createRandomKeystore(t testing.TB, password string) *keymanager.Keystore {
Description: encryptor.Name(),
}
}
func TestServer_ListFeeRecipientByPubkey(t *testing.T) {
ctx := context.Background()
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")
require.NoError(t, err)
type want struct {
EthAddress string
}
tests := []struct {
name string
args *validatorserviceconfig.ProposerSettings
want *want
cached *eth.FeeRecipientByPubKeyResponse
}{
{
name: "ProposerSettings.ProposeConfig.FeeRecipientConfig defined for pubkey (and ProposerSettings.DefaultConfig.FeeRecipientConfig defined)",
args: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): {
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
},
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
},
},
},
want: &want{
EthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
},
{
name: "ProposerSettings.ProposeConfig.FeeRecipientConfig NOT defined for pubkey and ProposerSettings.DefaultConfig.FeeRecipientConfig defined",
args: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{},
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
},
want: &want{
EthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
},
{
name: "ProposerSettings is nil and beacon node response is correct",
args: nil,
want: &want{
EthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
cached: &eth.FeeRecipientByPubKeyResponse{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9").Bytes(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
mockValidatorClient := validatormock.NewMockValidatorClient(ctrl)
m := &mock.Validator{}
err := m.SetProposerSettings(ctx, tt.args)
require.NoError(t, err)
if tt.args == nil {
mockValidatorClient.EXPECT().GetFeeRecipientByPubKey(gomock.Any(), gomock.Any()).Return(tt.cached, nil)
}
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: mockValidatorClient,
}
got, err := s.ListFeeRecipientByPubkey(ctx, &ethpbservice.PubkeyRequest{Pubkey: byteval})
require.NoError(t, err)
assert.Equal(t, tt.want.EthAddress, common.BytesToAddress(got.Data.Ethaddress).Hex())
})
}
}
func TestServer_ListFeeRecipientByPubKey_BeaconNodeError(t *testing.T) {
ctx := context.Background()
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")
require.NoError(t, err)
ctrl := gomock.NewController(t)
mockValidatorClient := validatormock.NewMockValidatorClient(ctrl)
mockValidatorClient.EXPECT().GetFeeRecipientByPubKey(gomock.Any(), gomock.Any()).Return(nil, errors.New("custom error"))
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: &mock.Validator{},
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: mockValidatorClient,
}
_, err = s.ListFeeRecipientByPubkey(ctx, &ethpbservice.PubkeyRequest{Pubkey: byteval})
require.ErrorContains(t, "Failed to retrieve default fee recipient from beacon node", err)
}
func TestServer_ListFeeRecipientByPubKey_NoFeeRecipientSet(t *testing.T) {
ctx := context.Background()
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")
require.NoError(t, err)
ctrl := gomock.NewController(t)
mockValidatorClient := validatormock.NewMockValidatorClient(ctrl)
mockValidatorClient.EXPECT().GetFeeRecipientByPubKey(gomock.Any(), gomock.Any()).Return(nil, nil)
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: &mock.Validator{},
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: mockValidatorClient,
}
_, err = s.ListFeeRecipientByPubkey(ctx, &ethpbservice.PubkeyRequest{Pubkey: byteval})
require.ErrorContains(t, "No fee recipient set", err)
}
func TestServer_ListFeeRecipientByPubkey_ValidatorServiceNil(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
s := &Server{}
_, err := s.ListFeeRecipientByPubkey(ctx, nil)
require.ErrorContains(t, "Validator service not ready", err)
}
func TestServer_ListFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
s := &Server{
validatorService: &client.ValidatorService{},
}
req := &ethpbservice.PubkeyRequest{
Pubkey: []byte{},
}
_, err := s.ListFeeRecipientByPubkey(ctx, req)
require.ErrorContains(t, "not a valid bls public key", err)
}
func TestServer_FeeRecipientByPubkey(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
beaconClient := validatormock.NewMockValidatorClient(ctrl)
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")
require.NoError(t, err)
type want struct {
valEthAddress string
defaultEthaddress string
}
type beaconResp struct {
resp *eth.FeeRecipientByPubKeyResponse
error error
}
tests := []struct {
name string
args string
proposerSettings *validatorserviceconfig.ProposerSettings
want *want
wantErr bool
beaconReturn *beaconResp
}{
{
name: "ProposerSetting is nil",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: nil,
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: nil,
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil AND ProposerSetting.Defaultconfig is defined",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: nil,
DefaultConfig: &validatorserviceconfig.ProposerOption{},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is defined for pubkey",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): {},
},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig not defined for pubkey",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil for pubkey",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): nil,
},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
{
name: "ProposerSetting.ProposeConfig is nil for pubkey AND DefaultConfig is not nil",
args: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): nil,
},
DefaultConfig: &validatorserviceconfig.ProposerOption{},
},
want: &want{
valEthAddress: "0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9",
},
wantErr: false,
beaconReturn: &beaconResp{
resp: nil,
error: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &mock.Validator{}
err := m.SetProposerSettings(ctx, tt.proposerSettings)
require.NoError(t, err)
validatorDB := dbtest.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{})
// save a default here
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
ValDB: validatorDB,
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: beaconClient,
valDB: validatorDB,
}
_, err = s.SetFeeRecipientByPubkey(ctx, &ethpbservice.SetFeeRecipientByPubkeyRequest{Pubkey: byteval, Ethaddress: common.HexToAddress(tt.args).Bytes()})
require.NoError(t, err)
assert.Equal(t, tt.want.valEthAddress, s.validatorService.ProposerSettings().ProposeConfig[bytesutil.ToBytes48(byteval)].FeeRecipientConfig.FeeRecipient.Hex())
})
}
}
func TestServer_SetFeeRecipientByPubkey_ValidatorServiceNil(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
s := &Server{}
_, err := s.SetFeeRecipientByPubkey(ctx, nil)
require.ErrorContains(t, "Validator service not ready", err)
}
func TestServer_SetFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
s := &Server{
validatorService: &client.ValidatorService{},
}
req := &ethpbservice.SetFeeRecipientByPubkeyRequest{
Pubkey: []byte{},
}
_, err := s.SetFeeRecipientByPubkey(ctx, req)
require.ErrorContains(t, "not a valid bls public key", err)
}
func TestServer_SetGasLimit_InvalidFeeRecipient(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")
require.NoError(t, err)
s := &Server{
validatorService: &client.ValidatorService{},
}
req := &ethpbservice.SetFeeRecipientByPubkeyRequest{
Pubkey: byteval,
}
_, err = s.SetFeeRecipientByPubkey(ctx, req)
require.ErrorContains(t, "Fee recipient is not a valid Ethereum address", err)
}
func TestServer_DeleteFeeRecipientByPubkey(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")
require.NoError(t, err)
type want struct {
EthAddress string
}
tests := []struct {
name string
proposerSettings *validatorserviceconfig.ProposerSettings
want *want
wantErr bool
}{
{
name: "Happy Path Test",
proposerSettings: &validatorserviceconfig.ProposerSettings{
ProposeConfig: map[[48]byte]*validatorserviceconfig.ProposerOption{
bytesutil.ToBytes48(byteval): {
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x055Fb65722E7b2455012BFEBf6177F1D2e9738D5"),
},
},
},
DefaultConfig: &validatorserviceconfig.ProposerOption{
FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{
FeeRecipient: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9"),
},
},
},
want: &want{
EthAddress: common.HexToAddress("0x046Fb65722E7b2455012BFEBf6177F1D2e9738D9").Hex(),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &mock.Validator{}
err := m.SetProposerSettings(ctx, tt.proposerSettings)
require.NoError(t, err)
validatorDB := dbtest.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{})
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
ValDB: validatorDB,
})
require.NoError(t, err)
s := &Server{
validatorService: vs,
valDB: validatorDB,
}
_, err = s.DeleteFeeRecipientByPubkey(ctx, &ethpbservice.PubkeyRequest{Pubkey: byteval})
require.NoError(t, err)
assert.Equal(t, true, s.validatorService.ProposerSettings().ProposeConfig[bytesutil.ToBytes48(byteval)].FeeRecipientConfig == nil)
})
}
}
func TestServer_DeleteFeeRecipientByPubkey_ValidatorServiceNil(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
s := &Server{}
_, err := s.DeleteFeeRecipientByPubkey(ctx, nil)
require.ErrorContains(t, "Validator service not ready", err)
}
func TestServer_DeleteFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
s := &Server{
validatorService: &client.ValidatorService{},
}
req := &ethpbservice.PubkeyRequest{
Pubkey: []byte{},
}
_, err := s.DeleteFeeRecipientByPubkey(ctx, req)
require.ErrorContains(t, "not a valid bls public key", err)
}

View File

@@ -44,3 +44,17 @@ type DeleteRemoteKeysRequest struct {
type RemoteKeysResponse struct {
Data []*keymanager.KeyStatus `json:"data"`
}
// Fee Recipient keymanager api
type FeeRecipient struct {
Pubkey string `json:"pubkey"`
Ethaddress string `json:"ethaddress"`
}
type GetFeeRecipientByPubkeyResponse struct {
Data *FeeRecipient `json:"data"`
}
type SetFeeRecipientByPubkeyRequest struct {
Ethaddress string `json:"ethaddress"`
}