Skip to content

Commit

Permalink
fix(oracle): #wip checkpoint for omit == abstain
Browse files Browse the repository at this point in the history
  • Loading branch information
Unique-Divine committed Sep 5, 2023
1 parent c6a592d commit 3ca259a
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 114 deletions.
14 changes: 6 additions & 8 deletions x/oracle/keeper/ballot.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ import (
// asset pair is associated with its collection of ExchangeRateBallots.
func (k Keeper) groupBallotsByPair(
ctx sdk.Context,
validatorsPerformance types.ValidatorPerformances,
valPerfMap types.ValidatorPerformances,
) (pairBallotsMap map[asset.Pair]types.ExchangeRateBallots) {
pairBallotsMap = map[asset.Pair]types.ExchangeRateBallots{}

for _, value := range k.Votes.Iterate(ctx, collections.Range[sdk.ValAddress]{}).KeyValues() {
voterAddr, aggregateVote := value.Key, value.Value

// organize ballot only for the active validators
if validatorPerformance, exists := validatorsPerformance[aggregateVote.Voter]; exists {
if validatorPerformance, exists := valPerfMap[aggregateVote.Voter]; exists {
for _, exchangeRateTuple := range aggregateVote.ExchangeRateTuples {
power := validatorPerformance.Power
if !exchangeRateTuple.ExchangeRate.IsPositive() {
Expand Down Expand Up @@ -147,7 +147,7 @@ func Tally(
ballots types.ExchangeRateBallots,
rewardBand sdk.Dec,
validatorPerformances types.ValidatorPerformances,
) sdk.Dec {
) (sdk.Dec, types.ValidatorPerformances) {
weightedMedian := ballots.WeightedMedianWithAssertion()
standardDeviation := ballots.StandardDeviation(weightedMedian)
rewardSpread := weightedMedian.Mul(rewardBand.QuoInt64(2))
Expand All @@ -162,20 +162,18 @@ func Tally(
ballot.ExchangeRate.LTE(weightedMedian.Add(rewardSpread))
isAbstainVote := !ballot.ExchangeRate.IsPositive()
isMiss := !(voteInsideSpread || isAbstainVote)

voterAddr := ballot.Voter.String()
validatorPerformance := validatorPerformances[voterAddr]
switch {
case isAbstainVote:
validatorPerformance.AbstainCount++
case voteInsideSpread:
validatorPerformance.RewardWeight += ballot.Power
validatorPerformance.WinCount++
case isMiss:
validatorPerformance.MissCount++
case isAbstainVote:
validatorPerformance.AbstainCount++
}
validatorPerformances[voterAddr] = validatorPerformance
}

return weightedMedian
return weightedMedian, validatorPerformances
}
2 changes: 1 addition & 1 deletion x/oracle/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestFeederDelegation(t *testing.T) {
exchangeRates := types.ExchangeRateTuples{
{
Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD),
ExchangeRate: randomExchangeRate,
ExchangeRate: TEST_ERATE,
},
}

Expand Down
2 changes: 1 addition & 1 deletion x/oracle/keeper/reward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestKeeperRewardsDistributionMultiVotePeriods(t *testing.T) {
MakeAggregatePrevoteAndVote(t, fixture, msgServer, fixture.Ctx.BlockHeight(), types.ExchangeRateTuples{
{
Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD),
ExchangeRate: randomExchangeRate,
ExchangeRate: TEST_ERATE,
},
}, valIndex)
}
Expand Down
101 changes: 62 additions & 39 deletions x/oracle/keeper/slash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,22 @@ func TestInvalidVotesSlashing(t *testing.T) {

// Account 1, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE},
}, 0)

// Account 2, govstable, miss vote
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Add(sdk.NewDec(100000000000000))},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Add(sdk.NewDec(100000000000000))},
}, 1)

// Account 3, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE},
}, 2)

// Account 4, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE},
}, 3)

input.OracleKeeper.UpdateExchangeRates(input.Ctx)
Expand All @@ -131,27 +131,27 @@ func TestInvalidVotesSlashing(t *testing.T) {
}

validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[1])
require.Equal(t, stakingAmt, validator.GetBondedTokens())
require.Equal(t, TEST_STAKING_AMT, validator.GetBondedTokens())

// one more miss vote will inccur ValAddrs[1] slashing
// Account 1, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE},
}, 0)

// Account 2, govstable, miss vote
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Add(sdk.NewDec(100000000000000))},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Add(sdk.NewDec(100000000000000))},
}, 1)

// Account 3, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE},
}, 2)

// Account 4, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate},
{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE},
}, 3)

input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1)
Expand All @@ -160,49 +160,72 @@ func TestInvalidVotesSlashing(t *testing.T) {
// input.OracleKeeper.UpdateExchangeRates(input.Ctx)

validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[1])
require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens())
require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(TEST_STAKING_AMT).TruncateInt(), validator.GetBondedTokens())
}

// TestWhitelistSlashing: Creates a scenario where one valoper (valIdx 0) does
// not vote throughout an entire vote window, while valopers 1 and 2 do.
func TestWhitelistSlashing(t *testing.T) {
input, h := Setup(t)
input, msgServer := Setup(t)

votePeriodsPerWindow := sdk.NewDec(int64(input.OracleKeeper.SlashWindow(input.Ctx))).QuoInt64(int64(input.OracleKeeper.VotePeriod(input.Ctx))).TruncateInt64()
slashFraction := input.OracleKeeper.SlashFraction(input.Ctx)
// slashFraction := input.OracleKeeper.SlashFraction(input.Ctx)
minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx)

pair := asset.Registry.Pair(denoms.NIBI, denoms.NUSD)
priceVoteFromVal := func(valIdx int, block int64, erate sdk.Dec) {
MakeAggregatePrevoteAndVote(t, input, msgServer, block,
types.ExchangeRateTuples{{Pair: pair, ExchangeRate: erate}},
valIdx)
}
input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, pair)
perfs := input.OracleKeeper.UpdateExchangeRates(input.Ctx)
require.EqualValues(t, 0, perfs.GetTotalRewardWeight())

block := input.Ctx.BlockHeight() + 50
priceVoteFromVal(0, block, TEST_ERATE)
priceVoteFromVal(1, block, TEST_ERATE)
input.Ctx = input.Ctx.WithBlockHeight(block + 100)

allowedMissPct := sdk.OneDec().Sub(minValidPerWindow)
allowedMissPeriods := allowedMissPct.MulInt64(votePeriodsPerWindow).
TruncateInt64()
for idxMissPeriod := uint64(0); idxMissPeriod < uint64(allowedMissPeriods); idxMissPeriod++ {
input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1)

// Account 2, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1)
// Account 3, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2)
// TODO

input.OracleKeeper.UpdateExchangeRates(input.Ctx)
// input.OracleKeeper.SlashAndResetMissCounters(input.Ctx)
// input.OracleKeeper.UpdateExchangeRates(input.Ctx)
require.Equal(t, idxMissPeriod+1, input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[0], 0))
}
t.Logf("For %v blocks, valoper0 does not vote, while 1 and 2 do.", allowedMissPeriods)
for idxMissPeriod := uint64(0); idxMissPeriod < uint64(allowedMissPeriods); idxMissPeriod++ {
block := input.Ctx.BlockHeight() + 1
input.Ctx = input.Ctx.WithBlockHeight(block)

validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[0])
require.Equal(t, stakingAmt, validator.GetBondedTokens())
valIdx := 0
priceVoteFromVal(valIdx+1, block, TEST_ERATE)
priceVoteFromVal(valIdx+2, block, TEST_ERATE)

// one more miss vote will inccur Account 1 slashing
// Valoper 0 votes outside band (miss)

// Account 2, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1)
// Account 3, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2)
perfs := input.OracleKeeper.UpdateExchangeRates(input.Ctx)
missCount := input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[0], 0)
require.EqualValues(t, 0, missCount, perfs.String())
}

input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1)
input.OracleKeeper.UpdateExchangeRates(input.Ctx)
input.OracleKeeper.SlashAndResetMissCounters(input.Ctx)
t.Log("valoper0 should not be slashed")
validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[0])
require.Equal(t, TEST_STAKING_AMT, validator.GetBondedTokens())

// // one more miss vote will inccur Account 1 slashing
//
// // Account 2, govstable
// MakeAggregatePrevoteAndVote(t, input, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1)
// // Account 3, govstable
// MakeAggregatePrevoteAndVote(t, input, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2)
//
// input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1)
// input.OracleKeeper.UpdateExchangeRates(input.Ctx)
validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[0])
require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens())
// input.OracleKeeper.SlashAndResetMissCounters(input.Ctx)
// // input.OracleKeeper.UpdateExchangeRates(input.Ctx)
// validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[0])
// require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens())
}

func TestNotPassedBallotSlashing(t *testing.T) {
Expand All @@ -221,7 +244,7 @@ func TestNotPassedBallotSlashing(t *testing.T) {
input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1)

// Account 1, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0)
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, 0)

input.OracleKeeper.UpdateExchangeRates(input.Ctx)
input.OracleKeeper.SlashAndResetMissCounters(input.Ctx)
Expand Down Expand Up @@ -251,13 +274,13 @@ func TestAbstainSlashing(t *testing.T) {
input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1)

// Account 1, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0)
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, 0)

// Account 2, govstable, abstain vote
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: sdk.ZeroDec()}}, 1)

// Account 3, govstable
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2)
MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, 2)

input.OracleKeeper.UpdateExchangeRates(input.Ctx)
input.OracleKeeper.SlashAndResetMissCounters(input.Ctx)
Expand All @@ -266,5 +289,5 @@ func TestAbstainSlashing(t *testing.T) {
}

validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[1])
require.Equal(t, stakingAmt, validator.GetBondedTokens())
require.Equal(t, TEST_STAKING_AMT, validator.GetBondedTokens())
}
23 changes: 15 additions & 8 deletions x/oracle/keeper/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ func AllocateRewards(t *testing.T, input TestFixture, rewards sdk.Coins, votePer
}

var (
stakingAmt = sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)
randomExchangeRate = sdk.NewDec(1700)
TEST_STAKING_AMT = sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)
TEST_ERATE = sdk.NewDec(1700)
)

func Setup(t *testing.T) (TestFixture, types.MsgServer) {
Expand All @@ -304,22 +304,29 @@ func Setup(t *testing.T) (TestFixture, types.MsgServer) {
sh := stakingkeeper.NewMsgServerImpl(&fixture.StakingKeeper)

// Validator created
_, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], stakingAmt))
_, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], TEST_STAKING_AMT))
require.NoError(t, err)
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], stakingAmt))
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], TEST_STAKING_AMT))
require.NoError(t, err)
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], stakingAmt))
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], TEST_STAKING_AMT))
require.NoError(t, err)
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[3], ValPubKeys[3], stakingAmt))
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[3], ValPubKeys[3], TEST_STAKING_AMT))
require.NoError(t, err)
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[4], ValPubKeys[4], stakingAmt))
_, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[4], ValPubKeys[4], TEST_STAKING_AMT))
require.NoError(t, err)
staking.EndBlocker(fixture.Ctx, &fixture.StakingKeeper)

return fixture, h
}

func MakeAggregatePrevoteAndVote(t *testing.T, input TestFixture, msgServer types.MsgServer, height int64, rates types.ExchangeRateTuples, valIdx int) {
func MakeAggregatePrevoteAndVote(
t *testing.T,
input TestFixture,
msgServer types.MsgServer,
height int64,
rates types.ExchangeRateTuples,
valIdx int,
) {
salt := "1"
ratesStr, err := rates.ToString()
require.NoError(t, err)
Expand Down
20 changes: 18 additions & 2 deletions x/oracle/keeper/update_exchange_rates.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

// UpdateExchangeRates updates the ExchangeRates, this is supposed to be executed on EndBlock.
func (k Keeper) UpdateExchangeRates(ctx sdk.Context) {
func (k Keeper) UpdateExchangeRates(ctx sdk.Context) types.ValidatorPerformances {
k.Logger(ctx).Info("processing validator price votes")

validatorPerformances := k.newValidatorPerformances(ctx)
Expand All @@ -27,6 +27,8 @@ func (k Keeper) UpdateExchangeRates(ctx sdk.Context) {
params, _ := k.Params.Get(ctx)
k.clearVotesAndPreVotes(ctx, params.VotePeriod)
k.updateWhitelist(ctx, params.Whitelist, whitelistedPairs)
k.registerAbstainsByOmission(ctx, len(params.Whitelist), validatorPerformances)
return validatorPerformances
}

// registerMissedVotes it parses all validators performance and increases the
Expand All @@ -46,6 +48,20 @@ func (k Keeper) registerMissedVotes(
}
}

func (k Keeper) registerAbstainsByOmission(
ctx sdk.Context,
numMarkets int,
perfs types.ValidatorPerformances,
) {
for valAddr, perf := range perfs {
omitCount := int64(numMarkets) - (perf.WinCount + perf.AbstainCount + perf.MissCount)
if omitCount > 0 {
perf.AbstainCount += omitCount
perfs[valAddr] = perf
}
}
}

// countVotesAndUpdateExchangeRates processes the votes and updates the
// ExchangeRates based on the results.
func (k Keeper) countVotesAndUpdateExchangeRates(
Expand All @@ -59,7 +75,7 @@ func (k Keeper) countVotesAndUpdateExchangeRates(
orderedBallotsMap := omap.OrderedMap_Pair[types.ExchangeRateBallots](pairBallotsMap)
for pair := range orderedBallotsMap.Range() {
ballots := pairBallotsMap[pair]
exchangeRate := Tally(ballots, rewardBand, validatorPerformances)
exchangeRate, _ := Tally(ballots, rewardBand, validatorPerformances)

k.SetPrice(ctx, pair, exchangeRate)

Expand Down
Loading

0 comments on commit 3ca259a

Please sign in to comment.