mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 16:08:26 -05:00
Implement Validator Cutoff Algorithm (#339)
This commit is contained in:
@@ -129,7 +129,7 @@ func (b *BeaconChain) CanProcessBlock(fetcher types.POWBlockFetcher, block *type
|
||||
// TODO: check if the parentHash pointed by the beacon block is in the beaconDB.
|
||||
|
||||
// Calculate the timestamp validity condition.
|
||||
slotDuration := time.Duration(block.SlotNumber()*params.SlotLength) * time.Second
|
||||
slotDuration := time.Duration(block.SlotNumber()*params.SlotDuration) * time.Second
|
||||
genesis, err := b.GenesisBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -242,6 +242,16 @@ func (b *BeaconChain) computeNewActiveState(seed common.Hash) (*types.ActiveStat
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getAttestersProposer returns lists of random sampled attesters and proposer indices.
|
||||
func (b *BeaconChain) getAttestersProposer(seed common.Hash) ([]int, int, error) {
|
||||
attesterCount := math.Min(params.AttesterCount, float64(len(b.CrystallizedState().ActiveValidators)))
|
||||
indices, err := utils.ShuffleIndices(seed, len(b.CrystallizedState().ActiveValidators))
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
return indices[:int(attesterCount)], indices[len(indices)-1], nil
|
||||
}
|
||||
|
||||
// hashActiveState serializes the active state object then uses
|
||||
// blake2b to hash the serialized object.
|
||||
func hashActiveState(state *types.ActiveState) ([32]byte, error) {
|
||||
@@ -262,14 +272,50 @@ func hashCrystallizedState(state *types.CrystallizedState) ([32]byte, error) {
|
||||
return blake2b.Sum256(serializedState), nil
|
||||
}
|
||||
|
||||
// getAttestersProposer returns lists of random sampled attesters and proposer indices.
|
||||
func (b *BeaconChain) getAttestersProposer(seed common.Hash) ([]int, int, error) {
|
||||
attesterCount := math.Min(params.AttesterCount, float64(len(b.CrystallizedState().ActiveValidators)))
|
||||
indices, err := utils.ShuffleIndices(seed, len(b.CrystallizedState().ActiveValidators))
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
// GetCutoffs is used to split up validators into groups at the start
|
||||
// of every epoch. It determines at what height validators can make
|
||||
// attestations and crosslinks. It returns lists of cutoff indices.
|
||||
func GetCutoffs(validatorCount int) []int {
|
||||
var heightCutoff = []int{0}
|
||||
var heights []int
|
||||
var heightCount float64
|
||||
|
||||
// Skip heights if there's not enough validators to fill in a min sized committee.
|
||||
if validatorCount < params.EpochLength*params.MinCommiteeSize {
|
||||
heightCount = math.Floor(float64(validatorCount) / params.MinCommiteeSize)
|
||||
for i := 0; i < int(heightCount); i++ {
|
||||
heights = append(heights, (i*params.Cofactor)%params.EpochLength)
|
||||
}
|
||||
// Enough validators, fill in all the heights.
|
||||
} else {
|
||||
heightCount = params.EpochLength
|
||||
for i := 0; i < int(heightCount); i++ {
|
||||
heights = append(heights, i)
|
||||
}
|
||||
}
|
||||
return indices[:int(attesterCount)], indices[len(indices)-1], nil
|
||||
|
||||
filled := 0
|
||||
appendHeight := false
|
||||
for i := 0; i < params.EpochLength-1; i++ {
|
||||
appendHeight = false
|
||||
for _, height := range heights {
|
||||
if i == height {
|
||||
appendHeight = true
|
||||
}
|
||||
}
|
||||
if appendHeight {
|
||||
filled++
|
||||
heightCutoff = append(heightCutoff, filled*validatorCount/int(heightCount))
|
||||
} else {
|
||||
heightCutoff = append(heightCutoff, heightCutoff[len(heightCutoff)-1])
|
||||
}
|
||||
}
|
||||
heightCutoff = append(heightCutoff, validatorCount)
|
||||
|
||||
// TODO: For the validators assigned to each height, split them up into
|
||||
// committees for different shards. Do not assign the last END_EPOCH_GRACE_PERIOD
|
||||
// heights in a epoch to any shards.
|
||||
return heightCutoff
|
||||
}
|
||||
|
||||
// hasVoted checks if the attester has voted by looking at the bitfield.
|
||||
|
||||
@@ -306,6 +306,50 @@ func TestRotateValidatorSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCutOffValidatorSet(t *testing.T) {
|
||||
|
||||
// Test scenario #1: Assume there's enough validators to fill in all the heights.
|
||||
validatorCount := params.EpochLength * params.MinCommiteeSize
|
||||
cutoffsValidators := GetCutoffs(validatorCount)
|
||||
|
||||
// The length of cutoff list should be 65. Since there is 64 heights per epoch,
|
||||
// it means during every height, a new set of 128 validators will form a committee.
|
||||
expectedCount := int(math.Ceil(float64(validatorCount)/params.MinCommiteeSize)) + 1
|
||||
if len(cutoffsValidators) != expectedCount {
|
||||
t.Errorf("Incorrect count for cutoffs validator. Wanted: %v, Got: %v", expectedCount, len(cutoffsValidators))
|
||||
}
|
||||
|
||||
// Verify each cutoff is an increment of MinCommiteeSize, it means 128 validators forms a
|
||||
// a committee and get to attest per height.
|
||||
count := 0
|
||||
for _, cutoff := range cutoffsValidators {
|
||||
if cutoff != count {
|
||||
t.Errorf("cutoffsValidators did not get 128 increment. Wanted: count, Got: %v", cutoff)
|
||||
}
|
||||
count += params.MinCommiteeSize
|
||||
}
|
||||
|
||||
// Test scenario #2: Assume there's not enough validators to fill in all the heights.
|
||||
validatorCount = 1000
|
||||
cutoffsValidators = unique(GetCutoffs(validatorCount))
|
||||
// With 1000 validators, we can't attest every height. Given min committee size is 128,
|
||||
// we can only attest 7 heights. round down 1000 / 128 equals to 7, means the length is 8.
|
||||
expectedCount = int(math.Ceil(float64(validatorCount) / params.MinCommiteeSize))
|
||||
if len(unique(cutoffsValidators)) != expectedCount {
|
||||
t.Errorf("Incorrect count for cutoffs validator. Wanted: %v, Got: %v", expectedCount, validatorCount/params.MinCommiteeSize)
|
||||
}
|
||||
|
||||
// Verify each cutoff is an increment of 142~143 (1000 / 7).
|
||||
count = 0
|
||||
for _, cutoff := range cutoffsValidators {
|
||||
num := count * validatorCount / (len(cutoffsValidators) - 1)
|
||||
if cutoff != num {
|
||||
t.Errorf("cutoffsValidators did not get correct increment. Wanted: %v, Got: %v", num, cutoff)
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEpochTransition(t *testing.T) {
|
||||
beaconChain, db := startInMemoryBeaconChain(t)
|
||||
defer db.Stop()
|
||||
@@ -652,3 +696,17 @@ func TestComputeValidatorRewardsAndPenalties(t *testing.T) {
|
||||
t.Fatalf("attester bitfields are not zeroed out: %v", beaconChain.state.ActiveState.AttesterBitfields)
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to remove duplicates in a int slice.
|
||||
func unique(ints []int) []int {
|
||||
keys := make(map[int]bool)
|
||||
list := []int{}
|
||||
for _, int := range ints {
|
||||
if _, value := keys[int]; !value {
|
||||
keys[int] = true
|
||||
list = append(list, int)
|
||||
}
|
||||
}
|
||||
return list
|
||||
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ const (
|
||||
// EpochLength is the beacon chain epoch length in slots.
|
||||
EpochLength = 64
|
||||
// ShardCount is a fixed number.
|
||||
ShardCount = 20
|
||||
ShardCount = 1024
|
||||
// DefaultBalance of a validator.
|
||||
DefaultBalance = 32000
|
||||
// DefaultSwitchDynasty value.
|
||||
DefaultSwitchDynasty = 9999999999999999999
|
||||
// MaxValidators in the protocol.
|
||||
MaxValidators = 4194304
|
||||
// NotariesPerCrosslink fixed to 100.
|
||||
NotariesPerCrosslink = 100
|
||||
// SlotLength in seconds.
|
||||
SlotLength = 8
|
||||
// SlotDuration in seconds.
|
||||
SlotDuration = 8
|
||||
// Cofactor is used cutoff algorithm to select height and shard cutoffs.
|
||||
Cofactor = 19
|
||||
// MinCommiteeSize is the minimal number of validator needs to be in a committee.
|
||||
MinCommiteeSize = 128
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user