diff --git a/beacon-chain/blockchain/forkchoice/process_block.go b/beacon-chain/blockchain/forkchoice/process_block.go index a593dd7993e0..735e0d9b99cc 100644 --- a/beacon-chain/blockchain/forkchoice/process_block.go +++ b/beacon-chain/blockchain/forkchoice/process_block.go @@ -486,7 +486,8 @@ func (s *Store) currentSlot() uint64 { // updates justified check point in store if a better check point is known func (s *Store) updateJustifiedCheckpoint() { - if helpers.SlotsSinceEpochStarts(s.currentSlot()) == 0 { + // Update at epoch boundary slot only + if !helpers.IsEpochStart(s.currentSlot()) { return } if s.bestJustifiedCheckpt.Epoch > s.justifiedCheckpt.Epoch { diff --git a/beacon-chain/blockchain/forkchoice/process_block_test.go b/beacon-chain/blockchain/forkchoice/process_block_test.go index 9d313fe9e8b4..579d9e3ba42e 100644 --- a/beacon-chain/blockchain/forkchoice/process_block_test.go +++ b/beacon-chain/blockchain/forkchoice/process_block_test.go @@ -1,10 +1,12 @@ package forkchoice import ( + "bytes" "context" "reflect" "strings" "testing" + "time" "github.com/prysmaticlabs/go-bitfield" "github.com/prysmaticlabs/go-ssz" @@ -334,3 +336,115 @@ func TestRemoveStateSinceLastFinalized(t *testing.T) { } } } + +func TestShouldUpdateJustified_ReturnTrue(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + params.UseMinimalConfig() + defer params.UseMainnetConfig() + + store := NewForkChoiceService(ctx, db) + store.genesisTime = uint64(time.Now().Unix()) + + update, err := store.shouldUpdateJustified(ctx, ðpb.Checkpoint{}) + if err != nil { + t.Fatal(err) + } + if !update { + t.Error("Should be able to update justified, received false") + } + + lastJustifiedBlk := ðpb.BeaconBlock{ParentRoot: []byte{'G'}} + lastJustifiedRoot, _ := ssz.SigningRoot(lastJustifiedBlk) + newJustifiedBlk := ðpb.BeaconBlock{Slot: 1, ParentRoot: lastJustifiedRoot[:]} + newJustifiedRoot, _ := ssz.SigningRoot(newJustifiedBlk) + if err := store.db.SaveBlock(ctx, newJustifiedBlk); err != nil { + t.Fatal(err) + } + if err := store.db.SaveBlock(ctx, lastJustifiedBlk); err != nil { + t.Fatal(err) + } + + diff := (params.BeaconConfig().SlotsPerEpoch - 1) * params.BeaconConfig().SecondsPerSlot + store.genesisTime = uint64(time.Now().Unix()) - diff + store.justifiedCheckpt = ðpb.Checkpoint{Root: lastJustifiedRoot[:]} + update, err = store.shouldUpdateJustified(ctx, ðpb.Checkpoint{Root: newJustifiedRoot[:]}) + if err != nil { + t.Fatal(err) + } + if !update { + t.Error("Should be able to update justified, received false") + } +} + +func TestShouldUpdateJustified_ReturnFalse(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + params.UseMinimalConfig() + defer params.UseMainnetConfig() + + store := NewForkChoiceService(ctx, db) + + lastJustifiedBlk := ðpb.BeaconBlock{ParentRoot: []byte{'G'}} + lastJustifiedRoot, _ := ssz.SigningRoot(lastJustifiedBlk) + newJustifiedBlk := ðpb.BeaconBlock{ParentRoot: lastJustifiedRoot[:]} + newJustifiedRoot, _ := ssz.SigningRoot(newJustifiedBlk) + if err := store.db.SaveBlock(ctx, newJustifiedBlk); err != nil { + t.Fatal(err) + } + if err := store.db.SaveBlock(ctx, lastJustifiedBlk); err != nil { + t.Fatal(err) + } + + diff := (params.BeaconConfig().SlotsPerEpoch - 1) * params.BeaconConfig().SecondsPerSlot + store.genesisTime = uint64(time.Now().Unix()) - diff + store.justifiedCheckpt = ðpb.Checkpoint{Root: lastJustifiedRoot[:]} + + update, err := store.shouldUpdateJustified(ctx, ðpb.Checkpoint{Root: newJustifiedRoot[:]}) + if err != nil { + t.Fatal(err) + } + if update { + t.Error("Should not be able to update justified, received true") + } +} + +func TestUpdateJustifiedCheckpoint_Update(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + params.UseMinimalConfig() + defer params.UseMainnetConfig() + + store := NewForkChoiceService(ctx, db) + store.genesisTime = uint64(time.Now().Unix()) + + store.justifiedCheckpt = ðpb.Checkpoint{Root: []byte{'A'}} + store.bestJustifiedCheckpt = ðpb.Checkpoint{Epoch: 1, Root: []byte{'B'}} + store.updateJustifiedCheckpoint() + + if !bytes.Equal(store.justifiedCheckpt.Root, []byte{'B'}) { + t.Error("Justified check point root did not update") + } +} + +func TestUpdateJustifiedCheckpoint_NoUpdate(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + params.UseMinimalConfig() + defer params.UseMainnetConfig() + + store := NewForkChoiceService(ctx, db) + store.genesisTime = uint64(time.Now().Unix()) - params.BeaconConfig().SecondsPerSlot + + store.justifiedCheckpt = ðpb.Checkpoint{Root: []byte{'A'}} + store.bestJustifiedCheckpt = ðpb.Checkpoint{Epoch: 1, Root: []byte{'B'}} + store.updateJustifiedCheckpoint() + + if bytes.Equal(store.justifiedCheckpt.Root, []byte{'B'}) { + t.Error("Justified check point root was not suppose to update") + } +} diff --git a/beacon-chain/core/helpers/slot_epoch_test.go b/beacon-chain/core/helpers/slot_epoch_test.go index 2a1bdc853bf5..017360592c2d 100644 --- a/beacon-chain/core/helpers/slot_epoch_test.go +++ b/beacon-chain/core/helpers/slot_epoch_test.go @@ -156,3 +156,21 @@ func TestIsEpochEnd(t *testing.T) { } } } + +func TestSlotsSinceEpochStarts(t *testing.T) { + tests := []struct { + slots uint64 + wantedSlots uint64 + }{ + {slots: 0, wantedSlots: 0}, + {slots: 1, wantedSlots: 1}, + {slots: params.BeaconConfig().SlotsPerEpoch - 1, wantedSlots: params.BeaconConfig().SlotsPerEpoch - 1}, + {slots: params.BeaconConfig().SlotsPerEpoch + 1, wantedSlots: 1}, + {slots: 10*params.BeaconConfig().SlotsPerEpoch + 2, wantedSlots: 2}, + } + for _, tt := range tests { + if got := SlotsSinceEpochStarts(tt.slots); got != tt.wantedSlots { + t.Errorf("SlotsSinceEpochStarts() = %v, want %v", got, tt.wantedSlots) + } + } +}