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 3a0278c56..aaa0592f1 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 000000000..bd00f1d18 --- /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 205667255..88338eca7 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 c550bbd2c..0a5853a72 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 229a7f112..bd4607d59 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 2d6e53f90..267ca72f5 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 3330c01ff..85500ceb8 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 e824b32e2..628f05119 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 f520a1909..3d5fccd86 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 761628dbb..3163b8dd8 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 a8d54e097..12779137f 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 96e1c50fd..463c1b1cc 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 abc69949a..a12ac3068 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 25c90cc93..47c22dde8 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 72563e3a0..d5c1d1fa2 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 f4d923a4d..48e08ef92 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 0a6980d82..13dc6332e 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 bb38f37aa..f285fc823 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 43b60f93e..f3e3830fd 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"`