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

227 lines
6.2 KiB

package votepower
import (
"encoding/hex"
"encoding/json"
"sort"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/bls/ffi/go/bls"
"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")
)
// Ballot is a vote cast by a validator
type Ballot struct {
SignerPubKey shard.BlsPublicKey `json:"bls-public-key"`
BlockHeaderHash common.Hash `json:"block-header-hash"`
Signature []byte `json:"bls-signature"`
}
// MarshalJSON ..
func (b Ballot) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
A string `json:"bls-public-key"`
B string `json:"block-header-hash"`
C string `json:"bls-signature"`
}{
b.SignerPubKey.Hex(),
b.BlockHeaderHash.Hex(),
hex.EncodeToString(b.Signature),
})
}
// Round is a round of voting in any FBFT phase
type Round struct {
AggregatedVote *bls.Sign
BallotBox map[string]*Ballot
}
func (b Ballot) String() string {
data, _ := json.Marshal(b)
return string(data)
}
// NewRound ..
func NewRound() *Round {
return &Round{AggregatedVote: nil, BallotBox: map[string]*Ballot{}}
}
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 {
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{}
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.VotingPower = append(payload.VotingPower,
staking.VotePerShard{roster.ShardID, value.EffectivePercent},
)
for i := range payload.BLSPublicKeysOwned {
if payload.BLSPublicKeysOwned[i].ShardID == roster.ShardID {
payload.BLSPublicKeysOwned[i].Keys = append(
payload.BLSPublicKeysOwned[i].Keys, key,
)
}
}
} else {
result[value.EarningAccount] = Staker{
TotalEffectiveStake: value.EffectiveStake,
VotingPower: []staking.VotePerShard{
{roster.ShardID, value.EffectivePercent},
},
BLSPublicKeysOwned: []staking.KeysPerShard{
{roster.ShardID, []shard.BlsPublicKey{key}}},
}
}
}
}
}
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].EffectiveStake == nil {
roster.HmySlotCount++
} else {
roster.RawStakedTotal = roster.RawStakedTotal.Add(
*staked[i].EffectiveStake,
)
}
}
// 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].EffectiveStake != nil {
member.IsHarmonyNode = false
member.EffectiveStake = member.EffectiveStake.Add(*staked[i].EffectiveStake)
member.EffectivePercent = staked[i].EffectiveStake.
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 {
lastStakedVoter.EffectivePercent = lastStakedVoter.EffectivePercent.Add(diff)
theirPercentage = theirPercentage.Add(diff)
}
if lastStakedVoter != nil &&
!ourPercentage.Add(theirPercentage).Equal(numeric.OneDec()) {
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,
}
}