core: introduce ValidatorsCount, make a proper count
We were completely lacking ValidatorsCount that is supposed to track the number of votes with particular count of consensus nodes which in theory can change the number of active consensus nodes (if it ever to exceed the number of standby validators), so we were not producing the right count and based on that not giving the right set of validators. Fixes #512.
This commit is contained in:
parent
357b675090
commit
c8a248596e
3 changed files with 111 additions and 49 deletions
|
@ -648,6 +648,15 @@ func processTXWithValidatorsAdd(output *transaction.Output, account *state.Accou
|
|||
return err
|
||||
}
|
||||
}
|
||||
vc, err := dao.GetValidatorsCount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vc[len(account.Votes)-1] += output.Amount
|
||||
err = dao.PutValidatorsCount(vc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -669,6 +678,17 @@ func processTXWithValidatorsSubtract(account *state.Account, dao *cachedDao, toS
|
|||
}
|
||||
}
|
||||
}
|
||||
if len(account.Votes) > 0 {
|
||||
vc, err := dao.GetValidatorsCount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vc[len(account.Votes)-1] -= toSubtract
|
||||
err = dao.PutValidatorsCount(vc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -713,6 +733,10 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(votes) > state.MaxValidatorsVoted {
|
||||
return errors.New("voting candidate limit exceeded")
|
||||
}
|
||||
if len(votes) > 0 {
|
||||
account.Votes = votes
|
||||
for _, vote := range account.Votes {
|
||||
validatorState, err := dao.GetValidatorStateOrNew(vote)
|
||||
|
@ -724,6 +748,18 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
|||
return err
|
||||
}
|
||||
}
|
||||
vc, err := dao.GetValidatorsCount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vc[len(account.Votes)-1] += balance
|
||||
err = dao.PutValidatorsCount(vc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
account.Votes = nil
|
||||
}
|
||||
return dao.PutAccountState(account)
|
||||
}
|
||||
return nil
|
||||
|
@ -1357,7 +1393,11 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
|||
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1
|
||||
})
|
||||
|
||||
count := state.GetValidatorsWeightedAverage(validators)
|
||||
validatorsCount, err := cache.GetValidatorsCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count := validatorsCount.GetWeightedAverage()
|
||||
standByValidators, err := bc.GetStandByValidators()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -266,6 +266,24 @@ func (dao *dao) DeleteValidatorState(vs *state.Validator) error {
|
|||
return dao.store.Delete(key)
|
||||
}
|
||||
|
||||
// GetValidatorsCount returns current ValidatorsCount or new one if there is none
|
||||
// in the DB.
|
||||
func (dao *dao) GetValidatorsCount() (*state.ValidatorsCount, error) {
|
||||
vc := &state.ValidatorsCount{}
|
||||
key := []byte{byte(storage.IXValidatorsCount)}
|
||||
err := dao.GetAndDecode(vc, key)
|
||||
if err != nil && err != storage.ErrKeyNotFound {
|
||||
return nil, err
|
||||
}
|
||||
return vc, nil
|
||||
}
|
||||
|
||||
// PutValidatorsCount put given ValidatorsCount in the store.
|
||||
func (dao *dao) PutValidatorsCount(vc *state.ValidatorsCount) error {
|
||||
key := []byte{byte(storage.IXValidatorsCount)}
|
||||
return dao.Put(vc, key)
|
||||
}
|
||||
|
||||
// -- end validator.
|
||||
|
||||
// -- start notification event.
|
||||
|
|
|
@ -6,6 +6,9 @@ import (
|
|||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// MaxValidatorsVoted limits the number of validators that one can vote for.
|
||||
const MaxValidatorsVoted = 1024
|
||||
|
||||
// Validator holds the state of a validator.
|
||||
type Validator struct {
|
||||
PublicKey *keys.PublicKey
|
||||
|
@ -13,6 +16,10 @@ type Validator struct {
|
|||
Votes util.Fixed8
|
||||
}
|
||||
|
||||
// ValidatorsCount represents votes with particular number of consensus nodes
|
||||
// for this number to be changeable by the voting system.
|
||||
type ValidatorsCount [MaxValidatorsVoted]util.Fixed8
|
||||
|
||||
// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes.
|
||||
func (vs *Validator) RegisteredAndHasVotes() bool {
|
||||
return vs.Registered && vs.Votes > util.Fixed8(0)
|
||||
|
@ -38,67 +45,64 @@ func (vs *Validator) DecodeBinary(reader *io.BinReader) {
|
|||
vs.Votes.DecodeBinary(reader)
|
||||
}
|
||||
|
||||
// GetValidatorsWeightedAverage applies weighted filter based on votes for validator and returns number of validators.
|
||||
// Get back to it with further investigation in https://github.com/nspcc-dev/neo-go/issues/512.
|
||||
func GetValidatorsWeightedAverage(validators []*Validator) int {
|
||||
return int(weightedAverage(applyWeightedFilter(validators)))
|
||||
}
|
||||
|
||||
// applyWeightedFilter is an implementation of the filter for validators votes.
|
||||
// C# reference https://github.com/neo-project/neo/blob/41caff115c28d6c7665b2a7ac72967e7ce82e921/neo/Helper.cs#L273
|
||||
func applyWeightedFilter(validators []*Validator) map[*Validator]float64 {
|
||||
var validatorsWithVotes []*Validator
|
||||
var amount float64
|
||||
|
||||
weightedVotes := make(map[*Validator]float64)
|
||||
start := 0.25
|
||||
end := 0.75
|
||||
sum := float64(0)
|
||||
current := float64(0)
|
||||
|
||||
for _, validator := range validators {
|
||||
if validator.Votes > util.Fixed8(0) {
|
||||
validatorsWithVotes = append(validatorsWithVotes, validator)
|
||||
amount += validator.Votes.FloatValue()
|
||||
// EncodeBinary encodes ValidatorCount to the given BinWriter.
|
||||
func (vc *ValidatorsCount) EncodeBinary(w *io.BinWriter) {
|
||||
for i := range vc {
|
||||
vc[i].EncodeBinary(w)
|
||||
}
|
||||
}
|
||||
|
||||
for _, validator := range validatorsWithVotes {
|
||||
if current >= end {
|
||||
// DecodeBinary decodes ValidatorCount from the given BinReader.
|
||||
func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) {
|
||||
for i := range vc {
|
||||
vc[i].DecodeBinary(r)
|
||||
}
|
||||
}
|
||||
|
||||
// GetWeightedAverage returns an average count of validators that's been voted
|
||||
// for not counting 1/4 of minimum and maximum numbers.
|
||||
func (vc *ValidatorsCount) GetWeightedAverage() int {
|
||||
const (
|
||||
lowerThreshold = 0.25
|
||||
upperThreshold = 0.75
|
||||
)
|
||||
var (
|
||||
sumWeight, sumValue, overallSum, slidingSum util.Fixed8
|
||||
slidingRatio float64
|
||||
)
|
||||
|
||||
for i := range vc {
|
||||
overallSum += vc[i]
|
||||
}
|
||||
|
||||
for i := range vc {
|
||||
if slidingRatio >= upperThreshold {
|
||||
break
|
||||
}
|
||||
weight := validator.Votes.FloatValue()
|
||||
sum += weight
|
||||
old := current
|
||||
current = sum / amount
|
||||
weight := vc[i]
|
||||
slidingSum += weight
|
||||
previousRatio := slidingRatio
|
||||
slidingRatio = slidingSum.FloatValue() / overallSum.FloatValue()
|
||||
|
||||
if current <= start {
|
||||
if slidingRatio <= lowerThreshold {
|
||||
continue
|
||||
}
|
||||
|
||||
if old < start {
|
||||
if current > end {
|
||||
weight = (end - start) * amount
|
||||
if previousRatio < lowerThreshold {
|
||||
if slidingRatio > upperThreshold {
|
||||
weight = util.Fixed8FromFloat((upperThreshold - lowerThreshold) * overallSum.FloatValue())
|
||||
} else {
|
||||
weight = (current - start) * amount
|
||||
weight = util.Fixed8FromFloat((slidingRatio - lowerThreshold) * overallSum.FloatValue())
|
||||
}
|
||||
} else if current > end {
|
||||
weight = (end - old) * amount
|
||||
} else if slidingRatio > upperThreshold {
|
||||
weight = util.Fixed8FromFloat((upperThreshold - previousRatio) * overallSum.FloatValue())
|
||||
}
|
||||
weightedVotes[validator] = weight
|
||||
}
|
||||
return weightedVotes
|
||||
}
|
||||
|
||||
func weightedAverage(weightedVotes map[*Validator]float64) float64 {
|
||||
sumWeight := float64(0)
|
||||
sumValue := float64(0)
|
||||
for vState, weight := range weightedVotes {
|
||||
sumWeight += weight
|
||||
sumValue += vState.Votes.FloatValue() * weight
|
||||
// Votes with N values get stored with N-1 index, thus +1 here.
|
||||
sumValue += util.Fixed8(i+1) * weight
|
||||
}
|
||||
if sumValue == 0 || sumWeight == 0 {
|
||||
return 0
|
||||
}
|
||||
return sumValue / sumWeight
|
||||
return int(sumValue / sumWeight)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue