Faster Doppelganger Check (#9964)

* faster check

* potuz's review

* potuz's review
This commit is contained in:
Nishant Das 2021-12-01 20:37:10 +08:00 committed by GitHub
parent 7cbef104b0
commit d94bf32dcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 33 deletions

View File

@ -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 := &ethpb.DoppelGangerResponse{
resp = &ethpb.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,
&ethpb.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 := &ethpb.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,
&ethpb.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)
}

View File

@ -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,
},