Skip to content

Commit

Permalink
Fix Doppelganger Protection (#9748)
Browse files Browse the repository at this point in the history
* add fix

* fix tests

* Update beacon-chain/rpc/prysm/v1alpha1/validator/status.go

* fix tests

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Oct 18, 2021
1 parent 20c7efd commit bfcb113
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 30 deletions.
1 change: 1 addition & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel
Expand Up @@ -137,6 +137,7 @@ go_test(
"//testing/util:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"@com_github_d4l3k_messagediff//:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
Expand Down
18 changes: 16 additions & 2 deletions beacon-chain/rpc/prysm/v1alpha1/validator/status.go
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/contracts/deposit"
Expand Down Expand Up @@ -116,11 +117,11 @@ func (vs *Server) CheckDoppelGanger(ctx context.Context, req *ethpb.DoppelGanger
if err != nil {
olderEpoch = previousEpoch
}
prevState, err := vs.StateGen.StateBySlot(ctx, params.BeaconConfig().SlotsPerEpoch.Mul(uint64(previousEpoch)))
prevState, err := vs.retrieveAfterEpochTransition(ctx, previousEpoch)
if err != nil {
return nil, status.Error(codes.Internal, "Could not get previous state")
}
olderState, err := vs.StateGen.StateBySlot(ctx, params.BeaconConfig().SlotsPerEpoch.Mul(uint64(olderEpoch)))
olderState, err := vs.retrieveAfterEpochTransition(ctx, olderEpoch)
if err != nil {
return nil, status.Error(codes.Internal, "Could not get older state")
}
Expand Down Expand Up @@ -159,6 +160,7 @@ func (vs *Server) CheckDoppelGanger(ctx context.Context, req *ethpb.DoppelGanger
// If the next epoch's balance is higher, we mark it as an existing
// duplicate.
if nextBal > baseBal {
log.Infof("current %d with last epoch %d and difference in bal %d gwei", currEpoch, v.Epoch, nextBal-baseBal)
resp.Responses = append(resp.Responses,
&ethpb.DoppelGangerResponse_ValidatorResponse{
PublicKey: v.PublicKey,
Expand Down Expand Up @@ -369,3 +371,15 @@ 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)
}
126 changes: 98 additions & 28 deletions beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go
Expand Up @@ -6,7 +6,9 @@ import (
"testing"
"time"

"github.com/d4l3k/messagediff"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/go-bitfield"
mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
Expand Down Expand Up @@ -944,14 +946,14 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 100 gwei, to mock an inactivity leak
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+100))
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+1000000000))
}
// Older Epoch State
for i := 0; i < 3; i++ {
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 200 gwei, to mock an inactivity leak
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+200))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+2000000000))
}
vs := &Server{
StateGen: mockGen,
Expand Down Expand Up @@ -990,24 +992,24 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 100 gwei, to mock an inactivity leak
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+100))
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+1000000000))
}
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(2))
assert.NoError(t, err)
// Sub 100 gwei, to mock an active validator.
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-100))
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-1000000000))

// Older Epoch State
for i := 0; i < 2; i++ {
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 200 gwei, to mock an inactivity leak
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+200))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+2000000000))
}
bal, err = os.BalanceAtIndex(types.ValidatorIndex(2))
assert.NoError(t, err)
// Sub 100 gwei, to mock an active validator.
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-100))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-1000000000))

vs := &Server{
StateGen: mockGen,
Expand Down Expand Up @@ -1057,24 +1059,24 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 100 gwei, to mock an inactivity leak
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+100))
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+1000000000))
}
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(2))
assert.NoError(t, err)
// Sub 100 gwei, to mock an active validator.
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-100))
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-1000000000))

// Older Epoch State
for i := 0; i < 2; i++ {
bal, err := os.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 200 gwei, to mock an inactivity leak
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+200))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal+2000000000))
}
bal, err = os.BalanceAtIndex(types.ValidatorIndex(2))
assert.NoError(t, err)
// Sub 200 gwei, to mock an active validator.
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-200))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(2), bal-2000000000))

vs := &Server{
StateGen: mockGen,
Expand Down Expand Up @@ -1124,15 +1126,15 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
bal, err := ps.BalanceAtIndex(types.ValidatorIndex(i))
assert.NoError(t, err)
// Add 100 gwei, to mock an inactivity leak
assert.NoError(t, ps.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-100))
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 inactivity leak
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-200))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-2000000000))
}

vs := &Server{
Expand All @@ -1146,17 +1148,6 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
}
response := &ethpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
for i := 0; i < 10; i++ {
request.ValidatorRequests = append(request.ValidatorRequests, &ethpb.DoppelGangerRequest_ValidatorRequest{
PublicKey: keys[i].PublicKey().Marshal(),
Epoch: 1,
SignedRoot: []byte{'A'},
})
response.Responses = append(response.Responses, &ethpb.DoppelGangerResponse_ValidatorResponse{
PublicKey: keys[i].PublicKey().Marshal(),
DuplicateExists: false,
})
}
for i := 10; i < 15; i++ {
// Add in for duplicate validator
request.ValidatorRequests = append(request.ValidatorRequests, &ethpb.DoppelGangerRequest_ValidatorRequest{
Expand Down Expand Up @@ -1185,15 +1176,15 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
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-100))
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-200))
assert.NoError(t, os.UpdateBalancesAtIndex(types.ValidatorIndex(i), bal-2000000000))
}

vs := &Server{
Expand Down Expand Up @@ -1232,7 +1223,8 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
return
}
if !reflect.DeepEqual(got, resp) {
t.Errorf("CheckDoppelGanger() got = %v, want %v", got.String(), resp.String())
diff, _ := messagediff.PrettyDiff(resp, got)
t.Errorf("CheckDoppelGanger() difference = %v", diff)
}
})
}
Expand All @@ -1246,20 +1238,98 @@ func createStateSetup(t *testing.T, head types.Epoch, mockgen *stategen.MockStat
headEpoch := head
headSlot := types.Slot(headEpoch) * params.BeaconConfig().SlotsPerEpoch
assert.NoError(t, hs.SetSlot(headSlot))
assingments, _, err := helpers.CommitteeAssignments(context.Background(), hs, headEpoch)
assert.NoError(t, err)
for _, ctr := range assingments {
pendingAtt := &ethpb.PendingAttestation{
AggregationBits: bitfield.NewBitlist64(uint64(len(ctr.Committee))).ToBitlist().Not(),
Data: &ethpb.AttestationData{
Slot: ctr.AttesterSlot,
CommitteeIndex: ctr.CommitteeIndex,
BeaconBlockRoot: make([]byte, 32),
Source: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
},
Target: &ethpb.Checkpoint{
Epoch: 1,
Root: make([]byte, 32),
},
},
InclusionDelay: 1,
ProposerIndex: 10,
}
assert.NoError(t, hs.AppendCurrentEpochAttestations(pendingAtt))

}
mockgen.StatesBySlot[headSlot] = hs

// Previous Epoch State
prevEpoch := headEpoch - 1
ps := gs.Copy()
prevSlot := types.Slot(prevEpoch) * params.BeaconConfig().SlotsPerEpoch
prevSlot, err := slots.EpochEnd(prevEpoch)
assert.NoError(t, err)
assert.NoError(t, ps.SetSlot(prevSlot))
assingments, _, err = helpers.CommitteeAssignments(context.Background(), ps, prevEpoch)
assert.NoError(t, err)
for _, ctr := range assingments {
pendingAtt := &ethpb.PendingAttestation{
AggregationBits: bitfield.NewBitlist64(uint64(len(ctr.Committee))).ToBitlist().Not(),
Data: &ethpb.AttestationData{
Slot: ctr.AttesterSlot,
CommitteeIndex: ctr.CommitteeIndex,
BeaconBlockRoot: make([]byte, 32),
Source: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
},
Target: &ethpb.Checkpoint{
Epoch: 1,
Root: make([]byte, 32),
},
},
InclusionDelay: 1,
ProposerIndex: 10,
}
assert.NoError(t, ps.AppendCurrentEpochAttestations(pendingAtt))

}
mockgen.StatesBySlot[prevSlot] = ps

// Older Epoch State
olderEpoch := prevEpoch - 1
os := gs.Copy()
olderSlot := types.Slot(olderEpoch) * params.BeaconConfig().SlotsPerEpoch
olderSlot, err := slots.EpochEnd(olderEpoch)
assert.NoError(t, err)
assert.NoError(t, os.SetSlot(olderSlot))
assingments, _, err = helpers.CommitteeAssignments(context.Background(), os, olderEpoch)
assert.NoError(t, err)
for _, ctr := range assingments {
attSlot := ctr.AttesterSlot
if attSlot == olderSlot {
continue
}
pendingAtt := &ethpb.PendingAttestation{
AggregationBits: bitfield.NewBitlist64(uint64(len(ctr.Committee))).ToBitlist().Not(),
Data: &ethpb.AttestationData{
Slot: attSlot,
CommitteeIndex: ctr.CommitteeIndex,
BeaconBlockRoot: make([]byte, 32),
Source: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
},
Target: &ethpb.Checkpoint{
Epoch: 1,
Root: make([]byte, 32),
},
},
InclusionDelay: 1,
ProposerIndex: 10,
}
assert.NoError(t, os.AppendCurrentEpochAttestations(pendingAtt))

}
mockgen.StatesBySlot[olderSlot] = os
return hs, ps, os, keys
}

0 comments on commit bfcb113

Please sign in to comment.