-
Notifications
You must be signed in to change notification settings - Fork 0
/
tally.go
136 lines (109 loc) · 4.31 KB
/
tally.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package keeper
import (
"github.com/merlin-network/elysium-sdk/v2/x/oracle/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// BuildClaimsMapAndTally builds a claim map over all validators in active set,
// marks misses and handles ballot tallying.
func (k Keeper) BuildClaimsMapAndTally(ctx sdk.Context, params types.Params) error {
// Build claim map over all validators in active set
validatorClaimMap := make(map[string]types.Claim)
powerReduction := k.StakingKeeper.PowerReduction(ctx)
// Calculate total validator power
var totalBondedValidatorPower int64
for _, v := range k.StakingKeeper.GetBondedValidatorsByPower(ctx) {
addr := v.GetOperator()
valConsensusPower := v.GetConsensusPower(powerReduction)
totalBondedValidatorPower += valConsensusPower
validatorClaimMap[addr.String()] = types.NewClaim(valConsensusPower, 0, 0, addr)
}
var (
// voteTargets defines the symbol (ticker) denoms that we require votes on
voteTargets = make([]string, 0, len(params.AcceptList))
voteTargetDenoms = make([]string, 0, len(params.AcceptList))
)
for _, v := range params.AcceptList {
voteTargets = append(voteTargets, v.SymbolDenom)
voteTargetDenoms = append(voteTargetDenoms, v.SymbolDenom)
}
// Clear all exchange rates
k.ClearExchangeRates(ctx)
// Organize votes to ballot by denom
// NOTE: **Filter out inactive or jailed validators**
ballotDenomSlice := k.OrganizeBallotByDenom(ctx, validatorClaimMap)
threshold := k.GetVoteThreshold(ctx).MulInt64(types.MaxVoteThresholdMultiplier).TruncateInt64()
// Iterate through ballots and update exchange rates; drop if not enough votes have been achieved.
for _, ballotDenom := range ballotDenomSlice {
// Calculate the portion of votes received as an integer, scaled up using the
// same multiplier as the `threshold` computed above
support := ballotDenom.Ballot.Power() * types.MaxVoteThresholdMultiplier / totalBondedValidatorPower
if support < threshold {
ctx.Logger().Info("Ballot voting power is under vote threshold, dropping ballot", "denom", ballotDenom)
continue
}
// Get weighted median of exchange rates
exchangeRate, err := Tally(ballotDenom.Ballot, params.RewardBand, validatorClaimMap)
if err != nil {
return err
}
// Set the exchange rate, emit ABCI event
k.SetExchangeRateWithEvent(ctx, ballotDenom.Denom, exchangeRate)
}
// update miss counting & slashing
voteTargetsLen := len(voteTargets)
claimSlice := types.ClaimMapToSlice(validatorClaimMap)
for _, claim := range claimSlice {
// Skip valid voters
if int(claim.WinCount) == voteTargetsLen {
continue
}
// Increase miss counter
k.SetMissCounter(ctx, claim.Recipient, k.GetMissCounter(ctx, claim.Recipient)+1)
}
// Distribute rewards to ballot winners
k.RewardBallotWinners(
ctx,
int64(params.VotePeriod),
int64(params.RewardDistributionWindow),
voteTargetDenoms,
claimSlice,
)
// Clear the ballot
k.ClearVotes(ctx, params.VotePeriod)
return nil
}
// Tally calculates the median and returns it. It sets the set of voters to be
// rewarded, i.e. voted within a reasonable spread from the weighted median to
// the store. Note, the ballot is sorted by ExchangeRate.
// https://classic-docs.terra.money/docs/develop/module-specifications/spec-oracle.html#tally
func Tally(
ballot types.ExchangeRateBallot,
rewardBand sdk.Dec,
validatorClaimMap map[string]types.Claim,
) (sdk.Dec, error) {
weightedMedian, err := ballot.WeightedMedian()
if err != nil {
return sdk.ZeroDec(), err
}
standardDeviation, err := ballot.StandardDeviation()
if err != nil {
return sdk.ZeroDec(), err
}
// rewardSpread is the MAX((weightedMedian * (rewardBand/2)), standardDeviation)
rewardSpread := weightedMedian.Mul(rewardBand.QuoInt64(2))
rewardSpread = sdk.MaxDec(rewardSpread, standardDeviation)
for _, tallyVote := range ballot {
// Filter ballot winners. For voters, we filter out the tally vote iff:
// (weightedMedian - rewardSpread) <= ExchangeRate <= (weightedMedian + rewardSpread)
if (tallyVote.ExchangeRate.GTE(weightedMedian.Sub(rewardSpread)) &&
tallyVote.ExchangeRate.LTE(weightedMedian.Add(rewardSpread))) ||
!tallyVote.ExchangeRate.IsPositive() {
key := tallyVote.Voter.String()
claim := validatorClaimMap[key]
claim.Weight += tallyVote.Power
claim.WinCount++
validatorClaimMap[key] = claim
}
}
return weightedMedian, nil
}