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
|
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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,6 +733,10 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(votes) > state.MaxValidatorsVoted {
|
||||||
|
return errors.New("voting candidate limit exceeded")
|
||||||
|
}
|
||||||
|
if len(votes) > 0 {
|
||||||
account.Votes = votes
|
account.Votes = votes
|
||||||
for _, vote := range account.Votes {
|
for _, vote := range account.Votes {
|
||||||
validatorState, err := dao.GetValidatorStateOrNew(vote)
|
validatorState, err := dao.GetValidatorStateOrNew(vote)
|
||||||
|
@ -724,6 +748,18 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
||||||
return err
|
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 dao.PutAccountState(account)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1357,7 +1393,11 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1
|
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()
|
standByValidators, err := bc.GetStandByValidators()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -266,6 +266,24 @@ func (dao *dao) DeleteValidatorState(vs *state.Validator) error {
|
||||||
return dao.store.Delete(key)
|
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.
|
// -- end validator.
|
||||||
|
|
||||||
// -- start notification event.
|
// -- start notification event.
|
||||||
|
|
|
@ -6,6 +6,9 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"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.
|
// Validator holds the state of a validator.
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
PublicKey *keys.PublicKey
|
PublicKey *keys.PublicKey
|
||||||
|
@ -13,6 +16,10 @@ type Validator struct {
|
||||||
Votes util.Fixed8
|
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.
|
// RegisteredAndHasVotes returns true or false whether Validator is registered and has votes.
|
||||||
func (vs *Validator) RegisteredAndHasVotes() bool {
|
func (vs *Validator) RegisteredAndHasVotes() bool {
|
||||||
return vs.Registered && vs.Votes > util.Fixed8(0)
|
return vs.Registered && vs.Votes > util.Fixed8(0)
|
||||||
|
@ -38,67 +45,64 @@ func (vs *Validator) DecodeBinary(reader *io.BinReader) {
|
||||||
vs.Votes.DecodeBinary(reader)
|
vs.Votes.DecodeBinary(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValidatorsWeightedAverage applies weighted filter based on votes for validator and returns number of validators.
|
// EncodeBinary encodes ValidatorCount to the given BinWriter.
|
||||||
// Get back to it with further investigation in https://github.com/nspcc-dev/neo-go/issues/512.
|
func (vc *ValidatorsCount) EncodeBinary(w *io.BinWriter) {
|
||||||
func GetValidatorsWeightedAverage(validators []*Validator) int {
|
for i := range vc {
|
||||||
return int(weightedAverage(applyWeightedFilter(validators)))
|
vc[i].EncodeBinary(w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyWeightedFilter is an implementation of the filter for validators votes.
|
// DecodeBinary decodes ValidatorCount from the given BinReader.
|
||||||
// C# reference https://github.com/neo-project/neo/blob/41caff115c28d6c7665b2a7ac72967e7ce82e921/neo/Helper.cs#L273
|
func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) {
|
||||||
func applyWeightedFilter(validators []*Validator) map[*Validator]float64 {
|
for i := range vc {
|
||||||
var validatorsWithVotes []*Validator
|
vc[i].DecodeBinary(r)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 _, validator := range validatorsWithVotes {
|
for i := range vc {
|
||||||
if current >= end {
|
if slidingRatio >= upperThreshold {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
weight := validator.Votes.FloatValue()
|
weight := vc[i]
|
||||||
sum += weight
|
slidingSum += weight
|
||||||
old := current
|
previousRatio := slidingRatio
|
||||||
current = sum / amount
|
slidingRatio = slidingSum.FloatValue() / overallSum.FloatValue()
|
||||||
|
|
||||||
if current <= start {
|
if slidingRatio <= lowerThreshold {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if old < start {
|
if previousRatio < lowerThreshold {
|
||||||
if current > end {
|
if slidingRatio > upperThreshold {
|
||||||
weight = (end - start) * amount
|
weight = util.Fixed8FromFloat((upperThreshold - lowerThreshold) * overallSum.FloatValue())
|
||||||
} else {
|
} else {
|
||||||
weight = (current - start) * amount
|
weight = util.Fixed8FromFloat((slidingRatio - lowerThreshold) * overallSum.FloatValue())
|
||||||
}
|
}
|
||||||
} else if current > end {
|
} else if slidingRatio > upperThreshold {
|
||||||
weight = (end - old) * amount
|
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
|
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 {
|
if sumValue == 0 || sumWeight == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return sumValue / sumWeight
|
return int(sumValue / sumWeight)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue