Merge pull request #661 from nspcc-dev/make-voting-system-work
Make voting system work
This commit is contained in:
commit
ef870d0c9d
6 changed files with 155 additions and 88 deletions
|
@ -5,7 +5,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -437,7 +436,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil {
|
if err = cache.PutSpentCoinState(input.PrevHash, spentCoin); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = processTXWithValidatorsSubtract(account, cache, prevTXOutput.Amount); err != nil {
|
if err = processTXWithValidatorsSubtract(&prevTXOutput, account, cache); err != nil {
|
||||||
return err
|
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 {
|
func processTXWithValidatorsAdd(output *transaction.Output, account *state.Account, dao *cachedDao) error {
|
||||||
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
|
if output.AssetID.Equals(governingTokenTX().Hash()) && len(account.Votes) > 0 {
|
||||||
for _, vote := range account.Votes {
|
return modAccountVotes(account, dao, output.Amount)
|
||||||
validatorState, err := dao.GetValidatorStateOrNew(vote)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validatorState.Votes += output.Amount
|
|
||||||
if err = dao.PutValidatorState(validatorState); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
for _, vote := range account.Votes {
|
||||||
validator, err := dao.GetValidatorStateOrNew(vote)
|
validator, err := dao.GetValidatorStateOrNew(vote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
validator.Votes -= toSubtract
|
validator.Votes += value
|
||||||
if !validator.RegisteredAndHasVotes() {
|
if validator.UnregisteredAndHasNoVotes() {
|
||||||
if err := dao.DeleteValidatorState(validator); err != nil {
|
if err := dao.DeleteValidatorState(validator); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,12 +693,11 @@ func processValidatorStateDescriptor(descriptor *transaction.StateDescriptor, da
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if descriptor.Field == "Registered" {
|
if descriptor.Field == "Registered" {
|
||||||
isRegistered, err := strconv.ParseBool(string(descriptor.Value))
|
if len(descriptor.Value) == 1 {
|
||||||
if err != nil {
|
validatorState.Registered = descriptor.Value[0] != 0
|
||||||
return err
|
return dao.PutValidatorState(validatorState)
|
||||||
}
|
}
|
||||||
validatorState.Registered = isRegistered
|
return errors.New("bad descriptor value")
|
||||||
return dao.PutValidatorState(validatorState)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -706,7 +714,7 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
||||||
|
|
||||||
if descriptor.Field == "Votes" {
|
if descriptor.Field == "Votes" {
|
||||||
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
|
balance := account.GetBalanceValues()[governingTokenTX().Hash()]
|
||||||
if err = processTXWithValidatorsSubtract(account, dao, balance); err != nil {
|
if err = modAccountVotes(account, dao, -balance); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,18 +723,34 @@ func processAccountStateDescriptor(descriptor *transaction.StateDescriptor, dao
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
account.Votes = votes
|
||||||
for _, vote := range votes {
|
for _, vote := range account.Votes {
|
||||||
validator, err := dao.GetValidatorStateOrNew(vote)
|
validatorState, err := dao.GetValidatorStateOrNew(vote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := dao.PutValidatorState(validator); err != nil {
|
validatorState.Votes += balance
|
||||||
|
if err = dao.PutValidatorState(validatorState); err != nil {
|
||||||
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 nil
|
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.
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
delete(accountState.Balances, prevOutput.AssetID)
|
delete(accountState.Balances, prevOutput.AssetID)
|
||||||
|
@ -1346,8 +1370,24 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
}
|
}
|
||||||
|
|
||||||
validators := cache.GetValidators()
|
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()
|
standByValidators, err := bc.GetStandByValidators()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1363,14 +1403,15 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
pubKeys = append(pubKeys, validator.PublicKey)
|
pubKeys = append(pubKeys, validator.PublicKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(sort.Reverse(pubKeys))
|
|
||||||
if pubKeys.Len() >= count {
|
if pubKeys.Len() >= count {
|
||||||
return pubKeys[:count], nil
|
return pubKeys[:count], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := pubKeys.Unique()
|
result := pubKeys.Unique()
|
||||||
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
|
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
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +16,20 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
// EncodeBinary encodes Validator to the given BinWriter.
|
||||||
func (vs *Validator) EncodeBinary(bw *io.BinWriter) {
|
func (vs *Validator) EncodeBinary(bw *io.BinWriter) {
|
||||||
vs.PublicKey.EncodeBinary(bw)
|
vs.PublicKey.EncodeBinary(bw)
|
||||||
|
@ -33,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)
|
// GetWeightedAverage returns an average count of validators that's been voted
|
||||||
start := 0.25
|
// for not counting 1/4 of minimum and maximum numbers.
|
||||||
end := 0.75
|
func (vc *ValidatorsCount) GetWeightedAverage() int {
|
||||||
sum := float64(0)
|
const (
|
||||||
current := float64(0)
|
lowerThreshold = 0.25
|
||||||
|
upperThreshold = 0.75
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
sumWeight, sumValue, overallSum, slidingSum util.Fixed8
|
||||||
|
slidingRatio float64
|
||||||
|
)
|
||||||
|
|
||||||
for _, validator := range validators {
|
for i := range vc {
|
||||||
if validator.Votes > util.Fixed8(0) {
|
overallSum += vc[i]
|
||||||
validatorsWithVotes = append(validatorsWithVotes, validator)
|
|
||||||
amount += validator.Votes.FloatValue()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,14 +26,14 @@ func (s *StateDescriptor) DecodeBinary(r *io.BinReader) {
|
||||||
s.Type = DescStateType(r.ReadB())
|
s.Type = DescStateType(r.ReadB())
|
||||||
|
|
||||||
s.Key = r.ReadVarBytes()
|
s.Key = r.ReadVarBytes()
|
||||||
s.Value = r.ReadVarBytes()
|
|
||||||
s.Field = r.ReadString()
|
s.Field = r.ReadString()
|
||||||
|
s.Value = r.ReadVarBytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements Serializable interface.
|
// EncodeBinary implements Serializable interface.
|
||||||
func (s *StateDescriptor) EncodeBinary(w *io.BinWriter) {
|
func (s *StateDescriptor) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteB(byte(s.Type))
|
w.WriteB(byte(s.Type))
|
||||||
w.WriteVarBytes(s.Key)
|
w.WriteVarBytes(s.Key)
|
||||||
w.WriteVarBytes(s.Value)
|
|
||||||
w.WriteString(s.Field)
|
w.WriteString(s.Field)
|
||||||
|
w.WriteVarBytes(s.Value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@ func TestEncodeDecodeState(t *testing.T) {
|
||||||
assert.Equal(t, 1, len(s.Descriptors))
|
assert.Equal(t, 1, len(s.Descriptors))
|
||||||
descriptor := s.Descriptors[0]
|
descriptor := s.Descriptors[0]
|
||||||
assert.Equal(t, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1", hex.EncodeToString(descriptor.Key))
|
assert.Equal(t, "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1", hex.EncodeToString(descriptor.Key))
|
||||||
assert.Equal(t, "52656769737465726564", hex.EncodeToString(descriptor.Value))
|
assert.Equal(t, "Registered", descriptor.Field)
|
||||||
assert.Equal(t, "\x01", descriptor.Field)
|
assert.Equal(t, []byte{0x01}, descriptor.Value)
|
||||||
assert.Equal(t, Validator, descriptor.Type)
|
assert.Equal(t, Validator, descriptor.Type)
|
||||||
|
|
||||||
// Encode
|
// Encode
|
||||||
|
|
|
@ -22,21 +22,11 @@ type PublicKeys []*PublicKey
|
||||||
func (keys PublicKeys) Len() int { return len(keys) }
|
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) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] }
|
||||||
func (keys PublicKeys) Less(i, j int) bool {
|
func (keys PublicKeys) Less(i, j int) bool {
|
||||||
if keys[i].X.Cmp(keys[j].X) == -1 {
|
return keys[i].Cmp(keys[j]) == -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBytes decodes a PublicKeys from the given slice of bytes.
|
// 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 := io.NewBinReaderFromBuf(data)
|
||||||
b.ReadArray(keys)
|
b.ReadArray(keys)
|
||||||
return b.Err
|
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
|
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
|
// NewPublicKeyFromString returns a public key created from the
|
||||||
// given hex string.
|
// given hex string.
|
||||||
func NewPublicKeyFromString(s string) (*PublicKey, error) {
|
func NewPublicKeyFromString(s string) (*PublicKey, error) {
|
||||||
|
|
Loading…
Reference in a new issue