diff --git a/validator/client/validator.go b/validator/client/validator.go index 3800049e4ec3..0afc3105f7d7 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -1000,7 +1000,11 @@ func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKey log.Info("No imported public keys. Skipping prepare proposer routine") return nil } - proposerReqs, err := v.buildPrepProposerReqs(ctx, pubkeys) + filteredKeys, err := v.filterAndCacheActiveKeys(ctx, pubkeys) + if err != nil { + return err + } + proposerReqs, err := v.buildPrepProposerReqs(ctx, filteredKeys) if err != nil { return err } @@ -1020,7 +1024,7 @@ func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKey return err } - signedRegReqs, err := v.buildSignedRegReqs(ctx, pubkeys, km.Sign) + signedRegReqs, err := v.buildSignedRegReqs(ctx, filteredKeys, km.Sign) if err != nil { return err } @@ -1030,36 +1034,57 @@ func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKey return nil } - -func (v *validator) buildPrepProposerReqs(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer, error) { - var prepareProposerReqs []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer - +func (v *validator) filterAndCacheActiveKeys(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte) ([][fieldparams.BLSPubkeyLength]byte, error) { + filteredKeys := make([][fieldparams.BLSPubkeyLength]byte, 0) + statusRequestKeys := make([][]byte, 0) for _, k := range pubkeys { - // Default case: Define fee recipient to burn address - var feeRecipient common.Address - isFeeRecipientDefined := false - - // If fee recipient is defined in default configuration, use it - if v.ProposerSettings() != nil && v.ProposerSettings().DefaultConfig != nil && v.ProposerSettings().DefaultConfig.FeeRecipientConfig != nil { - feeRecipient = v.ProposerSettings().DefaultConfig.FeeRecipientConfig.FeeRecipient // Use cli config for fee recipient. - isFeeRecipientDefined = true - } - - validatorIndex, ok := v.pubkeyToValidatorIndex[k] + _, ok := v.pubkeyToValidatorIndex[k] // Get validator index from RPC server if not found. if !ok { i, ok, err := v.validatorIndex(ctx, k) if err != nil { return nil, err } - if !ok { // Nothing we can do if RPC server doesn't have validator index. continue } - - validatorIndex = i v.pubkeyToValidatorIndex[k] = i } + statusRequestKeys = append(statusRequestKeys, k[:]) + } + resp, err := v.validatorClient.MultipleValidatorStatus(ctx, ðpb.MultipleValidatorStatusRequest{ + PublicKeys: statusRequestKeys, + }) + if err != nil { + return nil, err + } + for i, status := range resp.Statuses { + // skip registration creation if validator is not active status + if status.Status != ethpb.ValidatorStatus_ACTIVE { + log.WithFields(logrus.Fields{ + "publickey": hexutil.Encode(resp.PublicKeys[i]), + "status": status.Status.String(), + }).Debugf("skipping non active status key.") + continue + } + filteredKeys = append(filteredKeys, bytesutil.ToBytes48(resp.PublicKeys[i])) + } + + return filteredKeys, nil +} + +func (v *validator) buildPrepProposerReqs(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte /* only active pubkeys */) ([]*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer, error) { + var prepareProposerReqs []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer + for _, k := range pubkeys { + // Default case: Define fee recipient to burn address + var feeRecipient common.Address + isFeeRecipientDefined := false + + // If fee recipient is defined in default configuration, use it + if v.ProposerSettings() != nil && v.ProposerSettings().DefaultConfig != nil && v.ProposerSettings().DefaultConfig.FeeRecipientConfig != nil { + feeRecipient = v.ProposerSettings().DefaultConfig.FeeRecipientConfig.FeeRecipient // Use cli config for fee recipient. + isFeeRecipientDefined = true + } // If fee recipient is defined for this specific pubkey in proposer configuration, use it if v.ProposerSettings() != nil && v.ProposerSettings().ProposeConfig != nil { @@ -1071,6 +1096,11 @@ func (v *validator) buildPrepProposerReqs(ctx context.Context, pubkeys [][fieldp } } + validatorIndex, ok := v.pubkeyToValidatorIndex[k] + if !ok { + continue + } + if isFeeRecipientDefined { prepareProposerReqs = append(prepareProposerReqs, ðpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ ValidatorIndex: validatorIndex, @@ -1085,11 +1115,10 @@ func (v *validator) buildPrepProposerReqs(ctx context.Context, pubkeys [][fieldp } } } - return prepareProposerReqs, nil } -func (v *validator) buildSignedRegReqs(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte, signer iface.SigningFunc) ([]*ethpb.SignedValidatorRegistrationV1, error) { +func (v *validator) buildSignedRegReqs(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte /* only active pubkeys */, signer iface.SigningFunc) ([]*ethpb.SignedValidatorRegistrationV1, error) { var signedValRegRegs []*ethpb.SignedValidatorRegistrationV1 for i, k := range pubkeys { @@ -1128,6 +1157,12 @@ func (v *validator) buildSignedRegReqs(ctx context.Context, pubkeys [][fieldpara continue } + // map is populated before this function in buildPrepProposerReq + _, ok := v.pubkeyToValidatorIndex[k] + if !ok { + continue + } + req := ðpb.ValidatorRegistrationV1{ FeeRecipient: feeRecipient[:], GasLimit: gasLimit, diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index b35b9c81bd37..08c86e22e3bd 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -1443,18 +1443,15 @@ func TestValidator_PushProposerSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[1][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 2, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToValidatorIndex[keys[1]] = primitives.ValidatorIndex(2) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:], keys[1][:]}, + }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ {FeeRecipient: common.HexToAddress("0x055Fb65722E7b2455043BFEBf6177F1D2e9738D9").Bytes(), ValidatorIndex: 1}, @@ -1527,18 +1524,15 @@ func TestValidator_PushProposerSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[1][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 2, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToValidatorIndex[keys[1]] = primitives.ValidatorIndex(2) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:], keys[1][:]}, + }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ {FeeRecipient: common.HexToAddress("0x055Fb65722E7b2455043BFEBf6177F1D2e9738D9").Bytes(), ValidatorIndex: 1}, @@ -1607,18 +1601,15 @@ func TestValidator_PushProposerSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[1][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 2, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + v.pubkeyToValidatorIndex[keys[1]] = primitives.ValidatorIndex(2) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:], keys[1][:]}, + }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ {FeeRecipient: common.HexToAddress("0x055Fb65722E7b2455043BFEBf6177F1D2e9738D9").Bytes(), ValidatorIndex: 1}, @@ -1684,12 +1675,14 @@ func TestValidator_PushProposerSettings(t *testing.T) { }, }, }) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:]}, + }, nil) client.EXPECT().SubmitValidatorRegistrations( gomock.Any(), @@ -1746,12 +1739,14 @@ func TestValidator_PushProposerSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - ctx, // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:]}, + }, nil) client.EXPECT().SubmitValidatorRegistrations( gomock.Any(), gomock.Any(), @@ -1796,12 +1791,14 @@ func TestValidator_PushProposerSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - gomock.Any(), // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:]}, + }, nil) client.EXPECT().PrepareBeaconProposer(gomock.Any(), ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ {FeeRecipient: common.HexToAddress("0x0").Bytes(), ValidatorIndex: 1}, @@ -1887,12 +1884,14 @@ func TestValidator_PushProposerSettings(t *testing.T) { require.NoError(t, err) keys, err := km.FetchValidatingPublicKeys(ctx) require.NoError(t, err) - client.EXPECT().ValidatorIndex( - gomock.Any(), // ctx - ðpb.ValidatorIndexRequest{PublicKey: keys[0][:]}, - ).Return(ðpb.ValidatorIndexResponse{ - Index: 1, - }, nil) + v.pubkeyToValidatorIndex[keys[0]] = primitives.ValidatorIndex(1) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{keys[0][:]}, + }, nil) config[keys[0]] = &validatorserviceconfig.ProposerOption{ FeeRecipientConfig: &validatorserviceconfig.FeeRecipientConfig{ @@ -1977,24 +1976,6 @@ func TestValidator_PushProposerSettings(t *testing.T) { } } -func TestValidator_buildPrepProposerReqs_InvalidValidatorIndex(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ctx := context.Background() - client := validatormock.NewMockValidatorClient(ctrl) - v := validator{validatorClient: client} - pubkeys := [][fieldparams.BLSPubkeyLength]byte{{}} - - client.EXPECT().ValidatorIndex( - ctx, - gomock.Any(), - ).Return(nil, errors.New("custom error")) - - _, err := v.buildPrepProposerReqs(ctx, pubkeys) - assert.ErrorContains(t, "custom error", err) -} - func getPubkeyFromString(t *testing.T, stringPubkey string) [fieldparams.BLSPubkeyLength]byte { pubkeyTemp, err := hexutil.Decode(stringPubkey) require.NoError(t, err) @@ -2037,7 +2018,6 @@ func TestValidator_buildPrepProposerReqs_WithoutDefaultConfig(t *testing.T) { ctx := context.Background() client := validatormock.NewMockValidatorClient(ctrl) - client.EXPECT().ValidatorIndex( ctx, ðpb.ValidatorIndexRequest{ @@ -2054,6 +2034,13 @@ func TestValidator_buildPrepProposerReqs_WithoutDefaultConfig(t *testing.T) { }, ).Return(nil, status.Error(codes.NotFound, "NOT_FOUND")) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{pubkey1[:], pubkey2[:], pubkey4[:]}, + }, nil) v := validator{ validatorClient: client, proposerSettings: &validatorserviceconfig.ProposerSettings{ @@ -2094,8 +2081,9 @@ func TestValidator_buildPrepProposerReqs_WithoutDefaultConfig(t *testing.T) { FeeRecipient: feeRecipient2[:], }, } - - actual, err := v.buildPrepProposerReqs(ctx, pubkeys) + filteredKeys, err := v.filterAndCacheActiveKeys(ctx, pubkeys) + require.NoError(t, err) + actual, err := v.buildPrepProposerReqs(ctx, filteredKeys) require.NoError(t, err) assert.DeepEqual(t, expected, actual) } @@ -2141,6 +2129,14 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { }, ).Return(nil, status.Error(codes.NotFound, "NOT_FOUND")) + client.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any()).Return( + ðpb.MultipleValidatorStatusResponse{ + Statuses: []*ethpb.ValidatorStatusResponse{{Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}, {Status: ethpb.ValidatorStatus_ACTIVE}}, + PublicKeys: [][]byte{pubkey1[:], pubkey2[:], pubkey4[:]}, + }, nil) + v := validator{ validatorClient: client, proposerSettings: &validatorserviceconfig.ProposerSettings{ @@ -2189,8 +2185,9 @@ func TestValidator_buildPrepProposerReqs_WithDefaultConfig(t *testing.T) { FeeRecipient: defaultFeeRecipient[:], }, } - - actual, err := v.buildPrepProposerReqs(ctx, pubkeys) + filteredKeys, err := v.filterAndCacheActiveKeys(ctx, pubkeys) + require.NoError(t, err) + actual, err := v.buildPrepProposerReqs(ctx, filteredKeys) require.NoError(t, err) assert.DeepEqual(t, expected, actual) } @@ -2261,6 +2258,7 @@ func TestValidator_buildSignedRegReqs_DefaultConfigDisabled(t *testing.T) { }, }, }, + pubkeyToValidatorIndex: make(map[[48]byte]primitives.ValidatorIndex), } pubkeys := [][fieldparams.BLSPubkeyLength]byte{pubkey1, pubkey2, pubkey3} @@ -2268,7 +2266,9 @@ func TestValidator_buildSignedRegReqs_DefaultConfigDisabled(t *testing.T) { var signer = func(_ context.Context, _ *validatorpb.SignRequest) (bls.Signature, error) { return signature, nil } - + v.pubkeyToValidatorIndex[pubkey1] = primitives.ValidatorIndex(1) + v.pubkeyToValidatorIndex[pubkey2] = primitives.ValidatorIndex(2) + v.pubkeyToValidatorIndex[pubkey3] = primitives.ValidatorIndex(3) actual, err := v.buildSignedRegReqs(ctx, pubkeys, signer) require.NoError(t, err) @@ -2344,6 +2344,7 @@ func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { }, }, }, + pubkeyToValidatorIndex: make(map[[48]byte]primitives.ValidatorIndex), } pubkeys := [][fieldparams.BLSPubkeyLength]byte{pubkey1, pubkey2, pubkey3} @@ -2351,7 +2352,9 @@ func TestValidator_buildSignedRegReqs_DefaultConfigEnabled(t *testing.T) { var signer = func(_ context.Context, _ *validatorpb.SignRequest) (bls.Signature, error) { return signature, nil } - + v.pubkeyToValidatorIndex[pubkey1] = primitives.ValidatorIndex(1) + v.pubkeyToValidatorIndex[pubkey2] = primitives.ValidatorIndex(2) + v.pubkeyToValidatorIndex[pubkey3] = primitives.ValidatorIndex(3) actual, err := v.buildSignedRegReqs(ctx, pubkeys, signer) require.NoError(t, err) diff --git a/validator/node/node.go b/validator/node/node.go index d3ec35da26e0..be71dcd4fe96 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -526,6 +526,9 @@ func proposerSettings(cliCtx *cli.Context) (*validatorServiceConfig.ProposerSett // nothing is set, so just return nil if fileConfig == nil { + if cliCtx.Bool(flags.EnableBuilderFlag.Name) { + return nil, fmt.Errorf("%s flag can only be used when a default fee recipient is present on the validator client", flags.EnableBuilderFlag.Name) + } return nil, nil } // convert file config to proposer config for internal use @@ -538,6 +541,7 @@ func proposerSettings(cliCtx *cli.Context) (*validatorServiceConfig.ProposerSett if !common.IsHexAddress(fileConfig.DefaultConfig.FeeRecipient) { return nil, errors.New("default fileConfig fee recipient is not a valid eth1 address") } + if err := warnNonChecksummedAddress(fileConfig.DefaultConfig.FeeRecipient); err != nil { return nil, err } diff --git a/validator/node/node_test.go b/validator/node/node_test.go index 687db3b46daf..966546eee09b 100644 --- a/validator/node/node_test.go +++ b/validator/node/node_test.go @@ -692,3 +692,13 @@ func TestProposerSettings(t *testing.T) { }) } } + +// return an error if the user is using builder settings without any default fee recipient +func TestProposerSettings_EnableBuilder_noFeeRecipient(t *testing.T) { + app := cli.App{} + set := flag.NewFlagSet("test", 0) + set.Bool(flags.EnableBuilderFlag.Name, true, "") + cliCtx := cli.NewContext(&app, set, nil) + _, err := proposerSettings(cliCtx) + require.ErrorContains(t, "can only be used when a default fee recipient is present on the validator client", err) +}