|
|
|
package quorum
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"math/big"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/harmony-one/bls/ffi/go/bls"
|
|
|
|
"github.com/harmony-one/harmony/internal/utils"
|
|
|
|
"github.com/harmony-one/harmony/numeric"
|
|
|
|
"github.com/harmony-one/harmony/shard"
|
|
|
|
"github.com/harmony-one/harmony/staking/slash"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
twoThird = numeric.NewDec(2).Quo(numeric.NewDec(3))
|
|
|
|
ninetyPercent = numeric.MustNewDecFromStr("0.90")
|
|
|
|
harmonysShare = numeric.MustNewDecFromStr("0.68")
|
|
|
|
stakersShare = numeric.MustNewDecFromStr("0.32")
|
|
|
|
totalShare = numeric.MustNewDecFromStr("1.00")
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO Test the case where we have 33 nodes, 68/33 will give precision hell and it should trigger
|
|
|
|
// the 100% mismatch err.
|
|
|
|
|
|
|
|
// TallyResult is the result of when we calculate voting power,
|
|
|
|
// recall that it happens to us at epoch change
|
|
|
|
type TallyResult struct {
|
|
|
|
ourPercent numeric.Dec
|
|
|
|
theirPercent numeric.Dec
|
|
|
|
}
|
|
|
|
|
|
|
|
type stakedVoter struct {
|
|
|
|
isActive, isHarmonyNode bool
|
|
|
|
earningAccount common.Address
|
|
|
|
effectivePercent numeric.Dec
|
|
|
|
rawStake numeric.Dec
|
|
|
|
}
|
|
|
|
|
|
|
|
type stakedVoteWeight struct {
|
|
|
|
SignatureReader
|
|
|
|
DependencyInjectionWriter
|
|
|
|
DependencyInjectionReader
|
|
|
|
slash.ThresholdDecider
|
|
|
|
voters map[shard.BlsPublicKey]stakedVoter
|
|
|
|
ourVotingPowerTotal numeric.Dec
|
|
|
|
theirVotingPowerTotal numeric.Dec
|
|
|
|
stakedTotal numeric.Dec
|
|
|
|
hmySlotCount int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// Policy ..
|
|
|
|
func (v *stakedVoteWeight) Policy() Policy {
|
|
|
|
return SuperMajorityStake
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsQuorumAchieved ..
|
|
|
|
func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool {
|
|
|
|
t := v.QuorumThreshold()
|
|
|
|
currentTotalPower := v.computeCurrentTotalPower(p)
|
|
|
|
|
|
|
|
utils.Logger().Info().
|
|
|
|
Str("policy", v.Policy().String()).
|
|
|
|
Str("phase", p.String()).
|
|
|
|
Str("threshold", t.String()).
|
|
|
|
Str("total-power-of-signers", currentTotalPower.String()).
|
|
|
|
Msg("Attempt to reach quorum")
|
|
|
|
return currentTotalPower.GT(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *stakedVoteWeight) computeCurrentTotalPower(p Phase) numeric.Dec {
|
|
|
|
w := shard.BlsPublicKey{}
|
|
|
|
members := v.Participants()
|
|
|
|
currentTotalPower := numeric.ZeroDec()
|
|
|
|
|
|
|
|
for i := range members {
|
|
|
|
if v.ReadSignature(p, members[i]) != nil {
|
|
|
|
w.FromLibBLSPublicKey(members[i])
|
|
|
|
|
|
|
|
voter, ok := v.voters[w]
|
|
|
|
if !ok {
|
|
|
|
utils.Logger().Error().Bytes("BlsPubKey", w[:]).Msg("No voter found")
|
|
|
|
return numeric.ZeroDec()
|
|
|
|
}
|
|
|
|
currentTotalPower = currentTotalPower.Add(
|
|
|
|
voter.effectivePercent,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return currentTotalPower
|
|
|
|
}
|
|
|
|
|
|
|
|
// QuorumThreshold ..
|
|
|
|
func (v *stakedVoteWeight) QuorumThreshold() numeric.Dec {
|
|
|
|
return twoThird
|
|
|
|
}
|
|
|
|
|
|
|
|
// RewardThreshold ..
|
|
|
|
func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool {
|
|
|
|
return v.computeCurrentTotalPower(Commit).GTE(ninetyPercent)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Award ..
|
|
|
|
func (v *stakedVoteWeight) Award(
|
|
|
|
Pie *big.Int, earners []common.Address, hook func(earner common.Address, due *big.Int),
|
|
|
|
) *big.Int {
|
|
|
|
payout := big.NewInt(0)
|
|
|
|
last := big.NewInt(0)
|
|
|
|
count := big.NewInt(int64(len(earners)))
|
|
|
|
// proportional := map[common.Address]numeric.Dec{}
|
|
|
|
|
|
|
|
for _, voter := range v.voters {
|
|
|
|
if voter.isHarmonyNode == false {
|
|
|
|
// proportional[details.earningAccount] = details.effective.QuoTruncate(
|
|
|
|
// v.stakedTotal,
|
|
|
|
// )
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO Finish implementing this logic w/Chao
|
|
|
|
|
|
|
|
for i := range earners {
|
|
|
|
cur := big.NewInt(0)
|
|
|
|
|
|
|
|
cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count)
|
|
|
|
|
|
|
|
diff := big.NewInt(0).Sub(cur, last)
|
|
|
|
|
|
|
|
// hook(common.Address(account), diff)
|
|
|
|
|
|
|
|
payout = big.NewInt(0).Add(payout, diff)
|
|
|
|
|
|
|
|
last = cur
|
|
|
|
}
|
|
|
|
|
|
|
|
return payout
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
errSumOfVotingPowerNotOne = errors.New("sum of total votes do not sum to 100 percent")
|
|
|
|
errSumOfOursAndTheirsNotOne = errors.New(
|
|
|
|
"sum of hmy nodes and stakers do not sum to 100 percent",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
func (v *stakedVoteWeight) SetVoters(
|
|
|
|
staked shard.SlotList,
|
|
|
|
) (*TallyResult, error) {
|
|
|
|
s, _ := v.ShardIDProvider()()
|
|
|
|
v.voters = map[shard.BlsPublicKey]stakedVoter{}
|
|
|
|
v.Reset([]Phase{Prepare, Commit, ViewChange})
|
|
|
|
v.hmySlotCount = 0
|
|
|
|
v.stakedTotal = numeric.ZeroDec()
|
|
|
|
|
|
|
|
for i := range staked {
|
|
|
|
if staked[i].TotalStake == nil {
|
|
|
|
v.hmySlotCount++
|
|
|
|
} else {
|
|
|
|
v.stakedTotal = v.stakedTotal.Add(*staked[i].TotalStake)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ourCount := numeric.NewDec(v.hmySlotCount)
|
|
|
|
ourPercentage := numeric.ZeroDec()
|
|
|
|
theirPercentage := numeric.ZeroDec()
|
|
|
|
totalStakedPercent := numeric.ZeroDec()
|
|
|
|
|
|
|
|
for i := range staked {
|
|
|
|
member := stakedVoter{
|
|
|
|
isActive: true,
|
|
|
|
isHarmonyNode: true,
|
|
|
|
earningAccount: staked[i].EcdsaAddress,
|
|
|
|
effectivePercent: numeric.ZeroDec(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Real Staker
|
|
|
|
if staked[i].TotalStake != nil {
|
|
|
|
member.isHarmonyNode = false
|
|
|
|
member.effectivePercent = staked[i].TotalStake.
|
|
|
|
Quo(v.stakedTotal).
|
|
|
|
Mul(stakersShare)
|
|
|
|
theirPercentage = theirPercentage.Add(member.effectivePercent)
|
|
|
|
} else { // Our node
|
|
|
|
member.effectivePercent = harmonysShare.Quo(ourCount)
|
|
|
|
ourPercentage = ourPercentage.Add(member.effectivePercent)
|
|
|
|
}
|
|
|
|
|
|
|
|
totalStakedPercent = totalStakedPercent.Add(member.effectivePercent)
|
|
|
|
v.voters[staked[i].BlsPublicKey] = member
|
|
|
|
}
|
|
|
|
|
|
|
|
utils.Logger().Info().
|
|
|
|
Str("our-percentage", ourPercentage.String()).
|
|
|
|
Str("their-percentage", theirPercentage.String()).
|
|
|
|
Uint32("on-shard", s).
|
|
|
|
Str("Raw-Staked", v.stakedTotal.String()).
|
|
|
|
Msg("Total staked")
|
|
|
|
|
|
|
|
//switch {
|
|
|
|
//case totalStakedPercent.Equal(totalShare) == false:
|
|
|
|
// return nil, errSumOfVotingPowerNotOne
|
|
|
|
//case ourPercentage.Add(theirPercentage).Equal(totalShare) == false:
|
|
|
|
// return nil, errSumOfOursAndTheirsNotOne
|
|
|
|
//}
|
|
|
|
|
|
|
|
// Hold onto this calculation
|
|
|
|
v.ourVotingPowerTotal = ourPercentage
|
|
|
|
v.theirVotingPowerTotal = theirPercentage
|
|
|
|
|
|
|
|
return &TallyResult{ourPercentage, theirPercentage}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *stakedVoteWeight) ToggleActive(k *bls.PublicKey) bool {
|
|
|
|
w := shard.BlsPublicKey{}
|
|
|
|
w.FromLibBLSPublicKey(k)
|
|
|
|
g := v.voters[w]
|
|
|
|
g.isActive = !g.isActive
|
|
|
|
v.voters[w] = g
|
|
|
|
return v.voters[w].isActive
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool {
|
|
|
|
s, _ := v.ShardIDProvider()()
|
|
|
|
switch s {
|
|
|
|
case shard.BeaconChainShardID:
|
|
|
|
return v.SlashThresholdMet(key)
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *stakedVoteWeight) JSON() string {
|
|
|
|
s, _ := v.ShardIDProvider()()
|
|
|
|
|
|
|
|
type u struct {
|
|
|
|
IsHarmony bool `json:"is-harmony-slot"`
|
|
|
|
Identity string `json:"bls-public-key"`
|
|
|
|
VotingPower string `json:"voting-power-%"`
|
|
|
|
RawStake string `json:"raw-stake,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type t struct {
|
|
|
|
Policy string `json"policy"`
|
|
|
|
ShardID uint32 `json:"shard-id"`
|
|
|
|
Count int `json:"count"`
|
|
|
|
Participants []u `json:"committee-members"`
|
|
|
|
HmyVotingPower string `json:"hmy-voting-power"`
|
|
|
|
StakedVotingPower string `json:"staked-voting-power"`
|
|
|
|
TotalStaked string `json:"total-raw-staked"`
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := make([]u, len(v.voters))
|
|
|
|
i := 0
|
|
|
|
|
|
|
|
for identity, voter := range v.voters {
|
|
|
|
member := u{
|
|
|
|
voter.isHarmonyNode,
|
|
|
|
identity.Hex(),
|
|
|
|
voter.effectivePercent.String(),
|
|
|
|
"",
|
|
|
|
}
|
|
|
|
if !voter.isHarmonyNode {
|
|
|
|
member.RawStake = voter.rawStake.String()
|
|
|
|
}
|
|
|
|
parts[i] = member
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
b1, _ := json.Marshal(t{
|
|
|
|
v.Policy().String(),
|
|
|
|
s,
|
|
|
|
len(v.voters),
|
|
|
|
parts,
|
|
|
|
v.ourVotingPowerTotal.String(),
|
|
|
|
v.theirVotingPowerTotal.String(),
|
|
|
|
v.stakedTotal.String(),
|
|
|
|
})
|
|
|
|
return string(b1)
|
|
|
|
}
|