From 6779b4931e6c2ffe05b438b574b3413dc19e11af Mon Sep 17 00:00:00 2001 From: ze97286 Date: Thu, 8 Aug 2024 11:50:13 +0100 Subject: [PATCH] fix: non determinism in lottery ranking fixed --- CHANGELOG.md | 1 + core/rewards/lottery_ranking_test.go | 24 ++++++++++++----------- core/rewards/reward_distribution.go | 29 ++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcef12293a..44ed6fc774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - [11521](https://github.com/vegaprotocol/vega/issues/11521) - Restore `AMM` position factor when loading from a snapshot. - [11526](https://github.com/vegaprotocol/vega/issues/11526) - `EstimateAMMBounds` now respects the market's decimal places. - [11540](https://github.com/vegaprotocol/vega/issues/11540) - Fix spam check for spots to use not double count quantum. +- [11542](https://github.com/vegaprotocol/vega/issues/11542) - Fix non determinism in lottery ranking. ## 0.77.5 diff --git a/core/rewards/lottery_ranking_test.go b/core/rewards/lottery_ranking_test.go index 87197bc89a..d5ce8a6e46 100644 --- a/core/rewards/lottery_ranking_test.go +++ b/core/rewards/lottery_ranking_test.go @@ -26,16 +26,18 @@ import ( ) func TestLotteryRewardScoreSorting(t *testing.T) { - adjustedRewardScores := []*types.PartyContributionScore{ - {Party: "p1", Score: num.DecimalOne()}, - {Party: "p2", Score: num.DecimalTwo()}, - {Party: "p3", Score: num.DecimalFromFloat(0.01)}, - } - const layout = "Jan 2, 2006 at 3:04pm" + for i := 0; i < 100; i++ { + adjustedRewardScores := []*types.PartyContributionScore{ + {Party: "p1", Score: num.DecimalOne()}, + {Party: "p2", Score: num.DecimalTwo()}, + {Party: "p3", Score: num.DecimalFromFloat(0.01)}, + } + const layout = "Jan 2, 2006 at 3:04pm" - timestamp, _ := time.Parse(layout, "Aug 7, 2024 at 12:00pm") - lottery := lotteryRewardScoreSorting(adjustedRewardScores, timestamp) - require.Equal(t, "p2", lottery[0].Party) - require.Equal(t, "p1", lottery[1].Party) - require.Equal(t, "p3", lottery[2].Party) + timestamp, _ := time.Parse(layout, "Aug 7, 2024 at 12:00pm") + lottery := lotteryRewardScoreSorting(adjustedRewardScores, timestamp) + require.Equal(t, "p2", lottery[0].Party) + require.Equal(t, "p1", lottery[1].Party) + require.Equal(t, "p3", lottery[2].Party) + } } diff --git a/core/rewards/reward_distribution.go b/core/rewards/reward_distribution.go index 187f0c8fdd..7d15e5e777 100644 --- a/core/rewards/reward_distribution.go +++ b/core/rewards/reward_distribution.go @@ -128,6 +128,11 @@ func selectIndex(rng *rand.Rand, probabilities []num.Decimal) int { return len(probabilities) - 1 } +type PartyProbability struct { + Probability num.Decimal + Party string +} + func lotteryRewardScoreSorting(adjustedPartyScores []*types.PartyContributionScore, timestamp time.Time) []*types.PartyContributionScore { source := rand.NewSource(uint64(timestamp.UnixNano())) rng := rand.New(source) @@ -148,12 +153,28 @@ func lotteryRewardScoreSorting(adjustedPartyScores []*types.PartyContributionSco if len(unselectedParties) < 1 { break } - probabilities := make([]num.Decimal, 0, len(unselectedParties)) - parties := make([]string, 0, len(unselectedParties)) + pp := make([]PartyProbability, 0, len(unselectedParties)) for _, ps := range unselectedParties { - probabilities = append(probabilities, ps.Score.Div(totalScores)) - parties = append(parties, ps.Party) + pp = append(pp, PartyProbability{ + Probability: ps.Score.Div(totalScores), + Party: ps.Party, + }) + } + sort.Slice(pp, func(i, j int) bool { + if pp[i].Probability.Equal(pp[j].Probability) { + return pp[i].Party < pp[j].Party + } + return pp[i].Probability.LessThan(pp[j].Probability) + }) + + probabilities := make([]num.Decimal, len(pp)) + parties := make([]string, len(pp)) + + for i, partyProb := range pp { + probabilities[i] = partyProb.Probability + parties[i] = partyProb.Party } + selected := selectIndex(rng, probabilities) selectedParty := unselectedParties[parties[selected]] delete(unselectedParties, selectedParty.Party)