diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 43d716114..0c1ad2495 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -5,7 +5,6 @@ import ( "math" "math/big" "sort" - "strconv" "sync" "sync/atomic" "time" @@ -437,7 +436,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil { return err } - if err = processTXWithValidatorsSubtract(account, cache, prevTXOutput.Amount); err != nil { + if err = processTXWithValidatorsSubtract(&prevTXOutput, account, cache); err != nil { return err } } @@ -639,28 +638,27 @@ func processOutputs(tx *transaction.Transaction, dao *cachedDao) error { func processTXWithValidatorsAdd(output *transaction.Output, account *state.Account, dao *cachedDao) error { if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { - for _, vote := range account.Votes { - validatorState, err := dao.GetValidatorStateOrNew(vote) - if err != nil { - return err - } - validatorState.Votes += output.Amount - if err = dao.PutValidatorState(validatorState); err != nil { - return err - } - } + return modAccountVotes(account, dao, output.Amount) } return nil } -func processTXWithValidatorsSubtract(account *state.Account, dao *cachedDao, toSubtract util.Fixed8) error { +func processTXWithValidatorsSubtract(output *transaction.Output, account *state.Account, dao *cachedDao) error { + if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 { + return modAccountVotes(account, dao, -output.Amount) + } + return nil +} + +// modAccountVotes adds given value to given account voted validators. +func modAccountVotes(account *state.Account, dao *cachedDao, value util.Fixed8) error { for _, vote := range account.Votes { validator, err := dao.GetValidatorStateOrNew(vote) if err != nil { return err } - validator.Votes -= toSubtract - if !validator.RegisteredAndHasVotes() { + validator.Votes += value + if validator.UnregisteredAndHasNoVotes() { if err := dao.DeleteValidatorState(validator); err != nil { return err } @@ -670,6 +668,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] += value + err = dao.PutValidatorsCount(vc) + if err != nil { + return err + } + } return nil } @@ -684,12 +693,11 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, da return err } if descriptor.Field == "Registered" { - isRegistered, err := strconv.ParseBool(string(descriptor.Value)) - if err != nil { - return err + if len(descriptor.Value) == 1 { + validatorState.Registered = descriptor.Value[0] != 0 + return dao.PutValidatorState(validatorState) } - validatorState.Registered = isRegistered - return dao.PutValidatorState(validatorState) + return errors.New("bad descriptor value") } return nil } @@ -706,7 +714,7 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao if descriptor.Field == "Votes" { balance := account.GetBalanceValues()[governingTokenTX().Hash()] - if err = processTXWithValidatorsSubtract(account, dao, balance); err != nil { + if err = modAccountVotes(account, dao, -balance); err != nil { return err } @@ -715,18 +723,34 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao if err != nil { return err } - if votes.Len() != len(account.Votes) { + if len(votes) > state.MaxValidatorsVoted { + return errors.New("voting candidate limit exceeded") + } + if len(votes) > 0 { account.Votes = votes - for _, vote := range votes { - validator, err := dao.GetValidatorStateOrNew(vote) + for _, vote := range account.Votes { + validatorState, err := dao.GetValidatorStateOrNew(vote) if err != nil { return err } - if err := dao.PutValidatorState(validator); err != nil { + validatorState.Votes += balance + if err = dao.PutValidatorState(validatorState); err != nil { 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 } @@ -1322,7 +1346,7 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P } // process account state votes: if there are any -> validators will be updated. - if err = processTXWithValidatorsSubtract(accountState, cache, prevOutput.Amount); err != nil { + if err = processTXWithValidatorsSubtract(&prevOutput, accountState, cache); err != nil { return nil, err } delete(accountState.Balances, prevOutput.AssetID) @@ -1346,8 +1370,24 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P } validators := cache.GetValidators() + sort.Slice(validators, func(i, j int) bool { + // Unregistered validators go to the end of the list. + if validators[i].Registered != validators[j].Registered { + return validators[i].Registered + } + // The most-voted validators should end up in the front of the list. + if validators[i].Votes != validators[j].Votes { + return validators[i].Votes > validators[j].Votes + } + // Ties are broken with public keys. + 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 @@ -1363,14 +1403,15 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P pubKeys = append(pubKeys, validator.PublicKey) } } - sort.Sort(sort.Reverse(pubKeys)) if pubKeys.Len() >= count { return pubKeys[:count], nil } result := pubKeys.Unique() for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ { - result = append(result, uniqueSBValidators[i]) + if !result.Contains(uniqueSBValidators[i]) { + result = append(result, uniqueSBValidators[i]) + } } return result, nil } diff --git a/pkg/core/dao.go b/pkg/core/dao.go index 4111fba66..1ff5d5e5e 100644 --- a/pkg/core/dao.go +++ b/pkg/core/dao.go @@ -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. diff --git a/pkg/core/state/validator.go b/pkg/core/state/validator.go index bd3414a1f..0fdb73c47 100644 --- a/pkg/core/state/validator.go +++ b/pkg/core/state/validator.go @@ -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,11 +16,20 @@ 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) } +// UnregisteredAndHasNoVotes returns true when Validator is not registered and has no votes. +func (vs *Validator) UnregisteredAndHasNoVotes() bool { + return !vs.Registered && vs.Votes == 0 +} + // EncodeBinary encodes Validator to the given BinWriter. func (vs *Validator) EncodeBinary(bw *io.BinWriter) { vs.PublicKey.EncodeBinary(bw) @@ -33,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))) +// EncodeBinary encodes ValidatorCount to the given BinWriter. +func (vc *ValidatorsCount) EncodeBinary(w *io.BinWriter) { + for i := range vc { + vc[i].EncodeBinary(w) + } } -// 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 +// DecodeBinary decodes ValidatorCount from the given BinReader. +func (vc *ValidatorsCount) DecodeBinary(r *io.BinReader) { + for i := range vc { + vc[i].DecodeBinary(r) + } +} - weightedVotes := make(map[*Validator]float64) - start := 0.25 - end := 0.75 - sum := float64(0) - current := float64(0) +// 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 _, validator := range validators { - if validator.Votes > util.Fixed8(0) { - validatorsWithVotes = append(validatorsWithVotes, validator) - amount += validator.Votes.FloatValue() - } + for i := range vc { + overallSum += vc[i] } - for _, validator := range validatorsWithVotes { - if current >= end { + 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) } diff --git a/pkg/core/transaction/state_descriptor.go b/pkg/core/transaction/state_descriptor.go index a839d17d2..05f0a7f12 100644 --- a/pkg/core/transaction/state_descriptor.go +++ b/pkg/core/transaction/state_descriptor.go @@ -26,14 +26,14 @@ func (s *StateDescriptor) DecodeBinary(r *io.BinReader) { s.Type = DescStateType(r.ReadB()) s.Key = r.ReadVarBytes() - s.Value = r.ReadVarBytes() s.Field = r.ReadString() + s.Value = r.ReadVarBytes() } // EncodeBinary implements Serializable interface. func (s *StateDescriptor) EncodeBinary(w *io.BinWriter) { w.WriteB(byte(s.Type)) w.WriteVarBytes(s.Key) - w.WriteVarBytes(s.Value) w.WriteString(s.Field) + w.WriteVarBytes(s.Value) } diff --git a/pkg/core/transaction/state_test.go b/pkg/core/transaction/state_test.go index 0c01406cd..53fb17e7c 100644 --- a/pkg/core/transaction/state_test.go +++ b/pkg/core/transaction/state_test.go @@ -25,8 +25,8 @@ func TestEncodeDecodeState(t *testing.T) { assert.Equal(t, 1, len(s.Descriptors)) descriptor := s.Descriptors[0] assert.Equal(t, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1", hex.EncodeToString(descriptor.Key)) - assert.Equal(t, "52656769737465726564", hex.EncodeToString(descriptor.Value)) - assert.Equal(t, "\x01", descriptor.Field) + assert.Equal(t, "Registered", descriptor.Field) + assert.Equal(t, []byte{0x01}, descriptor.Value) assert.Equal(t, Validator, descriptor.Type) // Encode diff --git a/pkg/crypto/keys/publickey.go b/pkg/crypto/keys/publickey.go index 422efadca..b22e2f9f3 100644 --- a/pkg/crypto/keys/publickey.go +++ b/pkg/crypto/keys/publickey.go @@ -22,21 +22,11 @@ type PublicKeys []*PublicKey func (keys PublicKeys) Len() int { return len(keys) } func (keys PublicKeys) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] } func (keys PublicKeys) Less(i, j int) bool { - if keys[i].X.Cmp(keys[j].X) == -1 { - return true - } - if keys[i].X.Cmp(keys[j].X) == 1 { - return false - } - if keys[i].X.Cmp(keys[j].X) == 0 { - return false - } - - return keys[i].Y.Cmp(keys[j].Y) == -1 + return keys[i].Cmp(keys[j]) == -1 } // DecodeBytes decodes a PublicKeys from the given slice of bytes. -func (keys PublicKeys) DecodeBytes(data []byte) error { +func (keys *PublicKeys) DecodeBytes(data []byte) error { b := io.NewBinReaderFromBuf(data) b.ReadArray(keys) return b.Err @@ -75,6 +65,15 @@ func (p *PublicKey) Equal(key *PublicKey) bool { return p.X.Cmp(key.X) == 0 && p.Y.Cmp(key.Y) == 0 } +// Cmp compares two keys. +func (p *PublicKey) Cmp(key *PublicKey) int { + xCmp := p.X.Cmp(key.X) + if xCmp != 0 { + return xCmp + } + return p.Y.Cmp(key.Y) +} + // NewPublicKeyFromString returns a public key created from the // given hex string. func NewPublicKeyFromString(s string) (*PublicKey, error) {