From 7f931bf65b72353ef101d7600973d2010e683b1d Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:03:08 -0500 Subject: [PATCH] Keymanager APIs - get,post,delete graffiti (#13474) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * adding set and delete graffiti * fixing mock * fixing mock linting and putting in scaffolds for unit tests * adding some tests * gaz * adding tests * updating missing unit test * fixing unit test * Update validator/rpc/handlers_keymanager.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> * Update validator/client/propose.go Co-authored-by: Radosław Kapka * Update validator/rpc/handlers_keymanager.go Co-authored-by: Radosław Kapka * Update validator/client/propose.go Co-authored-by: Radosław Kapka * radek's feedback * fixing tests * using wrapper for graffiti * fixing linting * wip * fixing setting proposer settings * more partial fixes to tests * gaz * fixing tests and setting logic * changing keymanager * fixing tests and making graffiti optional in the proposer file * remove unneeded lines * reverting unintended changes * Update validator/client/propose.go Co-authored-by: Manu NALEPA * addressing feedback * removing uneeded line * fixing bad merge resolution * gofmt * gaz --------- Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Co-authored-by: Radosław Kapka Co-authored-by: Manu NALEPA --- config/proposer/loader/loader_test.go | 78 +++++++ .../testdata/good-graffiti-settings.json | 19 ++ config/proposer/settings.go | 37 +++- config/proposer/settings_test.go | 14 +- .../v1alpha1/validator-client/BUILD.bazel | 3 + .../validator-client/keymanager.pb.go | 107 +++++---- .../validator-client/keymanager.proto | 2 + validator/accounts/testing/BUILD.bazel | 1 + validator/accounts/testing/mock.go | 19 ++ validator/client/iface/validator.go | 5 +- validator/client/propose.go | 70 +++++- validator/client/propose_test.go | 206 +++++++++++++++++- validator/client/service.go | 21 ++ validator/client/testutil/mock_validator.go | 19 ++ validator/rpc/handlers_keymanager.go | 84 +++++++ validator/rpc/handlers_keymanager_test.go | 45 ++++ validator/rpc/server.go | 4 + validator/rpc/server_test.go | 1 + validator/rpc/structs.go | 10 + 19 files changed, 666 insertions(+), 79 deletions(-) create mode 100644 config/proposer/loader/testdata/good-graffiti-settings.json diff --git a/config/proposer/loader/loader_test.go b/config/proposer/loader/loader_test.go index 3a0278c56a47..aaa0592f15b1 100644 --- a/config/proposer/loader/loader_test.go +++ b/config/proposer/loader/loader_test.go @@ -49,6 +49,82 @@ func TestProposerSettingsLoader(t *testing.T) { validatorRegistrationEnabled bool skipDBSavedCheck bool }{ + { + name: "graffiti in db without fee recipient", + args: args{ + proposerSettingsFlagValues: &proposerSettingsFlag{ + dir: "", + url: "", + defaultfee: "", + }, + }, + want: func() *proposer.Settings { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + return &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + }, + }, + } + }, + withdb: func(db iface.ValidatorDB) error { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + settings := &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + }, + }, + } + return db.SaveProposerSettings(context.Background(), settings) + }, + }, + { + name: "graffiti from file", + args: args{ + proposerSettingsFlagValues: &proposerSettingsFlag{ + dir: "./testdata/good-graffiti-settings.json", + url: "", + defaultfee: "", + }, + }, + want: func() *proposer.Settings { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + return &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), + }, + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "some graffiti", + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: validator.Uint64(30000000), + }, + }, + }, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: validator.Uint64(40000000), + }, + }, + } + }, + }, { name: "db settings override file settings if file default config is missing", args: args{ @@ -875,6 +951,8 @@ func TestProposerSettingsLoader(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, tt.wantErr, err) return + } else { + require.NoError(t, err) } if tt.wantLog != "" { assert.LogsContain(t, hook, diff --git a/config/proposer/loader/testdata/good-graffiti-settings.json b/config/proposer/loader/testdata/good-graffiti-settings.json new file mode 100644 index 000000000000..bd00f1d18c7c --- /dev/null +++ b/config/proposer/loader/testdata/good-graffiti-settings.json @@ -0,0 +1,19 @@ +{ + "proposer_config": { + "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": { + "fee_recipient": "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3", + "graffiti": "some graffiti", + "builder": { + "enabled": true, + "gas_limit": "30000000" + } + } + }, + "default_config": { + "fee_recipient": "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", + "builder": { + "enabled": true, + "gas_limit": 40000000 + } + } +} \ No newline at end of file diff --git a/config/proposer/settings.go b/config/proposer/settings.go index 205667255505..88338eca7dbe 100644 --- a/config/proposer/settings.go +++ b/config/proposer/settings.go @@ -19,9 +19,6 @@ func SettingFromConsensus(ps *validatorpb.ProposerSettingsPayload) (*Settings, e if ps.ProposerConfig != nil && len(ps.ProposerConfig) != 0 { settings.ProposeConfig = make(map[[fieldparams.BLSPubkeyLength]byte]*Option) for key, optionPayload := range ps.ProposerConfig { - if optionPayload.FeeRecipient == "" { - continue - } decodedKey, err := hexutil.Decode(key) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("cannot decode public key %s", key)) @@ -29,13 +26,15 @@ func SettingFromConsensus(ps *validatorpb.ProposerSettingsPayload) (*Settings, e if len(decodedKey) != fieldparams.BLSPubkeyLength { return nil, fmt.Errorf("%v is not a bls public key", key) } - if err := verifyOption(key, optionPayload); err != nil { - return nil, err + p := &Option{} + if optionPayload.Graffiti != nil { + p.GraffitiConfig = &GraffitiConfig{*optionPayload.Graffiti} } - p := &Option{ - FeeRecipientConfig: &FeeRecipientConfig{ - FeeRecipient: common.HexToAddress(optionPayload.FeeRecipient), - }, + if optionPayload.FeeRecipient != "" { + if err := verifyOption(key, optionPayload); err != nil { + return nil, err + } + p.FeeRecipientConfig = &FeeRecipientConfig{FeeRecipient: common.HexToAddress(optionPayload.FeeRecipient)} } if optionPayload.Builder != nil { p.BuilderConfig = BuilderConfigFromConsensus(optionPayload.Builder) @@ -141,10 +140,16 @@ type FeeRecipientConfig struct { FeeRecipient common.Address } +// GraffitiConfig is a prysm internal representation to see if the graffiti was set. +type GraffitiConfig struct { + Graffiti string +} + // Option is a Prysm internal representation of the ProposerOptionPayload on the validator client in bytes format instead of hex. type Option struct { FeeRecipientConfig *FeeRecipientConfig BuilderConfig *BuilderConfig + GraffitiConfig *GraffitiConfig } // Clone creates a deep copy of proposer option @@ -159,6 +164,9 @@ func (po *Option) Clone() *Option { if po.BuilderConfig != nil { p.BuilderConfig = po.BuilderConfig.Clone() } + if po.GraffitiConfig != nil { + p.GraffitiConfig = po.GraffitiConfig.Clone() + } return p } @@ -173,6 +181,9 @@ func (po *Option) ToConsensus() *validatorpb.ProposerOptionPayload { if po.BuilderConfig != nil { p.Builder = po.BuilderConfig.ToConsensus() } + if po.GraffitiConfig != nil { + p.Graffiti = &po.GraffitiConfig.Graffiti + } return p } @@ -222,6 +233,14 @@ func (bc *BuilderConfig) Clone() *BuilderConfig { return c } +// Clone creates a deep copy of graffiti config +func (gc *GraffitiConfig) Clone() *GraffitiConfig { + if gc == nil { + return nil + } + return &GraffitiConfig{gc.Graffiti} +} + // ToConsensus converts Builder Config to the protobuf object func (bc *BuilderConfig) ToConsensus() *validatorpb.BuilderConfig { if bc == nil { diff --git a/config/proposer/settings_test.go b/config/proposer/settings_test.go index c550bbd2c349..0a5853a72a91 100644 --- a/config/proposer/settings_test.go +++ b/config/proposer/settings_test.go @@ -76,26 +76,14 @@ func Test_Proposer_Setting_Cloning(t *testing.T) { require.Equal(t, option.FeeRecipientConfig.FeeRecipient.Hex(), potion.FeeRecipient) require.Equal(t, settings.DefaultConfig.FeeRecipientConfig.FeeRecipient.Hex(), payload.DefaultConfig.FeeRecipient) require.Equal(t, settings.DefaultConfig.BuilderConfig.Enabled, payload.DefaultConfig.Builder.Enabled) - potion.FeeRecipient = "" + potion.FeeRecipient = fee newSettings, err := SettingFromConsensus(payload) require.NoError(t, err) - - // when converting to settings if a fee recipient is empty string then it will be skipped noption, ok := newSettings.ProposeConfig[bytesutil.ToBytes48(key1)] - require.Equal(t, false, ok) - require.Equal(t, true, noption == nil) - require.DeepEqual(t, newSettings.DefaultConfig, settings.DefaultConfig) - - // if fee recipient is set it will not skip - potion.FeeRecipient = fee - newSettings, err = SettingFromConsensus(payload) - require.NoError(t, err) - noption, ok = newSettings.ProposeConfig[bytesutil.ToBytes48(key1)] require.Equal(t, true, ok) require.Equal(t, option.FeeRecipientConfig.FeeRecipient.Hex(), noption.FeeRecipientConfig.FeeRecipient.Hex()) require.Equal(t, option.BuilderConfig.GasLimit, option.BuilderConfig.GasLimit) require.Equal(t, option.BuilderConfig.Enabled, option.BuilderConfig.Enabled) - }) } diff --git a/proto/prysm/v1alpha1/validator-client/BUILD.bazel b/proto/prysm/v1alpha1/validator-client/BUILD.bazel index 229a7f11238b..bd4607d598bc 100644 --- a/proto/prysm/v1alpha1/validator-client/BUILD.bazel +++ b/proto/prysm/v1alpha1/validator-client/BUILD.bazel @@ -29,6 +29,7 @@ proto_library( "@com_google_protobuf//:any_proto", "@com_google_protobuf//:descriptor_proto", "@com_google_protobuf//:empty_proto", + "@com_google_protobuf//:wrappers_proto", "@com_google_protobuf//:timestamp_proto", "@googleapis//google/api:annotations_proto", ], @@ -53,6 +54,7 @@ go_proto_library( "@googleapis//google/api:annotations_go_proto", "@io_bazel_rules_go//proto/wkt:descriptor_go_proto", "@io_bazel_rules_go//proto/wkt:empty_go_proto", + "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", "@io_bazel_rules_go//proto/wkt:timestamp_go_proto", "@org_golang_google_protobuf//reflect/protoreflect:go_default_library", "@org_golang_google_protobuf//runtime/protoimpl:go_default_library", @@ -78,6 +80,7 @@ go_proto_library( "@googleapis//google/api:annotations_go_proto", "@io_bazel_rules_go//proto/wkt:descriptor_go_proto", "@io_bazel_rules_go//proto/wkt:empty_go_proto", + "@io_bazel_rules_go//proto/wkt:wrappers_go_proto", "@io_bazel_rules_go//proto/wkt:timestamp_go_proto", ], ) diff --git a/proto/prysm/v1alpha1/validator-client/keymanager.pb.go b/proto/prysm/v1alpha1/validator-client/keymanager.pb.go index 2d6e53f90692..267ca72f5ab7 100755 --- a/proto/prysm/v1alpha1/validator-client/keymanager.pb.go +++ b/proto/prysm/v1alpha1/validator-client/keymanager.pb.go @@ -16,6 +16,7 @@ import ( v1alpha1 "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/wrapperspb" ) const ( @@ -462,6 +463,7 @@ type ProposerOptionPayload struct { FeeRecipient string `protobuf:"bytes,1,opt,name=fee_recipient,json=feeRecipient,proto3" json:"fee_recipient,omitempty"` Builder *BuilderConfig `protobuf:"bytes,2,opt,name=builder,proto3" json:"builder,omitempty"` + Graffiti *string `protobuf:"bytes,3,opt,name=graffiti,proto3,oneof" json:"graffiti,omitempty"` } func (x *ProposerOptionPayload) Reset() { @@ -510,6 +512,13 @@ func (x *ProposerOptionPayload) GetBuilder() *BuilderConfig { return nil } +func (x *ProposerOptionPayload) GetGraffiti() string { + if x != nil && x.Graffiti != nil { + return *x.Graffiti + } + return "" +} + type BuilderConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -636,7 +645,9 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x6b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, + 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x65, 0x78, 0x74, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x61, 0x74, 0x74, 0x65, @@ -771,7 +782,7 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, - 0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0x85, 0x01, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, + 0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0xb3, 0x01, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x65, 0x52, 0x65, 0x63, 0x69, 0x70, @@ -779,54 +790,57 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x22, 0xa6, 0x01, - 0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x09, 0x67, 0x61, 0x73, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, - 0x18, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, - 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, - 0x2f, 0x76, 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x69, - 0x6e, 0x74, 0x36, 0x34, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x70, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, + 0x08, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x08, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x88, 0x01, 0x01, 0x42, 0x0b, + 0x0a, 0x09, 0x5f, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x22, 0xa6, 0x01, 0x0a, 0x0d, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, 0x18, 0x42, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, + 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, + 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x69, 0x6e, 0x74, + 0x36, 0x34, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x6c, 0x61, 0x79, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x12, 0x74, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x12, 0x74, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x74, - 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, - 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, - 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5c, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, - 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x78, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x4b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, + 0x61, 0x64, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5c, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, - 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x42, 0xce, 0x01, 0x0a, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x42, 0x0f, 0x4b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x53, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, - 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2d, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x3b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x70, 0x62, 0xaa, - 0x02, 0x1e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x56, 0x32, - 0xca, 0x02, 0x1e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5c, 0x56, - 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x78, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x4b, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, + 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0xce, + 0x01, 0x0a, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x2e, 0x76, 0x32, 0x42, 0x0f, 0x4b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x53, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, + 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x3b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x70, 0x62, 0xaa, 0x02, 0x1e, + 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x56, 0x32, 0xca, 0x02, + 0x1e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x6f, 0x72, 0x5c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5c, 0x56, 0x32, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -979,6 +993,7 @@ func file_proto_prysm_v1alpha1_validator_client_keymanager_proto_init() { (*SignRequest_BlockDeneb)(nil), (*SignRequest_BlindedBlockDeneb)(nil), } + file_proto_prysm_v1alpha1_validator_client_keymanager_proto_msgTypes[2].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/proto/prysm/v1alpha1/validator-client/keymanager.proto b/proto/prysm/v1alpha1/validator-client/keymanager.proto index 3330c01ff7f8..85500ceb840b 100644 --- a/proto/prysm/v1alpha1/validator-client/keymanager.proto +++ b/proto/prysm/v1alpha1/validator-client/keymanager.proto @@ -1,6 +1,7 @@ syntax = "proto3"; package ethereum.validator.accounts.v2; +import "google/protobuf/wrappers.proto"; import "proto/eth/ext/options.proto"; import "proto/prysm/v1alpha1/attestation.proto"; import "proto/prysm/v1alpha1/beacon_block.proto"; @@ -87,6 +88,7 @@ message SignResponse { message ProposerOptionPayload { string fee_recipient = 1; BuilderConfig builder = 2; + optional string graffiti = 3; } // BuilderConfig is a property of ProposerOptionPayload diff --git a/validator/accounts/testing/BUILD.bazel b/validator/accounts/testing/BUILD.bazel index e824b32e2d18..628f051191c8 100644 --- a/validator/accounts/testing/BUILD.bazel +++ b/validator/accounts/testing/BUILD.bazel @@ -12,6 +12,7 @@ go_library( deps = [ "//api/client/beacon:go_default_library", "//api/client/event:go_default_library", + "//config/fieldparams:go_default_library", "//config/proposer:go_default_library", "//consensus-types/primitives:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/validator/accounts/testing/mock.go b/validator/accounts/testing/mock.go index f520a19098a5..3d5fccd86f74 100644 --- a/validator/accounts/testing/mock.go +++ b/validator/accounts/testing/mock.go @@ -9,6 +9,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/api/client/beacon" "github.com/prysmaticlabs/prysm/v5/api/client/event" + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/proposer" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -91,6 +92,7 @@ func (_ *Wallet) InitializeKeymanager(_ context.Context, _ iface.InitKeymanagerC type Validator struct { Km keymanager.IKeymanager + graffiti string proposerSettings *proposer.Settings } @@ -215,6 +217,23 @@ func (m *Validator) SetProposerSettings(_ context.Context, settings *proposer.Se return nil } +// GetGraffiti for mocking +func (m *Validator) GetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) ([]byte, error) { + return []byte(m.graffiti), nil +} + +// SetGraffiti for mocking +func (m *Validator) SetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error { + m.graffiti = string(graffiti) + return nil +} + +// DeleteGraffiti for mocking +func (m *Validator) DeleteGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) error { + m.graffiti = "" + return nil +} + func (*Validator) StartEventStream(_ context.Context, _ []string, _ chan<- *event.Event) { panic("implement me") } diff --git a/validator/client/iface/validator.go b/validator/client/iface/validator.go index 761628dbb53e..3163b8dd8a6a 100644 --- a/validator/client/iface/validator.go +++ b/validator/client/iface/validator.go @@ -60,10 +60,13 @@ type Validator interface { PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot, deadline time.Time) error SignValidatorRegistrationRequest(ctx context.Context, signer SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error) StartEventStream(ctx context.Context, topics []string, eventsChan chan<- *event.Event) + EventStreamIsRunning() bool ProcessEvent(event *event.Event) ProposerSettings() *proposer.Settings SetProposerSettings(context.Context, *proposer.Settings) error - EventStreamIsRunning() bool + GetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) + SetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error + DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error HealthTracker() *beacon.NodeHealthTracker } diff --git a/validator/client/propose.go b/validator/client/propose.go index a8d54e0976d4..12779137f0ad 100644 --- a/validator/client/propose.go +++ b/validator/client/propose.go @@ -6,12 +6,14 @@ import ( "fmt" "time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/protobuf/ptypes/timestamp" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/async" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/config/proposer" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" @@ -67,7 +69,7 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK return } - g, err := v.getGraffiti(ctx, pubKey) + g, err := v.GetGraffiti(ctx, pubKey) if err != nil { // Graffiti is not a critical enough to fail block production and cause // validator to miss block reward. When failed, validator should continue @@ -385,9 +387,25 @@ func signVoluntaryExit( return sig.Marshal(), nil } -// Gets the graffiti from cli or file for the validator public key. -func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) { - // When specified, default graffiti from the command line takes the first priority. +// GetGraffiti gets the graffiti from cli or file for the validator public key. +func (v *validator) GetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) { + if v.proposerSettings != nil { + // Check proposer settings for specific key first + if v.proposerSettings.ProposeConfig != nil { + option, ok := v.proposerSettings.ProposeConfig[pubKey] + if ok && option.GraffitiConfig != nil { + return []byte(option.GraffitiConfig.Graffiti), nil + } + } + // Check proposer settings for default settings second + if v.proposerSettings.DefaultConfig != nil { + if v.proposerSettings.DefaultConfig.GraffitiConfig != nil { + return []byte(v.proposerSettings.DefaultConfig.GraffitiConfig.Graffiti), nil + } + } + } + + // When specified, use default graffiti from the command line. if len(v.graffiti) != 0 { return bytesutil.PadTo(v.graffiti, 32), nil } @@ -396,7 +414,7 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk return nil, errors.New("graffitiStruct can't be nil") } - // When specified, individual validator specified graffiti takes the second priority. + // When specified, individual validator specified graffiti takes the third priority. idx, err := v.validatorClient.ValidatorIndex(ctx, ðpb.ValidatorIndexRequest{PublicKey: pubKey[:]}) if err != nil { return nil, err @@ -406,7 +424,7 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk return bytesutil.PadTo([]byte(g), 32), nil } - // When specified, a graffiti from the ordered list in the file take third priority. + // When specified, a graffiti from the ordered list in the file take fourth priority. if v.graffitiOrderedIndex < uint64(len(v.graffitiStruct.Ordered)) { graffiti := v.graffitiStruct.Ordered[v.graffitiOrderedIndex] v.graffitiOrderedIndex = v.graffitiOrderedIndex + 1 @@ -417,7 +435,7 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk return bytesutil.PadTo([]byte(graffiti), 32), nil } - // When specified, a graffiti from the random list in the file take fourth priority. + // When specified, a graffiti from the random list in the file take Fifth priority. if len(v.graffitiStruct.Random) != 0 { r := rand.NewGenerator() r.Seed(time.Now().Unix()) @@ -433,6 +451,44 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk return []byte{}, nil } +func (v *validator) SetGraffiti(ctx context.Context, pubkey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error { + if graffiti == nil { + return nil + } + settings := &proposer.Settings{} + if v.proposerSettings != nil { + settings = v.proposerSettings.Clone() + } + if settings.ProposeConfig == nil { + settings.ProposeConfig = map[[48]byte]*proposer.Option{pubkey: {GraffitiConfig: &proposer.GraffitiConfig{Graffiti: string(graffiti)}}} + return v.SetProposerSettings(ctx, settings) + } + option, ok := settings.ProposeConfig[pubkey] + if !ok || option == nil { + settings.ProposeConfig[pubkey] = &proposer.Option{GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: string(graffiti), + }} + } else { + option.GraffitiConfig = &proposer.GraffitiConfig{ + Graffiti: string(graffiti), + } + } + return v.SetProposerSettings(ctx, settings) // save the proposer settings +} + +func (v *validator) DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error { + if v.proposerSettings == nil || v.proposerSettings.ProposeConfig == nil { + return errors.New("attempted to delete graffiti without proposer settings, graffiti will default to flag options") + } + ps := v.proposerSettings.Clone() + option, ok := ps.ProposeConfig[pubKey] + if !ok || option == nil { + return fmt.Errorf("graffiti not found in proposer settings for pubkey:%s", hexutil.Encode(pubKey[:])) + } + option.GraffitiConfig = nil + return v.SetProposerSettings(ctx, ps) // save the proposer settings +} + func blockLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, blk interfaces.ReadOnlyBeaconBlock, sig []byte) logrus.Fields { fields := logrus.Fields{ "proposerPublicKey": fmt.Sprintf("%#x", pubKey), diff --git a/validator/client/propose_test.go b/validator/client/propose_test.go index 96e1c50fdce1..463c1b1ccd18 100644 --- a/validator/client/propose_test.go +++ b/validator/client/propose_test.go @@ -8,10 +8,12 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" lruwrpr "github.com/prysmaticlabs/prysm/v5/cache/lru" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/config/proposer" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" blocktest "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks/testing" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" @@ -955,6 +957,13 @@ func TestGetGraffiti_Ok(t *testing.T) { validatorClient: validatormock.NewMockValidatorClient(ctrl), } pubKey := [fieldparams.BLSPubkeyLength]byte{'a'} + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + config[pubKey] = &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + } + tests := []struct { name string v *validator @@ -1014,16 +1023,52 @@ func TestGetGraffiti_Ok(t *testing.T) { }, want: []byte{}, }, + {name: "graffiti from proposer settings for specific pubkey", + v: &validator{ + validatorClient: m.validatorClient, + proposerSettings: &proposer.Settings{ + ProposeConfig: config, + }, + }, + want: []byte("specific graffiti"), + }, + {name: "graffiti from proposer settings default config", + v: &validator{ + validatorClient: m.validatorClient, + proposerSettings: &proposer.Settings{ + DefaultConfig: &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "default graffiti", + }, + }, + }, + }, + want: []byte("default graffiti"), + }, + {name: "graffiti from proposer settings , specific pubkey overrides default config", + v: &validator{ + validatorClient: m.validatorClient, + proposerSettings: &proposer.Settings{ + ProposeConfig: config, + DefaultConfig: &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "default graffiti", + }, + }, + }, + }, + want: []byte("specific graffiti"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if !strings.Contains(tt.name, "use default cli graffiti") { + if !strings.Contains(tt.name, "use default cli graffiti") && tt.v.proposerSettings == nil { m.validatorClient.EXPECT(). ValidatorIndex(gomock.Any(), ðpb.ValidatorIndexRequest{PublicKey: pubKey[:]}). Return(ðpb.ValidatorIndexResponse{Index: 2}, nil) } - got, err := tt.v.getGraffiti(context.Background(), pubKey) + got, err := tt.v.GetGraffiti(context.Background(), pubKey) require.NoError(t, err) require.DeepEqual(t, tt.want, got) }) @@ -1053,10 +1098,165 @@ func TestGetGraffitiOrdered_Ok(t *testing.T) { }, } for _, want := range [][]byte{bytesutil.PadTo([]byte{'a'}, 32), bytesutil.PadTo([]byte{'b'}, 32), bytesutil.PadTo([]byte{'c'}, 32), bytesutil.PadTo([]byte{'d'}, 32), bytesutil.PadTo([]byte{'d'}, 32)} { - got, err := v.getGraffiti(context.Background(), pubKey) + got, err := v.GetGraffiti(context.Background(), pubKey) require.NoError(t, err) require.DeepEqual(t, want, got) } }) } } + +func Test_validator_DeleteGraffiti(t *testing.T) { + pubKey := [fieldparams.BLSPubkeyLength]byte{'a'} + tests := []struct { + name string + proposerSettings *proposer.Settings + wantErr string + }{ + { + name: "delete existing graffiti ok", + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + config[pubKey] = &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + } + return config + }(), + }, + }, + { + name: "delete with proposer settings but only default configs", + proposerSettings: &proposer.Settings{ + DefaultConfig: &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "default graffiti", + }, + }, + }, + wantErr: "attempted to delete graffiti without proposer settings, graffiti will default to flag options", + }, + { + name: "delete with proposer settings but without the specific public key setting", + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + pk := make([]byte, fieldparams.BLSPubkeyLength) + config[bytesutil.ToBytes48(pk)] = &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + } + return config + }(), + }, + wantErr: fmt.Sprintf("graffiti not found in proposer settings for pubkey:%s", hexutil.Encode(pubKey[:])), + }, + { + name: "delete without proposer settings", + wantErr: "attempted to delete graffiti without proposer settings, graffiti will default to flag options", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &validator{ + db: testing2.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{pubKey}, false), + proposerSettings: tt.proposerSettings, + } + err := v.DeleteGraffiti(context.Background(), pubKey) + if tt.wantErr != "" { + require.ErrorContains(t, tt.wantErr, err) + } else { + require.NoError(t, err) + require.Equal(t, v.proposerSettings.ProposeConfig[pubKey].GraffitiConfig == nil, true) + } + }) + } +} + +func Test_validator_SetGraffiti(t *testing.T) { + pubKey := [fieldparams.BLSPubkeyLength]byte{'a'} + tests := []struct { + name string + graffiti string + proposerSettings *proposer.Settings + wantProposerSettings *proposer.Settings + wantErr string + }{ + { + name: "setting existing graffiti ok", + graffiti: "new graffiti", + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + config[pubKey] = &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + } + return config + }(), + }, + }, + { + name: "set with proposer settings but only default configs", + proposerSettings: &proposer.Settings{ + DefaultConfig: &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "default graffiti", + }, + }, + }, + }, + { + name: "set with proposer settings but without the specific public key setting", + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + pk := make([]byte, fieldparams.BLSPubkeyLength) + config[bytesutil.ToBytes48(pk)] = &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + } + return config + }(), + }, + }, + { + name: "set without proposer settings", + graffiti: "specific graffiti", + wantProposerSettings: func() *proposer.Settings { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + config[pubKey] = &proposer.Option{ + GraffitiConfig: &proposer.GraffitiConfig{ + Graffiti: "specific graffiti", + }, + } + return &proposer.Settings{ProposeConfig: config} + }(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &validator{ + db: testing2.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{pubKey}, false), + proposerSettings: tt.proposerSettings, + } + err := v.SetGraffiti(context.Background(), pubKey, []byte(tt.graffiti)) + if tt.wantErr != "" { + require.ErrorContains(t, tt.wantErr, err) + } else { + require.NoError(t, err) + if tt.wantProposerSettings != nil { + require.DeepEqual(t, tt.wantProposerSettings, v.proposerSettings) + } else { + require.Equal(t, v.proposerSettings.ProposeConfig[pubKey].GraffitiConfig.Graffiti, tt.graffiti) + } + + } + }) + } +} diff --git a/validator/client/service.go b/validator/client/service.go index abc69949a24b..a12ac3068f0a 100644 --- a/validator/client/service.go +++ b/validator/client/service.go @@ -358,3 +358,24 @@ func (v *ValidatorService) GenesisInfo(ctx context.Context) (*ethpb.Genesis, err nc := ethpb.NewNodeClient(v.conn.GetGrpcClientConn()) return nc.GetGenesis(ctx, &emptypb.Empty{}) } + +func (v *ValidatorService) GetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) { + if v.validator == nil { + return nil, errors.New("validator is unavailable") + } + return v.validator.GetGraffiti(ctx, pubKey) +} + +func (v *ValidatorService) SetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error { + if v.validator == nil { + return errors.New("validator is unavailable") + } + return v.validator.SetGraffiti(ctx, pubKey, graffiti) +} + +func (v *ValidatorService) DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error { + if v.validator == nil { + return errors.New("validator is unavailable") + } + return v.validator.DeleteGraffiti(ctx, pubKey) +} diff --git a/validator/client/testutil/mock_validator.go b/validator/client/testutil/mock_validator.go index 25c90cc93310..47c22dde8921 100644 --- a/validator/client/testutil/mock_validator.go +++ b/validator/client/testutil/mock_validator.go @@ -58,6 +58,7 @@ type FakeValidator struct { proposerSettings *proposer.Settings ProposerSettingWait time.Duration Km keymanager.IKeymanager + graffiti string Tracker *beacon.NodeHealthTracker } @@ -282,7 +283,25 @@ func (fv *FakeValidator) SetProposerSettings(_ context.Context, settings *propos return nil } +// GetGraffiti for mocking +func (f *FakeValidator) GetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) ([]byte, error) { + return []byte(f.graffiti), nil +} + +// SetGraffiti for mocking +func (f *FakeValidator) SetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error { + f.graffiti = string(graffiti) + return nil +} + +// DeleteGraffiti for mocking +func (f *FakeValidator) DeleteGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) error { + f.graffiti = "" + return nil +} + func (*FakeValidator) StartEventStream(_ context.Context, _ []string, _ chan<- *event.Event) { + } func (*FakeValidator) ProcessEvent(_ *event.Event) {} diff --git a/validator/rpc/handlers_keymanager.go b/validator/rpc/handlers_keymanager.go index 72563e3a0f52..d5c1d1fa2b9d 100644 --- a/validator/rpc/handlers_keymanager.go +++ b/validator/rpc/handlers_keymanager.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -839,3 +840,86 @@ func (s *Server) DeleteGasLimit(w http.ResponseWriter, r *http.Request) { // we respond "not found". httputil.HandleError(w, fmt.Sprintf("No gas limit found for pubkey %q", rawPubkey), http.StatusNotFound) } + +func (s *Server) GetGraffiti(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.GetGraffiti") + defer span.End() + + if s.validatorService == nil { + httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable) + return + } + rawPubkey, pubkey, ok := shared.HexFromRoute(w, r, "pubkey", fieldparams.BLSPubkeyLength) + if !ok { + return + } + + graffiti, err := s.validatorService.GetGraffiti(ctx, bytesutil.ToBytes48(pubkey)) + if err != nil { + if strings.Contains(err.Error(), "unavailable") { + httputil.HandleError(w, err.Error(), http.StatusInternalServerError) + return + } + httputil.HandleError(w, err.Error(), http.StatusNotFound) + return + } + + httputil.WriteJson(w, &GetGraffitiResponse{ + Data: &GraffitiData{ + Pubkey: rawPubkey, + Graffiti: string(graffiti), + }, + }) +} + +func (s *Server) SetGraffiti(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.SetGraffiti") + defer span.End() + + if s.validatorService == nil { + httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable) + return + } + _, pubkey, ok := shared.HexFromRoute(w, r, "pubkey", fieldparams.BLSPubkeyLength) + if !ok { + return + } + + var req struct { + Graffiti string `json:"graffiti"` + } + 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 err := s.validatorService.SetGraffiti(ctx, bytesutil.ToBytes48(pubkey), []byte(req.Graffiti)); err != nil { + httputil.HandleError(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func (s *Server) DeleteGraffiti(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.DeleteGraffiti") + defer span.End() + + if s.validatorService == nil { + httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable) + return + } + + _, pubkey, ok := shared.HexFromRoute(w, r, "pubkey", fieldparams.BLSPubkeyLength) + if !ok { + return + } + + if err := s.validatorService.DeleteGraffiti(ctx, bytesutil.ToBytes48(pubkey)); err != nil { + httputil.HandleError(w, err.Error(), http.StatusNotFound) + return + } +} diff --git a/validator/rpc/handlers_keymanager_test.go b/validator/rpc/handlers_keymanager_test.go index f4d923a4da36..48e08ef92b73 100644 --- a/validator/rpc/handlers_keymanager_test.go +++ b/validator/rpc/handlers_keymanager_test.go @@ -1898,3 +1898,48 @@ func TestServer_DeleteFeeRecipientByPubkey_InvalidPubKey(t *testing.T) { require.StringContains(t, "pubkey is invalid", w.Body.String()) } + +func TestServer_Graffiti(t *testing.T) { + graffiti := "graffiti" + m := &mock.Validator{} + vs, err := client.NewValidatorService(context.Background(), &client.Config{ + Validator: m, + }) + require.NoError(t, err) + s := &Server{ + validatorService: vs, + } + + var request struct { + Graffiti string `json:"graffiti"` + } + request.Graffiti = graffiti + pubkey := "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493" + 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}/graffiti"), &buf) + req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey}) + w := httptest.NewRecorder() + w.Body = &bytes.Buffer{} + s.SetGraffiti(w, req) + require.Equal(t, http.StatusOK, w.Code) + + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/validator/{pubkey}/graffiti"), nil) + req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey}) + w = httptest.NewRecorder() + w.Body = &bytes.Buffer{} + s.GetGraffiti(w, req) + require.Equal(t, http.StatusOK, w.Code) + resp := &GetGraffitiResponse{} + require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp)) + assert.Equal(t, resp.Data.Graffiti, request.Graffiti) + assert.Equal(t, resp.Data.Pubkey, pubkey) + + req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/validator/{pubkey}/graffiti"), nil) + req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey}) + w = httptest.NewRecorder() + w.Body = &bytes.Buffer{} + s.DeleteGraffiti(w, req) + require.Equal(t, http.StatusOK, w.Code) +} diff --git a/validator/rpc/server.go b/validator/rpc/server.go index 0a6980d8255a..13dc6332e992 100644 --- a/validator/rpc/server.go +++ b/validator/rpc/server.go @@ -230,6 +230,10 @@ 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) + s.router.HandleFunc("/eth/v1/validator/{pubkey}/graffiti", s.GetGraffiti).Methods(http.MethodGet) + s.router.HandleFunc("/eth/v1/validator/{pubkey}/graffiti", s.SetGraffiti).Methods(http.MethodPost) + s.router.HandleFunc("/eth/v1/validator/{pubkey}/graffiti", s.DeleteGraffiti).Methods(http.MethodDelete) + // auth endpoint s.router.HandleFunc(api.WebUrlPrefix+"initialize", s.Initialize).Methods(http.MethodGet) // accounts endpoints diff --git a/validator/rpc/server_test.go b/validator/rpc/server_test.go index bb38f37aaf47..f285fc823068 100644 --- a/validator/rpc/server_test.go +++ b/validator/rpc/server_test.go @@ -21,6 +21,7 @@ func TestServer_InitializeRoutes(t *testing.T) { "/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}, + "/eth/v1/validator/{pubkey}/graffiti": {http.MethodGet, http.MethodPost, http.MethodDelete}, "/v2/validator/health/version": {http.MethodGet}, "/v2/validator/health/logs/validator/stream": {http.MethodGet}, "/v2/validator/health/logs/beacon/stream": {http.MethodGet}, diff --git a/validator/rpc/structs.go b/validator/rpc/structs.go index 43b60f93e9e4..f3e3830fdf2a 100644 --- a/validator/rpc/structs.go +++ b/validator/rpc/structs.go @@ -99,6 +99,16 @@ type SetFeeRecipientByPubkeyRequest struct { Ethaddress string `json:"ethaddress"` } +// Graffiti keymanager api +type GetGraffitiResponse struct { + Data *GraffitiData `json:"data"` +} + +type GraffitiData struct { + Pubkey string `json:"pubkey"` + Graffiti string `json:"graffiti"` +} + type BeaconStatusResponse struct { BeaconNodeEndpoint string `json:"beacon_node_endpoint"` Connected bool `json:"connected"`