diff --git a/beacon-chain/core/helpers/BUILD.bazel b/beacon-chain/core/helpers/BUILD.bazel index 4b17ef3ef7..17dc975cf9 100644 --- a/beacon-chain/core/helpers/BUILD.bazel +++ b/beacon-chain/core/helpers/BUILD.bazel @@ -38,6 +38,7 @@ go_library( "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", + "@io_opencensus_go//trace:go_default_library", ], ) diff --git a/beacon-chain/core/helpers/validators.go b/beacon-chain/core/helpers/validators.go index d37eb7806e..9afb1b75b7 100644 --- a/beacon-chain/core/helpers/validators.go +++ b/beacon-chain/core/helpers/validators.go @@ -17,6 +17,7 @@ import ( ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v4/time/slots" log "github.com/sirupsen/logrus" + "go.opencensus.io/trace" ) var CommitteeCacheInProgressHit = promauto.NewCounter(prometheus.CounterOpts{ @@ -396,3 +397,22 @@ func isEligibleForActivation(activationEligibilityEpoch, activationEpoch, finali return activationEligibilityEpoch <= finalizedEpoch && activationEpoch == params.BeaconConfig().FarFutureEpoch } + +// LastActivatedValidatorIndex provides the last activated validator given a state +func LastActivatedValidatorIndex(ctx context.Context, st state.ReadOnlyBeaconState) (primitives.ValidatorIndex, error) { + _, span := trace.StartSpan(ctx, "helpers.LastActivatedValidatorIndex") + defer span.End() + var lastActivatedvalidatorIndex primitives.ValidatorIndex + // linear search because status are not sorted + for j := st.NumValidators() - 1; j >= 0; j-- { + val, err := st.ValidatorAtIndexReadOnly(primitives.ValidatorIndex(j)) + if err != nil { + return 0, err + } + if IsActiveValidatorUsingTrie(val, time.CurrentEpoch(st)) { + lastActivatedvalidatorIndex = primitives.ValidatorIndex(j) + break + } + } + return lastActivatedvalidatorIndex, nil +} diff --git a/beacon-chain/core/helpers/validators_test.go b/beacon-chain/core/helpers/validators_test.go index d17766912b..2f0c6dca36 100644 --- a/beacon-chain/core/helpers/validators_test.go +++ b/beacon-chain/core/helpers/validators_test.go @@ -727,3 +727,26 @@ func computeProposerIndexWithValidators(validators []*ethpb.Validator, activeInd } } } + +func TestLastActivatedValidatorIndex_OK(t *testing.T) { + beaconState, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{}) + require.NoError(t, err) + + validators := make([]*ethpb.Validator, 4) + balances := make([]uint64, len(validators)) + for i := uint64(0); i < 4; i++ { + validators[i] = ðpb.Validator{ + PublicKey: make([]byte, params.BeaconConfig().BLSPubkeyLength), + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: 32 * 1e9, + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + } + balances[i] = validators[i].EffectiveBalance + } + require.NoError(t, beaconState.SetValidators(validators)) + require.NoError(t, beaconState.SetBalances(balances)) + + index, err := LastActivatedValidatorIndex(context.Background(), beaconState) + require.NoError(t, err) + require.Equal(t, index, primitives.ValidatorIndex(3)) +} diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/assignments.go b/beacon-chain/rpc/prysm/v1alpha1/validator/assignments.go index 2d98fd0674..cafd9b17a5 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/assignments.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/assignments.go @@ -146,6 +146,7 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. validatorAssignments := make([]*ethpb.DutiesResponse_Duty, 0, len(req.PublicKeys)) nextValidatorAssignments := make([]*ethpb.DutiesResponse_Duty, 0, len(req.PublicKeys)) + for _, pubKey := range req.PublicKeys { if ctx.Err() != nil { return nil, status.Errorf(codes.Aborted, "Could not continue fetching assignments: %v", ctx.Err()) @@ -194,7 +195,8 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. vs.ProposerSlotIndexCache.PrunePayloadIDs(epochStartSlot) } else { // If the validator isn't in the beacon state, try finding their deposit to determine their status. - vStatus, _ := vs.validatorStatus(ctx, s, pubKey) + // We don't need the lastActiveValidatorFn because we don't use the response in this. + vStatus, _ := vs.validatorStatus(ctx, s, pubKey, nil) assignment.Status = vStatus.Status } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/status.go b/beacon-chain/rpc/prysm/v1alpha1/validator/status.go index d45e42ea65..d82721f45c 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/status.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/status.go @@ -23,6 +23,7 @@ import ( ) var errPubkeyDoesNotExist = errors.New("pubkey does not exist") +var errHeadstateDoesNotExist = errors.New("head state does not exist") var errOptimisticMode = errors.New("the node is currently optimistic and cannot serve validators") var nonExistentIndex = primitives.ValidatorIndex(^uint64(0)) @@ -46,7 +47,8 @@ func (vs *Server) ValidatorStatus( if err != nil { return nil, status.Error(codes.Internal, "Could not get head state") } - vStatus, _ := vs.validatorStatus(ctx, headState, req.PublicKey) + + vStatus, _ := vs.validatorStatus(ctx, headState, req.PublicKey, func() (primitives.ValidatorIndex, error) { return helpers.LastActivatedValidatorIndex(ctx, headState) }) return vStatus, nil } @@ -86,8 +88,9 @@ func (vs *Server) MultipleValidatorStatus( // Fetch statuses from beacon state. statuses := make([]*ethpb.ValidatorStatusResponse, len(pubKeys)) indices := make([]primitives.ValidatorIndex, len(pubKeys)) + lastActivated, hpErr := helpers.LastActivatedValidatorIndex(ctx, headState) for i, pubKey := range pubKeys { - statuses[i], indices[i] = vs.validatorStatus(ctx, headState, pubKey) + statuses[i], indices[i] = vs.validatorStatus(ctx, headState, pubKey, func() (primitives.ValidatorIndex, error) { return lastActivated, hpErr }) } return ðpb.MultipleValidatorStatusResponse{ @@ -223,11 +226,13 @@ func (vs *Server) activationStatus( } activeValidatorExists := false statusResponses := make([]*ethpb.ValidatorActivationResponse_Status, len(pubKeys)) + // only run calculation of last activated once per state + lastActivated, hpErr := helpers.LastActivatedValidatorIndex(ctx, headState) for i, pubKey := range pubKeys { if ctx.Err() != nil { return false, nil, ctx.Err() } - vStatus, idx := vs.validatorStatus(ctx, headState, pubKey) + vStatus, idx := vs.validatorStatus(ctx, headState, pubKey, func() (primitives.ValidatorIndex, error) { return lastActivated, hpErr }) if vStatus == nil { continue } @@ -272,6 +277,7 @@ func (vs *Server) validatorStatus( ctx context.Context, headState state.ReadOnlyBeaconState, pubKey []byte, + lastActiveValidatorFn func() (primitives.ValidatorIndex, error), ) (*ethpb.ValidatorStatusResponse, primitives.ValidatorIndex) { ctx, span := trace.StartSpan(ctx, "ValidatorServer.validatorStatus") defer span.End() @@ -340,17 +346,12 @@ func (vs *Server) validatorStatus( } } } - - var lastActivatedvalidatorIndex primitives.ValidatorIndex - for j := headState.NumValidators() - 1; j >= 0; j-- { - val, err := headState.ValidatorAtIndexReadOnly(primitives.ValidatorIndex(j)) - if err != nil { - return resp, idx - } - if helpers.IsActiveValidatorUsingTrie(val, time.CurrentEpoch(headState)) { - lastActivatedvalidatorIndex = primitives.ValidatorIndex(j) - break - } + if lastActiveValidatorFn == nil { + return resp, idx + } + lastActivatedvalidatorIndex, err := lastActiveValidatorFn() + if err != nil { + return resp, idx } // Our position in the activation queue is the above index - our validator index. if lastActivatedvalidatorIndex < idx { @@ -390,7 +391,7 @@ func checkValidatorsAreRecent(headEpoch primitives.Epoch, req *ethpb.DoppelGange func statusForPubKey(headState state.ReadOnlyBeaconState, pubKey []byte) (ethpb.ValidatorStatus, primitives.ValidatorIndex, error) { if headState == nil || headState.IsNil() { - return ethpb.ValidatorStatus_UNKNOWN_STATUS, 0, errors.New("head state does not exist") + return ethpb.ValidatorStatus_UNKNOWN_STATUS, 0, errHeadstateDoesNotExist } idx, ok := headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) if !ok || uint64(idx) >= uint64(headState.NumValidators()) { diff --git a/validator/client/beacon-api/status.go b/validator/client/beacon-api/status.go index f23e3198b1..6031cf3581 100644 --- a/validator/client/beacon-api/status.go +++ b/validator/client/beacon-api/status.go @@ -121,7 +121,8 @@ func (c *beaconApiValidatorClient) getValidatorsStatusResponse(ctx context.Conte case ethpb.ValidatorStatus_PENDING, ethpb.ValidatorStatus_PARTIALLY_DEPOSITED, ethpb.ValidatorStatus_DEPOSITED: if !isLastActivatedValidatorIndexRetrieved { isLastActivatedValidatorIndexRetrieved = true - + // TODO: double check this due to potential of PENDING STATE being active.. + // edge case https://github.com/prysmaticlabs/prysm/blob/0669050ffabe925c3d6e5e5d535a86361ae8522b/validator/client/validator.go#L1068 activeStateValidators, err := c.stateValidatorsProvider.GetStateValidators(ctx, nil, nil, []string{"active"}) if err != nil { return nil, nil, nil, errors.Wrap(err, "failed to get state validators")