diff --git a/state/execution.go b/state/execution.go index decadddf5..3b1056e0b 100644 --- a/state/execution.go +++ b/state/execution.go @@ -343,6 +343,73 @@ func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, return nil } +func NextValidators(currentSet *types.ValidatorSet, updates []*types.Validator) (*types.ValidatorSet, error) { + nValSet := currentSet.Copy() + // update proposer priority + nValSet.UpdateProposerPriority() + + for _, valUpdate := range updates { + // should already have been checked + if valUpdate.VotingPower < 0 { + return nil, fmt.Errorf("Voting power can't be negative %v", valUpdate) + } + + address := valUpdate.Address + _, val := nValSet.GetByAddress(address) + // valUpdate.VotingPower is ensured to be non-negative in validation method + if valUpdate.VotingPower == 0 { // remove val + _, removed := nValSet.Remove(address) + if !removed { + return nil, fmt.Errorf("Failed to remove validator %X", address) + } + } else if val == nil { // add val + // make sure we do not exceed MaxTotalVotingPower by adding this validator: + totalVotingPower := currentSet.TotalVotingPower() + updatedVotingPower := valUpdate.VotingPower + totalVotingPower + overflow := updatedVotingPower > types.MaxTotalVotingPower || updatedVotingPower < 0 + if overflow { + return nil, fmt.Errorf( + "Failed to add new validator %v. Adding it would exceed max allowed total voting power %v", + valUpdate, + types.MaxTotalVotingPower) + } + + // TODO: issue #1558 update spec according to the following: + // Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't + // unbond/rebond to reset their (potentially previously negative) ProposerPriority to zero. + // + // Contract: totalVotingPower < MaxTotalVotingPower to ensure ProposerPriority does + // not exceed the bounds of int64. + // + // Compute ProposerPriority = -1.125*totalVotingPower == -(totalVotingPower + (totalVotingPower >> 3)). + valUpdate.ProposerPriority = -(totalVotingPower + (totalVotingPower >> 3)) + added := nValSet.Add(valUpdate) + if !added { + return nil, fmt.Errorf("Failed to add new validator %v", valUpdate) + } + } else { // update val + // make sure we do not exceed MaxTotalVotingPower by updating this validator: + totalVotingPower := nValSet.TotalVotingPower() + curVotingPower := val.VotingPower + updatedVotingPower := totalVotingPower - curVotingPower + valUpdate.VotingPower + overflow := updatedVotingPower > types.MaxTotalVotingPower || updatedVotingPower < 0 + if overflow { + return nil, fmt.Errorf( + "Failed to update existing validator %v. Updating it would exceed max allowed total voting power %v", + valUpdate, + types.MaxTotalVotingPower) + } + + updated := nValSet.Update(valUpdate) + if !updated { + return nil, fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) + } + } + } + + return nValSet, nil +} + // If more or equal than 1/3 of total voting power changed in one block, then // a light client could never prove the transition externally. See // ./lite/doc.go for details on how a light client tracks validators. diff --git a/types/validator_set.go b/types/validator_set.go index 8b2d71b8d..c24bab158 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -36,7 +36,9 @@ type ValidatorSet struct { Proposer *Validator `json:"proposer"` // cached (unexported) - totalVotingPower int64 + totalVotingPower int64 + initProposerPriorities []int64 + round int } // NewValidatorSet initializes a ValidatorSet by copying over the @@ -44,13 +46,22 @@ type ValidatorSet struct { // the new ValidatorSet will have an empty list of Validators. func NewValidatorSet(valz []*Validator) *ValidatorSet { validators := make([]*Validator, len(valz)) + propPriorities := make([]int64, len(valz)) for i, val := range valz { validators[i] = val.Copy() } sort.Sort(ValidatorsByAddress(validators)) - vals := &ValidatorSet{ - Validators: validators, + + for i, val := range validators { + propPriorities[i] = val.ProposerPriority } + + vals := &ValidatorSet{ + Validators: validators, + initProposerPriorities: propPriorities, + round: 0, + } + if len(valz) > 0 { vals.IncrementProposerPriority(1) } @@ -70,6 +81,29 @@ func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet return copy } +// FindProposer computes the proposer of the given round for the validator set. +// The function for a given validator set and round number always return the same validator +// as a proposer, i.e., it is purely functional. +func (vals *ValidatorSet) FindProposer(round int) *Validator { + var proposer *Validator + if round < vals.round { + initialValSet := vals.Copy() + initialValSet.round = 0 + for i, val := range initialValSet.Validators { + val.ProposerPriority = initialValSet.initProposerPriorities[i] + } + for i := initialValSet.round; i <= round; i++ { + proposer = vals.updateProposerPriority() + } + } else { + for i := vals.round; i <= round; i++ { + proposer = vals.updateProposerPriority() + } + vals.round = round + } + return proposer +} + // IncrementProposerPriority increments ProposerPriority of each validator and updates the // proposer. Panics if validator set is empty. // `times` must be positive. @@ -93,6 +127,26 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) { vals.Proposer = proposer } +func (vals *ValidatorSet) UpdateProposerPriority() *Validator { + for _, val := range vals.Validators { + // Check for overflow for sum. + val.ProposerPriority = safeAddClip(val.ProposerPriority, val.VotingPower) + } + + validatorsHeap := cmn.NewHeap() + // just update the heap + for _, val := range vals.Validators { + validatorsHeap.PushComparable(val, proposerPriorityComparable{val}) + } + + // Decrement the validator with most ProposerPriority: + mostest := validatorsHeap.Peek().(*Validator) + // mind underflow + mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower()) + + return mostest +} + func (vals *ValidatorSet) incrementProposerPriority(subAvg bool) *Validator { for _, val := range vals.Validators { // Check for overflow for sum. @@ -255,7 +309,8 @@ func (vals *ValidatorSet) Add(val *Validator) (added bool) { vals.Validators = append(vals.Validators, val) // Invalidate cache vals.Proposer = nil - vals.totalVotingPower = 0 + //vals.totalVotingPower = 0 + vals.totalVotingPower = vals.totalVotingPower + val.VotingPower return true } else if bytes.Equal(vals.Validators[idx].Address, val.Address) { return false @@ -267,7 +322,8 @@ func (vals *ValidatorSet) Add(val *Validator) (added bool) { vals.Validators = newValidators // Invalidate cache vals.Proposer = nil - vals.totalVotingPower = 0 + //vals.totalVotingPower = 0 + vals.totalVotingPower = vals.totalVotingPower + val.VotingPower return true } } @@ -291,7 +347,7 @@ func (vals *ValidatorSet) Update(val *Validator) (updated bool) { vals.Validators[index] = val.Copy() // Invalidate cache vals.Proposer = nil - vals.totalVotingPower = 0 + vals.totalVotingPower = vals.totalVotingPower - sameVal.VotingPower + val.VotingPower return true }