Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Reputation: CNS-1000 - LegacyDec pairing score #1599

Merged
merged 9 commits into from
Dec 16, 2024
13 changes: 13 additions & 0 deletions periods.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"start_time": 1733431162,
"periods":[
{
"coins": "10ulava",
"length_seconds":2592000
},
{
"coins": "10ulava",
"length_seconds":2592000
}
]
}
2 changes: 1 addition & 1 deletion protocol/lavasession/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func GetTlsConfig(networkAddress NetworkAddressData) *tls.Config {
}

func SortByGeolocations(pairingEndpoints []*Endpoint, currentGeo planstypes.Geolocation) {
latencyToGeo := func(a, b planstypes.Geolocation) uint64 {
latencyToGeo := func(a, b planstypes.Geolocation) int64 {
_, latency := scores.CalcGeoLatency(a, []planstypes.Geolocation{b})
return latency
}
Expand Down
4 changes: 2 additions & 2 deletions x/pairing/keeper/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ func (k Keeper) GetPairingForClient(ctx sdk.Context, chainID string, clientAddre

// CalculatePairingChance calculates the chance of a provider to be picked in the pairing process for the first pairing slot
func (k Keeper) CalculatePairingChance(ctx sdk.Context, provider string, chainID string, policy *planstypes.Policy, cluster string) (cosmosmath.LegacyDec, error) {
totalScore := cosmosmath.ZeroUint()
providerScore := cosmosmath.ZeroUint()
totalScore := cosmosmath.LegacyZeroDec()
providerScore := cosmosmath.LegacyZeroDec()

_, _, scores, err := k.getPairingForClient(ctx, chainID, uint64(ctx.BlockHeight()), policy, cluster, "dummy", true)
if err != nil {
Expand Down
21 changes: 10 additions & 11 deletions x/pairing/keeper/scores/geo_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/lavanet/lava/v4/utils"
planstypes "github.com/lavanet/lava/v4/x/plans/types"
)
Expand All @@ -15,9 +14,9 @@ type GeoReq struct {
}

const (
geoReqName = "geo-req"
maxGeoLatency uint64 = 10000 // highest geo cost < 300
minGeoLatency = 1
geoReqName = "geo-req"
maxGeoLatency int64 = 10000 // highest geo cost < 300
minGeoLatency = 1
)

func (gr GeoReq) Init(policy planstypes.Policy) bool {
Expand All @@ -26,7 +25,7 @@ func (gr GeoReq) Init(policy planstypes.Policy) bool {

// Score calculates the geo score of a provider based on preset latency data
// Note: each GeoReq must have exactly a single geolocation (bit)
func (gr GeoReq) Score(score PairingScore) math.Uint {
func (gr GeoReq) Score(score PairingScore) math.LegacyDec {
// check if the provider supports the required geolocation
if gr.Geo&^score.Provider.Geolocation == 0 {
return calculateCostFromLatency(minGeoLatency)
Expand Down Expand Up @@ -68,13 +67,13 @@ func (gr GeoReq) GetReqForSlot(policy planstypes.Policy, slotIdx int) ScoreReq {
}

// CalcGeoCost() finds the minimal latency between the required geo and the provider's supported geolocations
func CalcGeoCost(reqGeo planstypes.Geolocation, providerGeos []planstypes.Geolocation) (minLatencyGeo planstypes.Geolocation, minLatencyCost math.Uint) {
func CalcGeoCost(reqGeo planstypes.Geolocation, providerGeos []planstypes.Geolocation) (minLatencyGeo planstypes.Geolocation, minLatencyCost math.LegacyDec) {
minGeo, minLatency := CalcGeoLatency(reqGeo, providerGeos)

return minGeo, calculateCostFromLatency(minLatency)
}

func CalcGeoLatency(reqGeo planstypes.Geolocation, providerGeos []planstypes.Geolocation) (planstypes.Geolocation, uint64) {
func CalcGeoLatency(reqGeo planstypes.Geolocation, providerGeos []planstypes.Geolocation) (planstypes.Geolocation, int64) {
minGeo := planstypes.Geolocation(-1)
minLatency := maxGeoLatency
for _, pGeo := range providerGeos {
Expand All @@ -95,19 +94,19 @@ func CalcGeoLatency(reqGeo planstypes.Geolocation, providerGeos []planstypes.Geo
return minGeo, minLatency
}

func calculateCostFromLatency(latency uint64) math.Uint {
func calculateCostFromLatency(latency int64) math.LegacyDec {
if latency == 0 {
utils.LavaFormatWarning("got latency 0 when calculating geo req score", fmt.Errorf("invalid geo req score"))
return math.OneUint()
return math.LegacyOneDec()
}
return sdk.NewUint(maxGeoLatency / latency)
return math.LegacyNewDec(maxGeoLatency).QuoInt64(latency)
}

// GEO_LATENCY_MAP is a map of lists of GeoLatency that defines the cost of geo mismatch
// for each single geolocation. The map key is a single geolocation and the value is an
// ordered list of neighbors and their latency (ordered by latency)
// latency data from: https://wondernetwork.com/pings (July 2023)
var GEO_LATENCY_MAP = map[planstypes.Geolocation]map[planstypes.Geolocation]uint64{
var GEO_LATENCY_MAP = map[planstypes.Geolocation]map[planstypes.Geolocation]int64{
planstypes.Geolocation_AS: {
planstypes.Geolocation_AU: 146,
planstypes.Geolocation_EU: 155,
Expand Down
4 changes: 2 additions & 2 deletions x/pairing/keeper/scores/geo_req_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestCalcGeoCost(t *testing.T) {
reqGeo planstypes.Geolocation
providerGeos []planstypes.Geolocation
expectedGeo planstypes.Geolocation
expectedCostUint uint64
expectedCostUint int64
}{
{
name: "happy flow",
Expand Down Expand Up @@ -59,7 +59,7 @@ func TestGeoReqScore(t *testing.T) {
name string
reqGeo int32
providerGeo int32
expectedLatency uint64
expectedLatency int64
}{
{
name: "happy flow - provider supports geo",
Expand Down
8 changes: 4 additions & 4 deletions x/pairing/keeper/scores/pairing_score.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const (
// PairingScore holds a provider's score with respect to a set of requirements (ScoreReq), indexed by their unique name.
type PairingScore struct {
Provider *epochstoragetypes.StakeEntry
Score math.Uint
ScoreComponents map[string]math.Uint
Score math.LegacyDec
ScoreComponents map[string]math.LegacyDec
SkipForSelection bool
SlotFiltering map[int]struct{} // slot indexes here are skipped
QosExcellenceReport pairingtypes.QualityOfServiceReport
Expand Down Expand Up @@ -43,8 +43,8 @@ func (ps *PairingScore) InvalidIndexes(possibleIndexes []int) []int {
func NewPairingScore(stakeEntry *epochstoragetypes.StakeEntry, qos pairingtypes.QualityOfServiceReport) *PairingScore {
score := PairingScore{
Provider: stakeEntry,
Score: math.OneUint(),
ScoreComponents: map[string]math.Uint{},
Score: math.LegacyOneDec(),
ScoreComponents: map[string]math.LegacyDec{},
SkipForSelection: false,
QosExcellenceReport: qos,
}
Expand Down
4 changes: 2 additions & 2 deletions x/pairing/keeper/scores/qos_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (qr QosReq) Init(policy planstypes.Policy) bool {
}

// Score calculates the the provider's qos score
func (qr QosReq) Score(score PairingScore) math.Uint {
func (qr QosReq) Score(score PairingScore) math.LegacyDec {
// TODO: update Qos in providerQosFS properly and uncomment this code below
// Also, the qos score should range between 0.5-2

Expand All @@ -31,7 +31,7 @@ func (qr QosReq) Score(score PairingScore) math.Uint {
// }

// return math.Uint(qosScore)
return math.NewUint(1)
return math.LegacyOneDec()
}

func (qr QosReq) GetName() string {
Expand Down
32 changes: 15 additions & 17 deletions x/pairing/keeper/scores/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,20 @@ func CalcPairingScore(scores []*PairingScore, strategy ScoreStrategy, diffSlot P
}

newScoreComp := req.Score(*score)
if newScoreComp == math.ZeroUint() {
if newScoreComp == math.LegacyZeroDec() {
return utils.LavaFormatError("new score component is zero", fmt.Errorf("cannot calculate pairing score"),
utils.Attribute{Key: "score component", Value: reqName},
utils.Attribute{Key: "provider", Value: score.Provider.Address},
)
}
newScoreCompDec := sdk.NewDecFromInt(math.Int(newScoreComp))
newScoreCompDec = newScoreCompDec.Power(weight)
newScoreComp = math.Uint(newScoreCompDec.TruncateInt())
newScoreComp = newScoreComp.Power(weight)

// update the score component map
score.ScoreComponents[reqName] = newScoreComp
}

// calc new score
newScore := math.OneUint()
newScore := math.LegacyOneDec()
for _, scoreComp := range score.ScoreComponents {
newScore = newScore.Mul(scoreComp)
}
Expand Down Expand Up @@ -200,8 +198,8 @@ func PickProviders(ctx sdk.Context, scores []*PairingScore, groupIndexes []int,
groupIndex = -1
effectiveScore = totalScore
}
randomValue := uint64(rng.Int63n(effectiveScore.BigInt().Int64())) + 1
newScoreSum := math.ZeroUint()
randomValue := rng.Int63n(effectiveScore.RoundInt64()) + 1
newScoreSum := math.LegacyZeroDec()

for idx := len(scores) - 1; idx >= 0; idx-- {
if !scores[idx].IsValidForSelection(groupIndex) {
Expand All @@ -210,7 +208,7 @@ func PickProviders(ctx sdk.Context, scores []*PairingScore, groupIndexes []int,
}
providerScore := scores[idx]
newScoreSum = newScoreSum.Add(providerScore.Score)
if randomValue <= newScoreSum.Uint64() {
if randomValue <= newScoreSum.RoundInt64() {
// we hit our chosen provider
// remove this provider from the random pool, so the sum is lower now

Expand All @@ -228,27 +226,27 @@ func PickProviders(ctx sdk.Context, scores []*PairingScore, groupIndexes []int,
// the negative score modifiers contain the total stake of providers that are not allowed
// so if a provider is not allowed in slot X, it will be added to the total score but will have slotIndexScore[X]+= providerStake
// and during the selection of slot X we will have the selection effective sum as: totalScore - slotIndexScore[X], and that provider that is not allowed can't be selected
func CalculateTotalScoresForGroup(scores []*PairingScore, groupIndexes []int) (totalScore math.Uint, slotIndexScore map[int]math.Uint, err error) {
func CalculateTotalScoresForGroup(scores []*PairingScore, groupIndexes []int) (totalScore math.LegacyDec, slotIndexScore map[int]math.LegacyDec, err error) {
if len(scores) == 0 {
return math.ZeroUint(), nil, fmt.Errorf("invalid scores length")
return math.LegacyZeroDec(), nil, fmt.Errorf("invalid scores length")
}
totalScore = math.ZeroUint()
slotIndexScore = map[int]math.Uint{}
totalScore = math.LegacyZeroDec()
slotIndexScore = map[int]math.LegacyDec{}
for _, groupIndex := range groupIndexes {
slotIndexScore[groupIndex] = math.ZeroUint()
slotIndexScore[groupIndex] = math.LegacyZeroDec()
}
// all all providers to selection possibilities
for _, providerScore := range scores {
totalScore, slotIndexScore = AddProviderToSelection(providerScore, groupIndexes, totalScore, slotIndexScore)
}

if totalScore == math.ZeroUint() {
return math.ZeroUint(), nil, utils.LavaFormatError("score sum is zero", fmt.Errorf("cannot pick providers for pairing"))
if totalScore == math.LegacyZeroDec() {
return math.LegacyZeroDec(), nil, utils.LavaFormatError("score sum is zero", fmt.Errorf("cannot pick providers for pairing"))
}
return totalScore, slotIndexScore, nil
}

func AddProviderToSelection(providerScore *PairingScore, groupIndexes []int, totalScore math.Uint, slotIndexScore map[int]math.Uint) (math.Uint, map[int]math.Uint) {
func AddProviderToSelection(providerScore *PairingScore, groupIndexes []int, totalScore math.LegacyDec, slotIndexScore map[int]math.LegacyDec) (math.LegacyDec, map[int]math.LegacyDec) {
if providerScore.SkipForSelection {
return totalScore, slotIndexScore
}
Expand All @@ -260,7 +258,7 @@ func AddProviderToSelection(providerScore *PairingScore, groupIndexes []int, tot
return totalScore, slotIndexScore
}

func RemoveProviderFromSelection(providerScore *PairingScore, groupIndexes []int, totalScore math.Uint, slotIndexScore map[int]math.Uint) (math.Uint, map[int]math.Uint) {
func RemoveProviderFromSelection(providerScore *PairingScore, groupIndexes []int, totalScore math.LegacyDec, slotIndexScore map[int]math.LegacyDec) (math.LegacyDec, map[int]math.LegacyDec) {
// remove this provider from the total score
totalScore = totalScore.Sub(providerScore.Score)
// remove this provider for the subtraction scores as well
Expand Down
2 changes: 1 addition & 1 deletion x/pairing/keeper/scores/score_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type ScoreReq interface {
// Init() initializes the ScoreReq object and returns whether it's active
Init(policy planstypes.Policy) bool
// Score() calculates a provider's score according to the requirement
Score(score PairingScore) math.Uint
Score(score PairingScore) math.LegacyDec
// GetName returns the unique name of the ScoreReq implementation
GetName() string
// Equal compares two ScoreReq objects
Expand Down
10 changes: 5 additions & 5 deletions x/pairing/keeper/scores/score_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestTotalScore(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
totalScore, sloitIndexScores, err := CalculateTotalScoresForGroup(tt.scores, tt.groupIndexes)
require.NoError(t, err)
calculatedScore := math.ZeroUint()
calculatedScore := math.LegacyZeroDec()
for _, slotIndexScore := range sloitIndexScores {
calculatedScore = calculatedScore.Add(slotIndexScore)
}
Expand All @@ -59,8 +59,8 @@ func generateScores(count int, slotFilterIndex int) []*PairingScore {
for i := 0; i < count; i++ {
pairingScore := &PairingScore{
Provider: nil,
Score: math.ZeroUint().AddUint64(100),
ScoreComponents: map[string]math.Uint{},
Score: math.LegacyNewDec(100),
ScoreComponents: map[string]math.LegacyDec{},
}
if slotFilterIndex >= 0 {
pairingScore.SlotFiltering = map[int]struct{}{slotFilterIndex: {}}
Expand All @@ -75,8 +75,8 @@ func generateScoresWithRandomFilter(count int) []*PairingScore {
for i := 0; i < count; i++ {
pairingScore := &PairingScore{
Provider: nil,
Score: math.ZeroUint().AddUint64(100),
ScoreComponents: map[string]math.Uint{},
Score: math.LegacyNewDec(100),
ScoreComponents: map[string]math.LegacyDec{},
}
pairingScore.SlotFiltering = map[int]struct{}{rand.Int(): {}}
ret = append(ret, pairingScore)
Expand Down
9 changes: 4 additions & 5 deletions x/pairing/keeper/scores/stake_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package scores

import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
planstypes "github.com/lavanet/lava/v4/x/plans/types"
)

Expand All @@ -16,15 +15,15 @@ func (sr *StakeReq) Init(policy planstypes.Policy) bool {
}

// Score calculates the the provider score as the normalized stake
func (sr *StakeReq) Score(score PairingScore) math.Uint {
func (sr *StakeReq) Score(score PairingScore) math.LegacyDec {
if sr == nil {
return math.OneUint()
return math.LegacyOneDec()
}
effectiveStake := score.Provider.TotalStake()
if !effectiveStake.IsPositive() {
return math.OneUint()
return math.LegacyOneDec()
}
return sdk.NewUint(effectiveStake.Uint64())
return math.LegacyNewDecFromInt(effectiveStake)
}

func (sr *StakeReq) GetName() string {
Expand Down
Loading