From d94bf32dcfc6cd94c55765478c514e700952a27d Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Wed, 1 Dec 2021 20:37:10 +0800 Subject: [PATCH] Faster Doppelganger Check (#9964) * faster check * potuz's review * potuz's review --- .../rpc/prysm/v1alpha1/validator/status.go | 68 ++++++++++++++----- .../prysm/v1alpha1/validator/status_test.go | 19 +----- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/status.go b/beacon-chain/rpc/prysm/v1alpha1/validator/status.go index ea527475c..d8f8a6fa2 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/status.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/status.go @@ -24,6 +24,8 @@ import ( var errPubkeyDoesNotExist = errors.New("pubkey does not exist") var nonExistentIndex = types.ValidatorIndex(^uint64(0)) +const numStatesToCheck = 2 + // ValidatorStatus returns the validator status of the current epoch. // The status response can be one of the following: // DEPOSITED - validator's deposit has been recognized by Ethereum 1, not yet recognized by Ethereum. @@ -106,9 +108,17 @@ func (vs *Server) CheckDoppelGanger(ctx context.Context, req *ethpb.DoppelGanger if err != nil { return nil, status.Error(codes.Internal, "Could not get head state") } + + currEpoch := slots.ToEpoch(headState.Slot()) + isRecent, resp := checkValidatorsAreRecent(currEpoch, req) + // If all provided keys are recent we skip this check + // as we are unable to effectively determine if a doppelganger + // is active. + if isRecent { + return resp, nil + } // We walk back from the current head state to the state at the beginning of the previous 2 epochs. // Where S_i , i := 0,1,2. i = 0 would signify the current head state in this epoch. - currEpoch := slots.ToEpoch(headState.Slot()) previousEpoch, err := currEpoch.SafeSub(1) if err != nil { previousEpoch = currEpoch @@ -125,18 +135,18 @@ func (vs *Server) CheckDoppelGanger(ctx context.Context, req *ethpb.DoppelGanger if err != nil { return nil, status.Error(codes.Internal, "Could not get older state") } - resp := ðpb.DoppelGangerResponse{ + resp = ðpb.DoppelGangerResponse{ Responses: []*ethpb.DoppelGangerResponse_ValidatorResponse{}, } for _, v := range req.ValidatorRequests { // If the validator's last recorded epoch was - // less than or equal to 2 epochs ago, this method will not + // less than or equal to `numStatesToCheck` epochs ago, this method will not // be able to catch duplicates. This is due to how attestation // inclusion works, where an attestation for the current epoch // is able to included in the current or next epoch. Depending // on which epoch it is included the balance change will be // reflected in the following epoch. - if v.Epoch+2 >= currEpoch { + if v.Epoch+numStatesToCheck >= currEpoch { resp.Responses = append(resp.Responses, ðpb.DoppelGangerResponse_ValidatorResponse{ PublicKey: v.PublicKey, @@ -322,6 +332,44 @@ func (vs *Server) validatorStatus( } } +func (vs *Server) retrieveAfterEpochTransition(ctx context.Context, epoch types.Epoch) (state.BeaconState, error) { + endSlot, err := slots.EpochEnd(epoch) + if err != nil { + return nil, err + } + retState, err := vs.StateGen.StateBySlot(ctx, endSlot) + if err != nil { + return nil, err + } + return transition.ProcessSlots(ctx, retState, retState.Slot()+1) +} + +func checkValidatorsAreRecent(headEpoch types.Epoch, req *ethpb.DoppelGangerRequest) (bool, *ethpb.DoppelGangerResponse) { + validatorsAreRecent := true + resp := ðpb.DoppelGangerResponse{ + Responses: []*ethpb.DoppelGangerResponse_ValidatorResponse{}, + } + for _, v := range req.ValidatorRequests { + // Due to how balances are reflected for individual + // validators, we can only effectively determine if a + // validator voted or not if we are able to look + // back more than `numStatesToCheck` epochs into the past. + if v.Epoch+numStatesToCheck < headEpoch { + validatorsAreRecent = false + // Zero out response if we encounter non-recent validators to + // guard against potential misuse. + resp.Responses = []*ethpb.DoppelGangerResponse_ValidatorResponse{} + break + } + resp.Responses = append(resp.Responses, + ðpb.DoppelGangerResponse_ValidatorResponse{ + PublicKey: v.PublicKey, + DuplicateExists: false, + }) + } + return validatorsAreRecent, resp +} + func statusForPubKey(headState state.ReadOnlyBeaconState, pubKey []byte) (ethpb.ValidatorStatus, types.ValidatorIndex, error) { if headState == nil || headState.IsNil() { return ethpb.ValidatorStatus_UNKNOWN_STATUS, 0, errors.New("head state does not exist") @@ -371,15 +419,3 @@ func depositStatus(depositOrBalance uint64) ethpb.ValidatorStatus { } return ethpb.ValidatorStatus_DEPOSITED } - -func (vs *Server) retrieveAfterEpochTransition(ctx context.Context, epoch types.Epoch) (state.BeaconState, error) { - endSlot, err := slots.EpochEnd(epoch) - if err != nil { - return nil, err - } - retState, err := vs.StateGen.StateBySlot(ctx, endSlot) - if err != nil { - return nil, err - } - return transition.ProcessSlots(ctx, retState, retState.Slot()+1) -} diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go index ec17ae397..02bd02da3 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go @@ -1170,25 +1170,10 @@ func TestServer_CheckDoppelGanger(t *testing.T) { svSetup: func(t *testing.T) (*Server, *ethpb.DoppelGangerRequest, *ethpb.DoppelGangerResponse) { mockGen := stategen.NewMockService() - hs, ps, os, keys := createStateSetup(t, 4, mockGen) - // Previous Epoch State - for i := 10; i < 15; i++ { - bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i)) - assert.NoError(t, err) - // Add 100 gwei, to mock an active validator - assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-1000000000)) - } - - // Older Epoch State - for i := 10; i < 15; i++ { - bal, err := os.BalanceAtIndex(types.ValidatorIndex(i)) - assert.NoError(t, err) - // Add 200 gwei, to mock an active validator - assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-2000000000)) - } + hs, _, _, keys := createStateSetup(t, 4, mockGen) vs := &Server{ - StateGen: mockGen, + StateGen: nil, HeadFetcher: &mockChain.ChainService{ State: hs, },