Skip to content

Commit

Permalink
online weak merit based sortition
Browse files Browse the repository at this point in the history
  • Loading branch information
kpeluso committed Sep 2, 2024
1 parent 270042d commit 32576e9
Show file tree
Hide file tree
Showing 24 changed files with 8,569 additions and 2,503 deletions.
134 changes: 134 additions & 0 deletions test/testutil/testdata.go

Large diffs are not rendered by default.

1,072 changes: 771 additions & 301 deletions x/emissions/api/v3/genesis.pulsar.go

Large diffs are not rendered by default.

5,707 changes: 4,282 additions & 1,425 deletions x/emissions/api/v3/query.pulsar.go

Large diffs are not rendered by default.

111 changes: 111 additions & 0 deletions x/emissions/api/v3/query_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 99 additions & 0 deletions x/emissions/keeper/actor_utils/quantile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package actorutils

import (
"slices"

alloraMath "github.com/allora-network/allora-chain/math"
emissionstypes "github.com/allora-network/allora-chain/x/emissions/types"
)

// Returns the quantile value of the given sorted scores
// e.g. if quantile is 0.25 (25%), for all the scores sorted from greatest to smallest
// give me the value that is greater than 25% of the values and less than 75% of the values
// the domain of this quantile is assumed to be between 0 and 1.
// Scores should be of unique actors => no two elements have the same actor address.
func GetQuantileOfScores(
scores []emissionstypes.Score,
quantile alloraMath.Dec,
) (alloraMath.Dec, error) {
// Sort scores in descending order. Address is used to break ties.
slices.SortStableFunc(scores, func(x, y emissionstypes.Score) int {
if x.Score.Lt(y.Score) {
return 1
} else if x.Score.Gt(y.Score) {
return -1
} else {
if x.Address < y.Address {
return 1
} else if x.Address > y.Address {
return -1
} else {
return 0
}
}
})

// If there are no scores then the quantile of scores is 0.
// This better ensures chain continuity without consequence because in this situation
// there is no meaningful quantile to calculate.
if len(scores) == 0 {
return alloraMath.ZeroDec(), nil
}
// n elements, q quantile
// position = (1 - q) * (n - 1)
nLessOne, err := alloraMath.NewDecFromUint64(uint64(len(scores) - 1))
if err != nil {
return alloraMath.Dec{}, err
}
oneLessQ, err := alloraMath.OneDec().Sub(quantile)
if err != nil {
return alloraMath.Dec{}, err
}
position, err := oneLessQ.Mul(nLessOne)
if err != nil {
return alloraMath.Dec{}, err
}

lowerIndex, err := position.Floor()
if err != nil {
return alloraMath.Dec{}, err
}
lowerIndexInt, err := lowerIndex.Int64()
if err != nil {
return alloraMath.Dec{}, err
}
upperIndex, err := position.Ceil()
if err != nil {
return alloraMath.Dec{}, err
}
upperIndexInt, err := upperIndex.Int64()
if err != nil {
return alloraMath.Dec{}, err
}

if lowerIndex == upperIndex {
return scores[lowerIndexInt].Score, nil
}

// in cases where the quantile is between two values
// return lowerValue + (upperValue-lowerValue)*(position-lowerIndex)
lowerScore := scores[lowerIndexInt]
upperScore := scores[upperIndexInt]
positionMinusLowerIndex, err := position.Sub(lowerIndex)
if err != nil {
return alloraMath.Dec{}, err
}
upperMinusLower, err := upperScore.Score.Sub(lowerScore.Score)
if err != nil {
return alloraMath.Dec{}, err
}
product, err := positionMinusLowerIndex.Mul(upperMinusLower)
if err != nil {
return alloraMath.Dec{}, err
}
ret, err := lowerScore.Score.Add(product)
if err != nil {
return alloraMath.Dec{}, err
}
return ret, nil
}
104 changes: 104 additions & 0 deletions x/emissions/keeper/actor_utils/quantile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package actorutils_test

import (
"fmt"
"strconv"
"testing"

alloraMath "github.com/allora-network/allora-chain/math"
alloratestutil "github.com/allora-network/allora-chain/test/testutil"
actorutils "github.com/allora-network/allora-chain/x/emissions/keeper/actor_utils"
emissionstypes "github.com/allora-network/allora-chain/x/emissions/types"
"github.com/stretchr/testify/require"
)

func TestGetQuantileOfScores(t *testing.T) {
// Note: unsorted scores. GetQuantileOfScores should sort scores within its scope
scores := []emissionstypes.Score{
{TopicId: 0, BlockHeight: 0, Address: "w1", Score: alloraMath.NewDecFromInt64(90)},
{TopicId: 0, BlockHeight: 0, Address: "w4", Score: alloraMath.NewDecFromInt64(60)},
{TopicId: 0, BlockHeight: 0, Address: "w3", Score: alloraMath.NewDecFromInt64(70)},
{TopicId: 0, BlockHeight: 0, Address: "w5", Score: alloraMath.NewDecFromInt64(50)},
{TopicId: 0, BlockHeight: 0, Address: "w2", Score: alloraMath.NewDecFromInt64(80)},
}

quantile := alloraMath.MustNewDecFromString("0.5")
expectedResult := alloraMath.NewDecFromInt64(70)

result, err := actorutils.GetQuantileOfScores(scores, quantile)
require.NoError(t, err)
require.Equal(t, expectedResult, result)

quantile = alloraMath.MustNewDecFromString("0.2")
expectedResult = alloraMath.NewDecFromInt64(58)

result, err = actorutils.GetQuantileOfScores(scores, quantile)
require.NoError(t, err)
expectedInt, err := expectedResult.Int64()
require.NoError(t, err)
actualInt, err := result.Int64()
require.NoError(t, err)
require.Equal(t, expectedInt, actualInt)
}

func TestGetQuantileOfScores2(t *testing.T) {
scoresSorted := []emissionstypes.Score{
{Score: alloraMath.MustNewDecFromString("0.8"), Address: "w1", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.7"), Address: "w2", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.0"), Address: "w9", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.3"), Address: "w6", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.4"), Address: "w5", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.9"), Address: "w0", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.6"), Address: "w3", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.5"), Address: "w4", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.1"), Address: "w8", BlockHeight: 0, TopicId: 0},
{Score: alloraMath.MustNewDecFromString("0.2"), Address: "w7", BlockHeight: 0, TopicId: 0},
}
quantile := alloraMath.MustNewDecFromString("0.2")
expectedResult := alloraMath.MustNewDecFromString("0.18")

result, err := actorutils.GetQuantileOfScores(scoresSorted, quantile)
require.NoError(t, err)
alloratestutil.InEpsilon5Dec(t, result, expectedResult)
}

func TestGetQuantileOfScoresCsv(t *testing.T) {
for epoch := 301; epoch < 400; epoch++ {
epochGet := alloratestutil.GetSortitionSimulatorValuesGetterForEpochs()[epoch]
topicId := uint64(0)

nParticipants, err := epochGet("n_participants").UInt64()
require.NoError(t, err)
nParticipantsDrawn, err := epochGet("n_participants_drawn").UInt64()
require.NoError(t, err)

// populate the data from the csv
scoresSorted := make([]emissionstypes.Score, nParticipantsDrawn)
for i := uint64(0); i < nParticipants; i++ {
participantName := strconv.FormatUint(i, 10)
active := epochGet(fmt.Sprintf("%s_active", participantName))
if active.Equal(alloraMath.OneDec()) {
sortPosition := epochGet(fmt.Sprintf("%s_sort_position_quality_metrics", participantName))
sortPos, err := sortPosition.UInt64()
require.NoError(t, err)
qualityMetric := epochGet(fmt.Sprintf("%s_quality_metric", participantName))
scoresSorted[sortPos] = emissionstypes.Score{
TopicId: topicId,
Address: participantName,
BlockHeight: int64(epoch),
Score: qualityMetric,
}
}
}
for _, score := range scoresSorted {
require.NotEmpty(t, score)
}
expected := epochGet("quality_percentile")
percentile_to_use := epochGet("percentile")
quantile, err := percentile_to_use.Quo(alloraMath.NewDecFromInt64(int64(100)))
require.NoError(t, err)
result, err := actorutils.GetQuantileOfScores(scoresSorted, quantile)
require.NoError(t, err)
alloratestutil.InEpsilon5Dec(t, result, expected)
}
}
File renamed without changes.
Loading

0 comments on commit 32576e9

Please sign in to comment.