The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
woop/consensus/votepower/roster.go

212 lines
6.2 KiB

package votepower
import (
"encoding/json"
"sort"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors"
)
var (
// HarmonysShare ..
HarmonysShare = numeric.MustNewDecFromStr("0.68")
// StakersShare ..
StakersShare = numeric.MustNewDecFromStr("0.32")
// ErrVotingPowerNotEqualOne ..
ErrVotingPowerNotEqualOne = errors.New("voting power not equal to one")
)
type stakedVoter struct {
IsActive bool `json:"is-active"`
IsHarmonyNode bool `json:"is-harmony"`
EarningAccount common.Address `json:"earning-account"`
Identity shard.BlsPublicKey `json:"bls-public-key"`
EffectivePercent numeric.Dec `json:"voting"`
EffectiveStake numeric.Dec `json:"effective-stake"`
}
// Roster ..
type Roster struct {
Voters map[shard.BlsPublicKey]stakedVoter
OurVotingPowerTotalPercentage numeric.Dec
TheirVotingPowerTotalPercentage numeric.Dec
RawStakedTotal numeric.Dec
HmySlotCount int64
}
// Staker ..
type Staker struct {
AverageVotingPower numeric.Dec
AverageEffectiveStake numeric.Dec
TotalEffectiveStake numeric.Dec
VotingPower []staking.VotePerShard
BLSPublicKeysOwned []staking.KeysPerShard
}
// RosterPerShard ..
type RosterPerShard struct {
ShardID uint32
Record *Roster
}
// AggregateRosters ..
func AggregateRosters(rosters []RosterPerShard) map[common.Address]Staker {
result := map[common.Address]Staker{}
c := int64(len(rosters))
sort.SliceStable(rosters,
func(i, j int) bool { return rosters[i].ShardID < rosters[j].ShardID },
)
for _, roster := range rosters {
for key, value := range roster.Record.Voters {
if !value.IsHarmonyNode {
payload, alreadyExists := result[value.EarningAccount]
if alreadyExists {
payload.TotalEffectiveStake = payload.TotalEffectiveStake.Add(
value.EffectiveStake,
)
payload.AverageVotingPower = payload.AverageVotingPower.Add(
value.EffectivePercent,
)
payload.VotingPower = append(payload.VotingPower,
staking.VotePerShard{roster.ShardID, value.EffectivePercent},
)
payload.BLSPublicKeysOwned[roster.ShardID].Keys = append(
payload.BLSPublicKeysOwned[roster.ShardID].Keys, key,
)
} else {
result[value.EarningAccount] = Staker{
AverageVotingPower: value.EffectivePercent,
AverageEffectiveStake: numeric.ZeroDec(),
TotalEffectiveStake: value.EffectiveStake,
VotingPower: []staking.VotePerShard{
{roster.ShardID, value.EffectivePercent},
},
BLSPublicKeysOwned: []staking.KeysPerShard{
{roster.ShardID, []shard.BlsPublicKey{key}}},
}
}
}
}
}
for _, value := range result {
value.AverageEffectiveStake = value.TotalEffectiveStake.QuoInt64(c)
value.AverageVotingPower = value.AverageVotingPower.QuoInt64(c)
}
return result
}
// JSON dump
func (r *Roster) JSON() string {
v := map[string]stakedVoter{}
for k, value := range r.Voters {
v[k.Hex()] = value
}
c := struct {
Voters map[string]stakedVoter `json:"voters"`
Our string `json:"ours"`
Their string `json:"theirs"`
Raw string `json:"raw-total"`
}{
v,
r.OurVotingPowerTotalPercentage.String(),
r.TheirVotingPowerTotalPercentage.String(),
r.RawStakedTotal.String(),
}
b, _ := json.Marshal(&c)
return string(b)
}
// Compute creates a new roster based off the shard.SlotList
func Compute(staked shard.SlotList) (*Roster, error) {
roster := NewRoster()
for i := range staked {
if staked[i].TotalStake == nil {
roster.HmySlotCount++
} else {
roster.RawStakedTotal = roster.RawStakedTotal.Add(
*staked[i].TotalStake,
)
}
}
// TODO Check for duplicate BLS Keys
ourCount := numeric.NewDec(roster.HmySlotCount)
ourPercentage := numeric.ZeroDec()
theirPercentage := numeric.ZeroDec()
var lastStakedVoter *stakedVoter
for i := range staked {
member := stakedVoter{
IsActive: true,
IsHarmonyNode: true,
EarningAccount: staked[i].EcdsaAddress,
Identity: staked[i].BlsPublicKey,
EffectivePercent: numeric.ZeroDec(),
EffectiveStake: numeric.ZeroDec(),
}
// Real Staker
if staked[i].TotalStake != nil {
member.IsHarmonyNode = false
member.EffectiveStake = member.EffectiveStake.Add(*staked[i].TotalStake)
member.EffectivePercent = staked[i].TotalStake.
Quo(roster.RawStakedTotal).
Mul(StakersShare)
theirPercentage = theirPercentage.Add(member.EffectivePercent)
lastStakedVoter = &member
} else { // Our node
member.EffectivePercent = HarmonysShare.Quo(ourCount)
ourPercentage = ourPercentage.Add(member.EffectivePercent)
}
roster.Voters[staked[i].BlsPublicKey] = member
}
// NOTE Enforce voting power sums to one, give diff (expect tiny amt) to last staked voter
if diff := numeric.OneDec().Sub(
ourPercentage.Add(theirPercentage),
); !diff.IsZero() && lastStakedVoter != nil {
utils.Logger().Info().
Str("theirs", theirPercentage.String()).
Str("ours", ourPercentage.String()).
Str("diff", diff.String()).
Str("combined", theirPercentage.Add(diff).Add(ourPercentage).String()).
Str("bls-public-key-of-receipent", lastStakedVoter.Identity.Hex()).
Msg("voting power of hmy & staked slots not sum to 1, giving diff to staked slot")
lastStakedVoter.EffectivePercent = lastStakedVoter.EffectivePercent.Add(diff)
theirPercentage = theirPercentage.Add(diff)
}
if lastStakedVoter != nil &&
!ourPercentage.Add(theirPercentage).Equal(numeric.OneDec()) {
utils.Logger().Error().
Str("theirs", theirPercentage.String()).
Str("ours", ourPercentage.String()).
Msg("Total voting power not equal 100 percent")
return nil, ErrVotingPowerNotEqualOne
}
roster.OurVotingPowerTotalPercentage = ourPercentage
roster.TheirVotingPowerTotalPercentage = theirPercentage
return roster, nil
}
// NewRoster ..
func NewRoster() *Roster {
return &Roster{
map[shard.BlsPublicKey]stakedVoter{},
numeric.ZeroDec(),
numeric.ZeroDec(),
numeric.ZeroDec(),
0,
}
}