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

CNS-359: add geolocation scoring #617

Merged
merged 46 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6f2c742
CNS-359: made the generic min max function to return the index of min…
oren-lava Jul 5, 2023
c057a8f
CNS-359: fix code that calls min/max funcs
oren-lava Jul 5, 2023
522bd17
CNS-359: added geolocation function to extract geoEnums from geo uint
oren-lava Jul 5, 2023
962c1ac
CNS-359: implemented geoReq
oren-lava Jul 5, 2023
dbc1316
CNS-359: update plans and geo latency table
oren-lava Jul 6, 2023
5e5d2ad
CNS-359: added geo req assignment to slots
oren-lava Jul 6, 2023
bade7c7
CNS-359: disabling the geo filter
oren-lava Jul 9, 2023
8d0f903
CNS-359: add Equal method to compare req values for slot grouping
oren-lava Jul 9, 2023
247f13f
CNS-359: enforce geo restrictions of plans + unit test
oren-lava Jul 9, 2023
0214871
CNS 359: added geo req to strategy
oren-lava Jul 11, 2023
45bb017
CNS-359: implemented calculating geo reqs for slots
oren-lava Jul 11, 2023
e4170bb
CNS-359: fix small bug in calling FindMin
oren-lava Jul 11, 2023
0098ec3
CNS-359: fixed geo scoring (larger latency now is worse score), added…
oren-lava Jul 11, 2023
60549df
CNS-359: made stake req hold min stake and fix stake normalization bug
oren-lava Jul 11, 2023
f7df516
CNS-359: fix unit tests
oren-lava Jul 11, 2023
d816610
CNS-359: fix stake score calculation
oren-lava Jul 12, 2023
c7a62b3
CNS 359: fixed CalcSlots and GeoReq Score()
oren-lava Jul 13, 2023
c00ba87
CNS-359: added unit test that checks the geo scoring
oren-lava Jul 13, 2023
434a5c5
CNS-359: added safe power function for score^weight to avoid using float
oren-lava Jul 13, 2023
d7bfe01
CNS-359: duplicate providers unit test
oren-lava Jul 13, 2023
a1b5193
CNS-359: test to verify we get providers even if there is no one with…
oren-lava Jul 13, 2023
5c99a24
Merge branch 'CNS-484-implement-generic-pairing-scoring-mechanism' in…
oren-lava Jul 13, 2023
b5caee0
CNS-359: fix comments
oren-lava Jul 17, 2023
f252c0e
CNS-359: reverted FindMin and created FindIndexOfMin (same for max)
oren-lava Jul 17, 2023
8b262bb
CNS-359: small fix of geo req Equal()
oren-lava Jul 17, 2023
1e34649
CNS-359: small fix
oren-lava Jul 17, 2023
bd878b4
CNS-359: changed CalcGeoCost and GeoLatencyMap
oren-lava Jul 17, 2023
3e3112d
CNS-359: PR fixes
oren-lava Jul 18, 2023
76f0ff8
CNS-359: GetGeoReqsForSlots -> GetGeoReqsForSlot
oren-lava Jul 18, 2023
0a7ed1c
CNS-359: unittest to verify that geo req always holds single bit geo …
oren-lava Jul 18, 2023
6f438b4
Merge branch 'CNS-484-implement-generic-pairing-scoring-mechanism' in…
oren-lava Jul 18, 2023
2ebcb45
CNS-359: small fixes
oren-lava Jul 18, 2023
16a9bfa
CNS-359: fix lint issues
oren-lava Jul 18, 2023
7580a3b
CNS-359: small fix
oren-lava Jul 18, 2023
0a0ec8c
CNS-359: small PR fix
oren-lava Jul 19, 2023
9e7d567
CNS-359: fix TestGeoSlotCalc unit test
oren-lava Jul 19, 2023
22b00fd
CNS-359: PR changes
oren-lava Jul 23, 2023
f97207e
Merge branch 'CNS-484-implement-generic-pairing-scoring-mechanism' in…
oren-lava Jul 23, 2023
99403cf
CNS-359: fix after merge with CNS-484
oren-lava Jul 23, 2023
7948ea1
CNS-359: added geo req to GetAllReqs
oren-lava Jul 23, 2023
de5fd0a
Merge branch 'CNS-484-implement-generic-pairing-scoring-mechanism' in…
oren-lava Jul 23, 2023
d7b83f5
Merge branch 'main' into CNS-359-add-geolocation-profiles
oren-lava Jul 25, 2023
8b3ebb2
CNS-359: fix unit test
oren-lava Jul 25, 2023
a5a242c
CNS-359: check denom is ulava
oren-lava Jul 27, 2023
701d576
Merge branch 'main' into CNS-359-add-geolocation-profiles
oren-lava Jul 27, 2023
e610fa2
CNS-359: small fixes
oren-lava Jul 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions common/types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,53 @@ import (
"golang.org/x/exp/constraints"
)

func FindMin[T constraints.Ordered](s []T) (m T) {
if len(s) > 0 {
m = s[0]
for _, v := range s[1:] {
if m > v {
m = v
}
func FindMin[T constraints.Ordered](s []T) T {
ind := FindIndexOfMin(s)
if ind == -1 {
var zero T
return zero
}

return s[ind]
}

func FindIndexOfMin[T constraints.Ordered](s []T) int {
if len(s) == 0 {
return -1
}
m := s[0]
mInd := 0
for i := range s {
if m > s[i] {
mInd = i
}
}
return m
return mInd
}

func FindMax[T constraints.Ordered](s []T) (m T) {
if len(s) > 0 {
m = s[0]
for _, v := range s[1:] {
if m < v {
m = v
}
func FindMax[T constraints.Ordered](s []T) T {
ind := FindIndexOfMax(s)
if ind == -1 {
var zero T
return zero
}

return s[ind]
}

func FindIndexOfMax[T constraints.Ordered](s []T) int {
if len(s) == 0 {
return -1
}
m := s[0]
mInd := 0
for i := range s {
if m < s[i] {
m = s[i]
mInd = i
}
}
return m
return mInd
}

func Intersection[T comparable](arrays ...[]T) []T {
Expand Down
12 changes: 6 additions & 6 deletions cookbook/plans/adventurer.json → cookbook/plans/basic.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"proposal": {
"title": "Add plan proposal: advneturer",
"description": "A proposal for a new plan for a mid-range user",
"title": "Add plan proposal: Basic",
"description": "A proposal for a new plan for a basic user",
"plans": [
{
"index": "adventurer",
"description": "Plan for the Lava adventurer",
"index": "basic",
"description": "Plan for the basic Lava user",
"type": "rpc",
"price": {
"denom": "ulava",
Expand All @@ -15,10 +15,10 @@
"allow_overuse": false,
"overuse_rate": "0",
"plan_policy": {
"geolocation_profile": "18446744073709551615",
"geolocation_profile": "0",
omerlavanet marked this conversation as resolved.
Show resolved Hide resolved
"total_cu_limit": "1000000000",
"epoch_cu_limit": "5000000",
"max_providers_to_pair": "5"
"max_providers_to_pair": "14"
}
}
]
Expand Down
27 changes: 27 additions & 0 deletions cookbook/plans/enterprise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"proposal": {
"title": "Add plan proposal: Enterprise",
"description": "A proposal for a new plan for a enterprise user",
"plans": [
{
"index": "enterprise",
"description": "Plan for the enterprise Lava user",
"type": "rpc",
"price": {
"denom": "ulava",
"amount": "600000000000"
},
"annual_discount_percentage": "0",
"allow_overuse": false,
"overuse_rate": "0",
"plan_policy": {
"geolocation_profile": "65535",
omerlavanet marked this conversation as resolved.
Show resolved Hide resolved
"total_cu_limit": "9223372036854775807",
omerlavanet marked this conversation as resolved.
Show resolved Hide resolved
"epoch_cu_limit": "9223372036854775807",
"max_providers_to_pair": "28"
}
}
]
},
"deposit": "10000000ulava"
}
10 changes: 5 additions & 5 deletions cookbook/plans/explorer.json → cookbook/plans/free.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"proposal": {
"title": "Add plan proposal: explorer",
"description": "A proposal for a new plan for a beginner user",
"title": "Add plan proposal: free",
"description": "A proposal for a new plan for a free user",
"plans": [
{
"index": "explorer",
"description": "Plan for the Lava explorer",
"index": "free",
"description": "Plan for the free Lava user",
"type": "rpc",
"price": {
"denom": "ulava",
Expand All @@ -15,7 +15,7 @@
"allow_overuse": false,
"overuse_rate": "0",
"plan_policy": {
"geolocation_profile": "18446744073709551615",
"geolocation_profile": "4",
omerlavanet marked this conversation as resolved.
Show resolved Hide resolved
"total_cu_limit": "10000000",
"epoch_cu_limit": "100000",
"max_providers_to_pair": "5"
Expand Down
14 changes: 7 additions & 7 deletions cookbook/plans/whale.json → cookbook/plans/premium.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"proposal": {
"title": "Add plan proposal: whale",
"description": "A proposal for a new plan for an advanced user",
"title": "Add plan proposal: Premium",
"description": "A proposal for a new plan for a premium user",
"plans": [
{
"index": "whale",
"description": "Plan for the Lava whale",
"index": "premium",
"description": "Plan for the premium Lava user",
"type": "rpc",
"price": {
"denom": "ulava",
"amount": "100000000000000"
"amount": "600000000000"
},
"annual_discount_percentage": "0",
"allow_overuse": false,
"overuse_rate": "0",
"plan_policy": {
"geolocation_profile": "18446744073709551615",
"geolocation_profile": "65535",
"total_cu_limit": "9223372036854775807",
"epoch_cu_limit": "9223372036854775807",
"max_providers_to_pair": "5"
"max_providers_to_pair": "14"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion x/pairing/client/cli/tx_stake_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func HandleEndpointsAndGeolocationArgs(endpArg []string, geoArg string) (endp []

if geoloc == int32(planstypes.Geolocation_GL) {
// if global ("GL"), append the endpoint in all possible geolocations
for _, geoloc := range types.GetGeolocations() {
for _, geoloc := range types.GetAllGeolocations() {
endpoint := epochstoragetypes.Endpoint{
IPPORT: split[0],
Geolocation: uint64(geoloc),
Expand Down
15 changes: 9 additions & 6 deletions x/pairing/keeper/filters/geolocation_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import (
planstypes "github.com/lavanet/lava/x/plans/types"
)

// TODO: This is a temp filter until the geolocation mechanism changes (this is not optimal)
// TODO: This filter is disabled (InitFilter always returns false)
// will be used in the future to exclude geolocations (thus keeping this code)

type GeolocationFilter struct {
geolocation uint64
}

func (f *GeolocationFilter) InitFilter(strictestPolicy planstypes.Policy) bool {
if strictestPolicy.SelectedProvidersMode == planstypes.SELECTED_PROVIDERS_MODE_DISABLED ||
strictestPolicy.SelectedProvidersMode == planstypes.SELECTED_PROVIDERS_MODE_ALLOWED {
f.geolocation = strictestPolicy.GeolocationProfile
return true
}
/*
if strictestPolicy.SelectedProvidersMode == planstypes.SELECTED_PROVIDERS_MODE_DISABLED ||
strictestPolicy.SelectedProvidersMode == planstypes.SELECTED_PROVIDERS_MODE_ALLOWED {
f.geolocation = strictestPolicy.GeolocationProfile
return true
}
*/
return false
}

Expand Down
13 changes: 8 additions & 5 deletions x/pairing/keeper/grpc_query_user_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ func (k Keeper) UserEntry(goCtx context.Context, req *types.QueryUserEntryReques
return nil, err
}

sub, found := k.subscriptionKeeper.GetSubscription(ctx, project.GetSubscription())
if !found {
return nil, fmt.Errorf("could not find subscription with address %s", project.GetSubscription())
}

plan, err := k.subscriptionKeeper.GetPlanFromSubscription(ctx, project.GetSubscription())
if err != nil {
return nil, err
Expand All @@ -43,11 +48,9 @@ func (k Keeper) UserEntry(goCtx context.Context, req *types.QueryUserEntryReques
planPolicy := plan.GetPlanPolicy()
policies := []*planstypes.Policy{&planPolicy, project.AdminPolicy, project.SubscriptionPolicy}
// geolocation is a bitmap. common denominator can be calculated with logical AND
geolocation := k.CalculateEffectiveGeolocationFromPolicies(policies)

sub, found := k.subscriptionKeeper.GetSubscription(ctx, project.GetSubscription())
if !found {
return nil, fmt.Errorf("could not find subscription with address %s", project.GetSubscription())
geolocation, err := k.CalculateEffectiveGeolocationFromPolicies(policies)
if err != nil {
return nil, err
}
allowedCU, _ := k.CalculateEffectiveAllowedCuPerEpochFromPolicies(policies, project.GetUsedCu(), sub.GetMonthCuLeft())

Expand Down
21 changes: 15 additions & 6 deletions x/pairing/keeper/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ func (k Keeper) getPairingForClient(ctx sdk.Context, chainID string, clientAddre
// calculate score (always on the diff in score components of consecutive groups) and pick providers
prevGroupSlot := pairingscores.NewPairingSlot() // init dummy slot to compare to
prevGroupSlot.Reqs = map[string]pairingscores.ScoreReq{}

for idx, group := range slotGroups {
hashData := pairingscores.PrepareHashData(project.Index, chainID, epochHash, idx)
diffSlot := group.Subtract(prevGroupSlot)
Expand All @@ -175,7 +174,6 @@ func (k Keeper) getPairingForClient(ctx sdk.Context, chainID string, clientAddre
}
pickedProviders := pairingscores.PickProviders(ctx, providerScores, group.Count, hashData)
providers = append(providers, pickedProviders...)

prevGroupSlot = group
}

Expand All @@ -201,7 +199,10 @@ func (k Keeper) GetProjectStrictestPolicy(ctx sdk.Context, project projectstypes
return planstypes.Policy{}, fmt.Errorf("chain ID not allowed in all policies, or collections specified and have no intersection %#v", policies)
}

geolocation := k.CalculateEffectiveGeolocationFromPolicies(policies)
geolocation, err := k.CalculateEffectiveGeolocationFromPolicies(policies)
if err != nil {
return planstypes.Policy{}, err
}

providersToPair, err := k.CalculateEffectiveProvidersToPairFromPolicies(policies)
if err != nil {
Expand Down Expand Up @@ -247,17 +248,25 @@ func (k Keeper) CalculateEffectiveSelectedProviders(policies []*planstypes.Polic
return effectiveMode, effectiveSelectedProviders
}

func (k Keeper) CalculateEffectiveGeolocationFromPolicies(policies []*planstypes.Policy) uint64 {
func (k Keeper) CalculateEffectiveGeolocationFromPolicies(policies []*planstypes.Policy) (uint64, error) {
geolocation := uint64(math.MaxUint64)

// geolocation is a bitmap. common denominator can be calculated with logical AND
for _, policy := range policies {
if policy != nil {
geolocation &= policy.GetGeolocationProfile()
geo := policy.GetGeolocationProfile()
if geo == uint64(planstypes.Geolocation_value["GLS"]) {
return uint64(planstypes.Geolocation_value["GL"]), nil
}
geolocation &= geo
}
}

return geolocation
if geolocation == 0 {
return 0, utils.LavaFormatWarning("invalid strictest geolocation", fmt.Errorf("strictest geo = 0"))
}

return geolocation, nil
}

func (k Keeper) CalculateEffectiveProvidersToPairFromPolicies(policies []*planstypes.Policy) (uint64, error) {
Expand Down
24 changes: 13 additions & 11 deletions x/pairing/keeper/pairing_subscription_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func TestStrictestPolicyGeolocation(t *testing.T) {
ts.plan.PlanPolicy.GeolocationProfile = 7
ts.AddPlan("mock", ts.plan)

ts.setupForPayments(1, 1, 0) // 1 provider, 0 client, default providers-to-pair
ts.setupForPayments(1, 1, 0) // 1 provider, 1 client, default providers-to-pair

_, client1Addr := ts.GetAccount(common.CONSUMER, 0)

Expand All @@ -209,13 +209,14 @@ func TestStrictestPolicyGeolocation(t *testing.T) {
name string
geolocationAdminPolicy uint64
geolocationSubPolicy uint64
success bool
expectedProviderPaired int
validPairing bool
}{
{"effective geo = 1", uint64(1), uint64(1), true},
{"effective geo = 3 (includes geo=1)", uint64(3), uint64(3), true},
{"effective geo = 2", uint64(3), uint64(2), false},
{"effective geo = 0 (planPolicy & subPolicy = 1)", uint64(2), uint64(1), false},
{"effective geo = 0 (planPolicy & adminPolicy = 1)", uint64(1), uint64(2), false},
{"effective geo = 1", uint64(1), uint64(1), len(ts.Accounts(common.PROVIDER)), true},
{"effective geo = 3 (includes geo=1)", uint64(3), uint64(3), len(ts.Accounts(common.PROVIDER)), true},
{"effective geo = 2", uint64(3), uint64(2), len(ts.Accounts(common.PROVIDER)), true},
{"effective geo = 0 (planPolicy & subPolicy = 1)", uint64(2), uint64(1), 0, false},
{"effective geo = 0 (planPolicy & adminPolicy = 1)", uint64(1), uint64(2), 0, false},
}

for _, tt := range geolocationTestTemplates {
Expand All @@ -242,12 +243,13 @@ func TestStrictestPolicyGeolocation(t *testing.T) {
// the only provider is set with geolocation=1. So only geolocation that ANDs
// with 1 and output non-zero result, will output a provider for pairing
res, err := ts.QueryPairingGetPairing(ts.spec.Index, client1Addr)
require.Nil(t, err)
if tt.success {
require.NotEqual(t, 0, len(res.Providers))
if tt.validPairing {
require.Nil(t, err)
} else {
require.Equal(t, 0, len(res.Providers))
require.NotNil(t, err)
return
}
require.Equal(t, tt.expectedProviderPaired, len(res.Providers))
})
}
}
Expand Down
Loading