From 608837373da3b86d629bf83797b240e8c414fbe5 Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Tue, 26 Dec 2023 14:45:04 +0000 Subject: [PATCH 01/52] added spec readme --- x/spec/README.md | 228 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 x/spec/README.md diff --git a/x/spec/README.md b/x/spec/README.md new file mode 100644 index 0000000000..921b630944 --- /dev/null +++ b/x/spec/README.md @@ -0,0 +1,228 @@ +# `x/spec` + +## Abstract + +This document specifies the spec module of Lava Protocol. + +The spec module is responsible for managing Lava's chains specifications. a spec is a struct difining all the api's of a specific chain and its bahaviours. The specs determines the chain properties like block time, finalization distance, imports and more. Adding a spec is done with a proposal on chain with the gov module. The first step of providers and subscriptions to work in lava is having a specification for the wanted chain. + +This document is focuses on the spec' technical aspects. For more information on chain support in lava see pairing README. + +## Contents +* [Concepts](#concepts) + * [Spec](#spec) + * [ApiCollections](#apicollections) + * [CollectionData](#collectiondata) + * [ParseDirective](#parsedirective) + * [Verification](#verification) + * [Import](#import) +* [Parameters](#parameters) +* [Queries](#queries) +* [Transactions](#transactions) +* [Proposals](#proposals) + +## Concepts + +### Spec + +A chain spec consists of general properties of the chain and a list of interface it supports. To make specs simple to create and maintain specs can import api's from another, for example, X testnet spec can Import from X mainnet spec and thus not need to redifine all of the interfaces. + +``` +type Spec struct { + Index string // chain index (index key for the chain) + Name string // description string of the spec + Enabled bool // spec enabled/disable (base specs are usually disabled, like cosmos base specs) + AverageBlockTime int64 // average block time of the chain in msec + MinStakeProvider Coin // min stake for a provider to be active in the chain + ProvidersTypes Spec_ProvidersTypes // determines if the spec is for lava or others + Imports []string // list of chains to import ApiCollections from + ApiCollections []*ApiCollection // list of ApiCollections that defines all the interfaces and api's of the chain + Contributor []string // list of contributers (public lava address {lava@...}) + ContributorPercentage *Dec // the percentage of coins the contributers will get from each reward a provider get + Shares uint64 // factor for bonus rewards at the end of the month (see rewards module) + AllowedBlockLagForQosSync int64 // defines the accepted blocks a provider can be behind the chain without QOS degradation + BlockLastUpdated uint64 // the last block this spec was updated on chain + ReliabilityThreshold uint32 // this determines the amount of data reliability for the chain + DataReliabilityEnabled bool // enables/disables data reliability for the chain + BlockDistanceForFinalizedData uint32 + BlocksInFinalizationProof uint32 +} +``` +Note, the `Coin` type is from Cosmos-SDK (`cosmos.base.v1beta1.Coin`). +Note, the `Dec` type is from Cosmos-SDK math LegacyDec. + +### ApiCollection + +ApiCollection is a struct that defines an interface, for example rest, json etc.., and all of its api's and properties + +``` +type ApiCollection struct { + Enabled bool // enables/disables the collection + CollectionData CollectionData // defines the properties of the collection, also acts as a unique key + Apis []*Api // list of api's in the collection + Headers []*Header // list of headers supported by the interface and their behaviour + InheritanceApis []*CollectionData // list of other ApiCollection to inherite from + ParseDirectives []*ParseDirective // list of parsing instructions of specific api's + Extensions []*Extension // list of extensions that providers can support in addition to the basic behaviour (for example, archive node) + Verifications []*Verification // list of verifications that providers must pass to make sure they provide full functionality +} +``` + +### CollectionData + +CollectionData defines the api properties and acts as a unique key for the [api collection](#apicollection) + +TAKE THIS FROM PLANS + +``` +type CollectionData struct { + ApiInterface string // defines the connection interface (rest/json/grpc etc...) + InternalPath string // defines internal path of the node for this specific ApiCollection + Type string // type of api (POST/GET) + AddOn string // +} +``` + +### ParseDirective + +ParseDirective is a struct that defines a function needed by the provider in a generic way. it describes how for a specific api collection how to get information from the node. for example, how to get the latest block of an EVM node. + +``` +type ParseDirective struct { + FunctionTag FUNCTION_TAG // defines what the function this serves for + FunctionTemplate string // api template to fill and send to the node + ResultParsing BlockParser // description for parsing the result of the api + ApiName string // the api name +} +``` + +FunctionTag can be one of the following : +``` +const ( + FUNCTION_TAG_GET_BLOCKNUM FUNCTION_TAG = 1 // get latest block number + FUNCTION_TAG_GET_BLOCK_BY_NUM FUNCTION_TAG = 2 // get a specific block by block numer + FUNCTION_TAG_SET_LATEST_IN_METADATA FUNCTION_TAG = 3 + FUNCTION_TAG_SET_LATEST_IN_BODY FUNCTION_TAG = 4 + FUNCTION_TAG_VERIFICATION FUNCTION_TAG = 5 +) +``` + +### Verification + +Verification is a struct that defines how to verify a specific property of the api collection, for example: verify the chain id of the node. + +type Verification struct { + Name string // verification name + ParseDirective *ParseDirective // [ParseDirective](#parsedirective) to get the the value to verify from the node + Values []*ParseValue // expected value we want from the result + Severity Verification_VerificationSeverity // instructions for what to do if a verification fails +} + +### Import + +## Parameters + +The plans module does not contain parameters. + +## Queries + +The plans module supports the following queries: + +| Query | Arguments | What it does | +| ---------- | --------------- | ----------------------------------------------| +| `list-spec` | none | shows all the specs | +| `params` | none | show the params of the module | +| `show-all-chains` | none | shows all the specs with minimal info | +| `show-chain-info` | chainid | shows a spec with minimal info | +| `show-spec` | chainid | shows a full spec | + +## Transactions + +The plans module does not support any transactions. + +## Proposals + +The plans module provides a proposal to add/overwrite a spec to the chain + +``` +lavad tx gov submit-legacy-proposal spec-add , --from alice + +``` + +A valid `add_spec_json_1` JSON proposal format: + +``` +{ + "proposal": { + "title": "Add Specs: Lava", + "description": "Adding new specification support for relaying Lava data on Lava", + "specs": [ + { + "index": "LAV1", + "name": "lava testnet", + "enabled": true, + "imports": [ + "COSMOSSDK" + ], + "providers_types": 1, + "reliability_threshold": 268435455, + "data_reliability_enabled": true, + "block_distance_for_finalized_data": 0, + "blocks_in_finalization_proof": 1, + "average_block_time": 30000, + "allowed_block_lag_for_qos_sync": 2, + "shares" : 1, + "min_stake_provider": { + "denom": "ulava", + "amount": "50000000000" + }, + "api_collections": [ + { + "enabled": true, + "collection_data": { + "api_interface": "rest", + "internal_path": "", + "type": "GET", + "add_on": "" + }, + "apis": [ + { + "name": "/lavanet/lava/spec/show_all_chains", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + } + ], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "lava-testnet" + } + ] + } + ] + } + ] + } + ] + }, + "deposit": "10000000ulava" +} +``` \ No newline at end of file From a662810e98f3f251513cd8ef0d347fc45f0e071b Mon Sep 17 00:00:00 2001 From: oren-lava Date: Tue, 26 Dec 2023 15:26:19 +0200 Subject: [PATCH 02/52] CNS-784: small fix plans readme --- x/plans/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/plans/README.md b/x/plans/README.md index fa49209c6d..d90e79542e 100644 --- a/x/plans/README.md +++ b/x/plans/README.md @@ -113,7 +113,7 @@ The `InternalPath` field is utilized for chains that have varying RPC API sets i The `Type` field lets the user define APIs that have different functionalities depending on their type. the valid types are: `GET` and `POST`. An example of such API is Cosmos' `/cosmos/tx/v1beta1/txs` API. If it's sent as a `GET` request, it fetches transactions by event and if it's sent as a `POST` request, it sends a transaction. -The `AddOn` field lets you use additional optional APIs like debug, trace, +The `AddOn` field lets you use additional optional APIs (debug, trace and more). Overall, the add-ons can be: `debug`, `trace` and `convex`. #### Geolocation From 9f1dec9bad19a9b6da9f304b06902859e9587d03 Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Tue, 26 Dec 2023 14:50:21 +0000 Subject: [PATCH 03/52] take from plan --- x/plans/README.md | 19 ++----------------- x/spec/README.md | 12 +++++++++--- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/x/plans/README.md b/x/plans/README.md index d90e79542e..28db50b4a7 100644 --- a/x/plans/README.md +++ b/x/plans/README.md @@ -86,7 +86,7 @@ A chain requirement is defined as follows: ``` struct ChainRequirement { - Collection types.CollectionData + Collection [types.CollectionData](https://github.com/lavanet/lava/tree/main/x/spec#CollectionData) Extensions []string Mixed bool } @@ -96,24 +96,9 @@ The `Extensions` field is intended to enable the use of certain APIs to obtain d The `Mixed` field is designed to enable a combination of regular and extension/addon supporting providers. For instance, if the `archive` extension is defined but the `Mixed` field is set to `false`, the consumer's project will only be paired with providers that support the specified extensions and addons. On the other hand, if the `Mixed` field is set to `true`, the consumer's project will also be paired with providers that don't fully support the extenstion/addons. -A chain collection is defined as follows: -``` -struct CollectionData { - ApiInterface string - InternalPath string - Type string - AddOn string -} -``` -The `ApiInterface` field defines the API interface on which the limitations are applied. The available API interfaces for a chain are defined in the chain's spec. Overall, the API interfaces can be: `jsonrpc`, `rest`, `tendermintrpc` and `grpc`. -The `InternalPath` field is utilized for chains that have varying RPC API sets in different internal paths. Avalanche is a prime example of such a chain, consisting of three distinct subchains (or subnets) designed for different applications. For instance, Avalanche's C-Chain is dedicated to smart contracts, while Avalanche's X-Chain facilitates the sending and receiving of funds. For further information on how to define this field, please consult the Avalanche (AVAX) specification. - -The `Type` field lets the user define APIs that have different functionalities depending on their type. the valid types are: `GET` and `POST`. An example of such API is Cosmos' `/cosmos/tx/v1beta1/txs` API. If it's sent as a `GET` request, it fetches transactions by event and if it's sent as a `POST` request, it sends a transaction. - -The `AddOn` field lets you use additional optional APIs (debug, trace and more). Overall, the add-ons can be: `debug`, `trace` and `convex`. #### Geolocation @@ -186,7 +171,7 @@ lavad tx gov vote yes --from alice A valid `plans-add` JSON proposal format: -``` +```json { "proposal": { "title": "Add temporary to-delete plan proposal", diff --git a/x/spec/README.md b/x/spec/README.md index 921b630944..7aba43dba2 100644 --- a/x/spec/README.md +++ b/x/spec/README.md @@ -72,8 +72,6 @@ type ApiCollection struct { CollectionData defines the api properties and acts as a unique key for the [api collection](#apicollection) -TAKE THIS FROM PLANS - ``` type CollectionData struct { ApiInterface string // defines the connection interface (rest/json/grpc etc...) @@ -83,6 +81,14 @@ type CollectionData struct { } ``` +The `ApiInterface` field defines the API interface on which the limitations are applied. The available API interfaces for a chain are defined in the chain's spec. Overall, the API interfaces can be: `jsonrpc`, `rest`, `tendermintrpc` and `grpc`. + +The `InternalPath` field is utilized for chains that have varying RPC API sets in different internal paths. Avalanche is a prime example of such a chain, consisting of three distinct subchains (or subnets) designed for different applications. For instance, Avalanche's C-Chain is dedicated to smart contracts, while Avalanche's X-Chain facilitates the sending and receiving of funds. For further information on how to define this field, please consult the Avalanche (AVAX) specification. + +The `Type` field lets the user define APIs that have different functionalities depending on their type. the valid types are: `GET` and `POST`. An example of such API is Cosmos' `/cosmos/tx/v1beta1/txs` API. If it's sent as a `GET` request, it fetches transactions by event and if it's sent as a `POST` request, it sends a transaction. + +The `AddOn` field lets you use additional optional APIs like debug, trace, + ### ParseDirective ParseDirective is a struct that defines a function needed by the provider in a generic way. it describes how for a specific api collection how to get information from the node. for example, how to get the latest block of an EVM node. @@ -151,7 +157,7 @@ lavad tx gov submit-legacy-proposal spec-add , --from A valid `add_spec_json_1` JSON proposal format: -``` +```json { "proposal": { "title": "Add Specs: Lava", From e4669be1deff7c2767b989a1783fd7af65bdc0bb Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Tue, 26 Dec 2023 15:10:41 +0000 Subject: [PATCH 04/52] done --- x/spec/README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/x/spec/README.md b/x/spec/README.md index 7aba43dba2..b6ed581bb4 100644 --- a/x/spec/README.md +++ b/x/spec/README.md @@ -62,7 +62,7 @@ type ApiCollection struct { Apis []*Api // list of api's in the collection Headers []*Header // list of headers supported by the interface and their behaviour InheritanceApis []*CollectionData // list of other ApiCollection to inherite from - ParseDirectives []*ParseDirective // list of parsing instructions of specific api's + ParseDirectives []*[ParseDirective](#parsedirective) // list of parsing instructions of specific api's Extensions []*Extension // list of extensions that providers can support in addition to the basic behaviour (for example, archive node) Verifications []*Verification // list of verifications that providers must pass to make sure they provide full functionality } @@ -89,6 +89,44 @@ The `Type` field lets the user define APIs that have different functionalities d The `AddOn` field lets you use additional optional APIs like debug, trace, +### Api + +Api define a specific api in the api collection + +``` +type Api struct { + Enabled bool // enable/disable the api + Name string // api name + ComputeUnits uint64 // the amount of cu of this api (can be defined as the "price" of using this api) + ExtraComputeUnits uint64 // not used + Category SpecCategory // defines the property of the api + BlockParsing BlockParser // specify how to parse the block from the api request + TimeoutMs uint64 // specifies the timeout expected for the api +} +``` + +example of an api definition: +```json + { + "name": "eth_getBlockByNumber", + "block_parsing": { + "parser_arg": [ + "0" + ], + "parser_func": "PARSE_BY_ARG" + }, + "compute_units": 20, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, +``` + ### ParseDirective ParseDirective is a struct that defines a function needed by the provider in a generic way. it describes how for a specific api collection how to get information from the node. for example, how to get the latest block of an EVM node. @@ -126,6 +164,15 @@ type Verification struct { ### Import +Specs can import from other specs, this allows easy creation and maintanance of specs. the specs imports the api collection of the base specs. +A good example for this is cosmos base specs. all cosmos chains support querie/tx of the bank module and are defines in a cosmos spec (which is disabled), when creating a cosmos base spec we can import the cosmos spec and we get all the api's of the bank module. this makes it so specs need to define only the api's that are unique to the new chain. + +rules: +* an import cycle of specs is prohibited +* specs can override/disable/add api's from the imported api collection +* specs imports the verificaions, they can also be overridden and sometimes it is a must (for example chain-id value must be overwritten) + + ## Parameters The plans module does not contain parameters. From c27789d1e6536c13b3ec9f221ef6ede99ed5c4dc Mon Sep 17 00:00:00 2001 From: oren-lava Date: Wed, 27 Dec 2023 15:58:10 +0200 Subject: [PATCH 05/52] CNS-770: fix pairing event --- x/pairing/keeper/msg_server_relay_payment.go | 12 +++++++++++- x/pairing/types/types.go | 12 +++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/x/pairing/keeper/msg_server_relay_payment.go b/x/pairing/keeper/msg_server_relay_payment.go index 160c3d7d5e..c84bfe11cd 100644 --- a/x/pairing/keeper/msg_server_relay_payment.go +++ b/x/pairing/keeper/msg_server_relay_payment.go @@ -235,7 +235,17 @@ func (k msgServer) RelayPayment(goCtx context.Context, msg *types.MsgRelayPaymen // update provider payment storage with complainer's CU err = k.updateProviderPaymentStorageWithComplainerCU(ctx, relay.UnresponsiveProviders, logger, epochStart, relay.SpecId, relay.CuSum, servicersToPair, project.Index) if err != nil { - utils.LogLavaEvent(ctx, logger, types.UnresponsiveProviderUnstakeFailedEventName, map[string]string{"err:": err.Error()}, "Error Unresponsive Providers could not unstake") + reportedProviders := "" + for _, p := range relay.UnresponsiveProviders { + reportedProviders += p.String() + } + utils.LavaFormatError("failed to update complainers CU for providers", err, + utils.Attribute{Key: "reported_providers", Value: reportedProviders}, + utils.Attribute{Key: "epoch", Value: strconv.FormatUint(epochStart, 10)}, + utils.Attribute{Key: "chain_id", Value: relay.SpecId}, + utils.Attribute{Key: "cu", Value: strconv.FormatUint(relay.CuSum, 10)}, + utils.Attribute{Key: "project_index", Value: project.Index}, + ) } rejectedCu -= relay.CuSum rejected_relays_num-- diff --git a/x/pairing/types/types.go b/x/pairing/types/types.go index 884178109c..8aab58a8ea 100644 --- a/x/pairing/types/types.go +++ b/x/pairing/types/types.go @@ -5,13 +5,11 @@ const ( ProviderStakeUpdateEventName = "stake_update_provider" ProviderUnstakeEventName = "provider_unstake_commit" - ConsumerInsufficientFundsToStayStakedEventName = "consumer_insufficient_funds_to_stay_staked" - RelayPaymentEventName = "relay_payment" - UnresponsiveProviderUnstakeFailedEventName = "unresponsive_provider" - ProviderJailedEventName = "provider_jailed" - ProviderReportedEventName = "provider_reported" - LatestBlocksReportEventName = "provider_latest_block_report" - RejectedCuEventName = "rejected_cu" + RelayPaymentEventName = "relay_payment" + ProviderJailedEventName = "provider_jailed" + ProviderReportedEventName = "provider_reported" + LatestBlocksReportEventName = "provider_latest_block_report" + RejectedCuEventName = "rejected_cu" ) // unstake description strings From 42c6aababafd026b9ac2860b9bd0d6fcdf741c39 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Wed, 27 Dec 2023 16:50:07 +0200 Subject: [PATCH 06/52] CNS-770: fix more pairing events --- x/pairing/keeper/msg_server_relay_payment.go | 2 +- x/pairing/proposal_handler.go | 2 +- x/pairing/types/types.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x/pairing/keeper/msg_server_relay_payment.go b/x/pairing/keeper/msg_server_relay_payment.go index c84bfe11cd..4e800f313f 100644 --- a/x/pairing/keeper/msg_server_relay_payment.go +++ b/x/pairing/keeper/msg_server_relay_payment.go @@ -237,7 +237,7 @@ func (k msgServer) RelayPayment(goCtx context.Context, msg *types.MsgRelayPaymen if err != nil { reportedProviders := "" for _, p := range relay.UnresponsiveProviders { - reportedProviders += p.String() + reportedProviders += p.String() + ", " } utils.LavaFormatError("failed to update complainers CU for providers", err, utils.Attribute{Key: "reported_providers", Value: reportedProviders}, diff --git a/x/pairing/proposal_handler.go b/x/pairing/proposal_handler.go index 876b5a52af..32620c3273 100644 --- a/x/pairing/proposal_handler.go +++ b/x/pairing/proposal_handler.go @@ -70,7 +70,7 @@ func handleUnstakeProposal(ctx sdk.Context, k keeper.Keeper, p *types.UnstakePro details["providers_not_staked_from_before"] = strings.Join(providersNotStaked, ";") details["providers_failed_unstaking"] = strings.Join(providersFailedUnstaking, ";") - utils.LogLavaEvent(ctx, k.Logger(ctx), "unstake_gov_proposal", details, "Unstake gov proposal performed") + utils.LogLavaEvent(ctx, k.Logger(ctx), types.UnstakeProposalEventName, details, "Unstake gov proposal performed") return nil } diff --git a/x/pairing/types/types.go b/x/pairing/types/types.go index 8aab58a8ea..113dc3e95b 100644 --- a/x/pairing/types/types.go +++ b/x/pairing/types/types.go @@ -10,6 +10,7 @@ const ( ProviderReportedEventName = "provider_reported" LatestBlocksReportEventName = "provider_latest_block_report" RejectedCuEventName = "rejected_cu" + UnstakeProposalEventName = "unstake_gov_proposal" ) // unstake description strings From f2cfe505786f9c74e101f01151db8b2ea25b92dc Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Thu, 28 Dec 2023 13:06:27 +0000 Subject: [PATCH 07/52] pr changes --- x/spec/README.md | 69 ++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/x/spec/README.md b/x/spec/README.md index b6ed581bb4..fcd6cda3a6 100644 --- a/x/spec/README.md +++ b/x/spec/README.md @@ -4,14 +4,15 @@ This document specifies the spec module of Lava Protocol. -The spec module is responsible for managing Lava's chains specifications. a spec is a struct difining all the api's of a specific chain and its bahaviours. The specs determines the chain properties like block time, finalization distance, imports and more. Adding a spec is done with a proposal on chain with the gov module. The first step of providers and subscriptions to work in lava is having a specification for the wanted chain. +A specification, also known as a spec, defines the APIs that a provider commits to providing to consumers. An example of a spec is the Ethereum JSON-RPC spec, which defines the properties of the chain, all supported API calls and their compute units, also known as CUs. +Lava has multiple specs, and participants can add and modify specs using governance proposals. When adding a new blockchain, the first step to support a new chain in lava is to create a spec for it. -This document is focuses on the spec' technical aspects. For more information on chain support in lava see pairing README. +This document focuses on the specs' technical aspects and does not include current chain support in Lava. For more information on those, please visit Lava's official website. ## Contents * [Concepts](#concepts) * [Spec](#spec) - * [ApiCollections](#apicollections) + * [ApiCollections](#apicollection) * [CollectionData](#collectiondata) * [ParseDirective](#parsedirective) * [Verification](#verification) @@ -20,40 +21,43 @@ This document is focuses on the spec' technical aspects. For more information on * [Queries](#queries) * [Transactions](#transactions) * [Proposals](#proposals) +* [Events](#events) ## Concepts ### Spec -A chain spec consists of general properties of the chain and a list of interface it supports. To make specs simple to create and maintain specs can import api's from another, for example, X testnet spec can Import from X mainnet spec and thus not need to redifine all of the interfaces. +A chain spec consists of general properties of the chain and a list of interfaces it supports. To simplify the creation and maintenance of specs, they can import APIs from another spec. For example, the X testnet spec can import from the X mainnet spec, eliminating the need to redefine all of the interfaces. ``` type Spec struct { - Index string // chain index (index key for the chain) + Index string // chain unique index Name string // description string of the spec - Enabled bool // spec enabled/disable (base specs are usually disabled, like cosmos base specs) + Enabled bool // spec enabled/disable AverageBlockTime int64 // average block time of the chain in msec MinStakeProvider Coin // min stake for a provider to be active in the chain - ProvidersTypes Spec_ProvidersTypes // determines if the spec is for lava or others + ProvidersTypes Spec_ProvidersTypes // determines if the spec is for lava or chains Imports []string // list of chains to import ApiCollections from - ApiCollections []*ApiCollection // list of ApiCollections that defines all the interfaces and api's of the chain + ApiCollections []*ApiCollection // list of ApiCollections that defines all the interfaces and APIs of the chain Contributor []string // list of contributers (public lava address {lava@...}) ContributorPercentage *Dec // the percentage of coins the contributers will get from each reward a provider get Shares uint64 // factor for bonus rewards at the end of the month (see rewards module) AllowedBlockLagForQosSync int64 // defines the accepted blocks a provider can be behind the chain without QOS degradation BlockLastUpdated uint64 // the last block this spec was updated on chain - ReliabilityThreshold uint32 // this determines the amount of data reliability for the chain + ReliabilityThreshold uint32 // this determines the probability of data reliability checks by the consumer DataReliabilityEnabled bool // enables/disables data reliability for the chain BlockDistanceForFinalizedData uint32 BlocksInFinalizationProof uint32 } ``` -Note, the `Coin` type is from Cosmos-SDK (`cosmos.base.v1beta1.Coin`). -Note, the `Dec` type is from Cosmos-SDK math LegacyDec. +`Coin` type is from Cosmos-SDK (`cosmos.base.v1beta1.Coin`). +`Dec` type is from Cosmos-SDK math (`cosmossdk.io/math`). + +A `Contributor` is a member of the Lava community who can earn token commissions by maintaining specs on Lava. ### ApiCollection -ApiCollection is a struct that defines an interface, for example rest, json etc.., and all of its api's and properties +ApiCollection is a struct that defines an interface, such as REST, JSON, etc., along with all of its APIs and properties. ``` type ApiCollection struct { @@ -140,7 +144,7 @@ type ParseDirective struct { } ``` -FunctionTag can be one of the following : +`FunctionTag` can be one of the following : ``` const ( FUNCTION_TAG_GET_BLOCKNUM FUNCTION_TAG = 1 // get latest block number @@ -153,33 +157,36 @@ const ( ### Verification -Verification is a struct that defines how to verify a specific property of the api collection, for example: verify the chain id of the node. +The Verification struct defines how to verify a specific property of the API collection. For example, it can be used to verify the chain ID of the node. +``` type Verification struct { Name string // verification name - ParseDirective *ParseDirective // [ParseDirective](#parsedirective) to get the the value to verify from the node + ParseDirective *ParseDirective // ParseDirective to get the the value to verify from the node Values []*ParseValue // expected value we want from the result Severity Verification_VerificationSeverity // instructions for what to do if a verification fails } +``` ### Import -Specs can import from other specs, this allows easy creation and maintanance of specs. the specs imports the api collection of the base specs. -A good example for this is cosmos base specs. all cosmos chains support querie/tx of the bank module and are defines in a cosmos spec (which is disabled), when creating a cosmos base spec we can import the cosmos spec and we get all the api's of the bank module. this makes it so specs need to define only the api's that are unique to the new chain. +Specs can import from other specs, which allows for easy creation and maintenance of specs. Specs import the api collections of the base specs. + +A good example of this is the cosmos base specs. All cosmos chains support queries/transactions of the bank module, and these are defined in a cosmos spec (which is disabled since it is a base spec). When creating a cosmos based spec, we can import the cosmos spec and obtain all the APIs of the bank module. This means that specs only need to define the APIs that are unique to the new chain. -rules: -* an import cycle of specs is prohibited -* specs can override/disable/add api's from the imported api collection -* specs imports the verificaions, they can also be overridden and sometimes it is a must (for example chain-id value must be overwritten) +Rules: +* Import cycles of specs are prohibited. +* Specs can override/disable/add APIs from the imported API collection. +* Specs also import the verifications, which can be overridden when necessary (for example, the chain-id value must be overwritten). ## Parameters -The plans module does not contain parameters. +The Spec module does not contain parameters. ## Queries -The plans module supports the following queries: +The Spec module supports the following queries: | Query | Arguments | What it does | | ---------- | --------------- | ----------------------------------------------| @@ -191,11 +198,11 @@ The plans module supports the following queries: ## Transactions -The plans module does not support any transactions. +The Spec module does not support any transactions. ## Proposals -The plans module provides a proposal to add/overwrite a spec to the chain +The Spec module provides a proposal to add/overwrite a spec to the chain ``` lavad tx gov submit-legacy-proposal spec-add , --from alice @@ -278,4 +285,14 @@ A valid `add_spec_json_1` JSON proposal format: }, "deposit": "10000000ulava" } -``` \ No newline at end of file +``` + +### Events + + +The plans module has the following events: +| Event | When it happens | +| ---------- | --------------- | +| `spec_add` | a successful addition of a spec | +| `spec_modify` | a successful modification of an existing spec | +| `spec_refresh` | a spec was redreshed since it had a imported spec modified| \ No newline at end of file From 411536f2035405ffa4ef7e1707971414b268c561 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 10:57:19 -0500 Subject: [PATCH 08/52] Fix some tx and queries docs --- x/subscription/client/cli/query_list.go | 2 +- x/subscription/client/cli/query_list_projects.go | 2 +- x/subscription/client/cli/query_next_to_month_expiry.go | 2 +- x/subscription/client/cli/tx_buy.go | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/x/subscription/client/cli/query_list.go b/x/subscription/client/cli/query_list.go index 64ad1c0793..c60b5f03c3 100644 --- a/x/subscription/client/cli/query_list.go +++ b/x/subscription/client/cli/query_list.go @@ -14,7 +14,7 @@ var _ = strconv.Itoa(0) func CmdList() *cobra.Command { cmd := &cobra.Command{ Use: "list", - Short: "Query all current subscriptions to service packages", + Short: "Query all current subscriptions", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/subscription/client/cli/query_list_projects.go b/x/subscription/client/cli/query_list_projects.go index 76ae9f21c7..543ce42a08 100644 --- a/x/subscription/client/cli/query_list_projects.go +++ b/x/subscription/client/cli/query_list_projects.go @@ -14,7 +14,7 @@ var _ = strconv.Itoa(0) func CmdListProjects() *cobra.Command { cmd := &cobra.Command{ Use: "list-projects [subscription]", - Short: "Query to show all the subscription's projects", + Short: "Query all the subscription's projects", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { reqSubscription := args[0] diff --git a/x/subscription/client/cli/query_next_to_month_expiry.go b/x/subscription/client/cli/query_next_to_month_expiry.go index f892a88e42..a8b99c110e 100644 --- a/x/subscription/client/cli/query_next_to_month_expiry.go +++ b/x/subscription/client/cli/query_next_to_month_expiry.go @@ -14,7 +14,7 @@ var _ = strconv.Itoa(0) func CmdNextToMonthExpiry() *cobra.Command { cmd := &cobra.Command{ Use: "next-to-month-expiry", - Short: "Query for the subscription with the closest month expiry", + Short: "Query the subscriptions with the closest month expiry", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/subscription/client/cli/tx_buy.go b/x/subscription/client/cli/tx_buy.go index 838eb402b6..475051fa16 100644 --- a/x/subscription/client/cli/tx_buy.go +++ b/x/subscription/client/cli/tx_buy.go @@ -19,11 +19,12 @@ const ( func CmdBuy() *cobra.Command { cmd := &cobra.Command{ Use: "buy [plan-index] [optional: consumer] [optional: duration(months)]", - Short: "buy a service plan", + Short: "Buy a service plan", Long: `The buy command allows a user to buy or upgrade a subscription to a service plan for another user, effective next epoch. The consumer is the beneficiary user (default: the creator). The duration is stated in number of months (default: 1). -If the plan index is different than the consumer's current plan, it will upgrade to that plan index.`, +If the plan index is different than the consumer's current plan, it will upgrade to that plan index. +If the plan index is the same as the consumer's current plan, it will extend the current subscription by [duration].`, Example: `required flags: --from , optional flags: --enable-auto-renewal lavad tx subscription buy [plan-index] --from lavad tx subscription buy [plan-index] --from 12 From f420a2c421d01f145ac913b77f9695e6bb553cd6 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 11:01:32 -0500 Subject: [PATCH 09/52] Add README for subscription module --- x/subscription/README.md | 201 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 x/subscription/README.md diff --git a/x/subscription/README.md b/x/subscription/README.md new file mode 100644 index 0000000000..1f158ff5c2 --- /dev/null +++ b/x/subscription/README.md @@ -0,0 +1,201 @@ +# `x/subscription` + +## Abstract + +This document specifies the subscription module of Lava Protocol. + +The subscription module is responsible for managing Lava's consumer subscription. +When a consumer wants to use Lava, they need to purchase a subscription. +The subscription is monthly - meaning - it starts in the next block after it was bought, and ends 1 month later. +If the user has bought more than 1 month, the subscription will reset at the end of each month until it expires. +When a subscription is reset, the monthly left CUs is set to the plan's monthly CU. + +A subscription can be renewed, manually or automatically. +Additionally, a subscription plan can be bought in advance for months ahead. + +There are 2 other concepts that the subscription is connected to: + +- [Plans](https://github.com/lavanet/lava/blob/main/x/pairing/README.md) +- [Projects](https://github.com/lavanet/lava/blob/main/x/projects/README.md) + +To fully understand this module, it is highly recommended to read Plans and Projects READMEs as well. + +## Contents + +- [Concepts](#concepts) + - [Subscription](#subscription) + - [Advance Month](#advance-month) + - [Subscription Upgrade](#subscription-upgrade) + - [Subscription Renewal](#subscription-renewal) + - [Advance Purchase](#advance-purchase) + - [Auto Renewal](#auto-renewal) +- [Parameters](#parameters) +- [Queries](#queries) +- [Transactions](#transactions) +- [Proposals](#proposals) + +## Concepts + +### Subscription + +In order for a consumer to purchase a subscription, they first must choose a plan to use. +Once a plan has been chosen, to perform the purchase, they can use the transaction command: + +```bash +lavad tx subscription buy [plan-index] [optional: consumer] [optional: duration(months)] [flags] +``` + +More on the `buy` transaction is under the [Transactions](#transactions) part under Buy. + +Once a consumer bought a subscription, a new instance of Subscription object will be saved into the state: + +```go +struct Subscription { + Creator string // creator pays for the subscription + Consumer string // consumer uses the subscription + Block uint64 // when the subscription was last recharged + PlanIndex string // index (name) of plan + PlanBlock uint64 // when the plan was created + DurationBought uint64 // total requested duration in months + DurationLeft uint64 // remaining duration in months + MonthExpiryTime uint64 // expiry time of current month + MonthCuTotal uint64 // CU allowance during current month + MonthCuLeft uint64 // CU remaining during current month + Cluster string // cluster key + DurationTotal uint64 // continuous subscription usage in months + AutoRenewal bool // automatic renewal when the subscription expires + FutureSubscription *FutureSubscription // future subscription made with buy --advance-purchase +} + +struct FutureSubscription { + Creator string // creator pays for the future subscription. Will replace the original one once activated + PlanIndex string // index (name) of plan + PlanBlock uint64 // when the plan was created + DurationBought uint64 // total requested duration in months +} +``` + +When a consumer buys a subscription, an admin project is automatically generated for them. +When the creator and the consumer are not equal - meaning - someone is buying a subscription for somebody else, the tokens will be taken out of the creator's account. + +Subscriptions are saved in a fixation store (see [FixationStore](https://github.com/lavanet/lava/blob/main/x/fixationstore/README.md) module for reference). + +There could only be a single active subscription for a consumer. +When a consumer purchase a subscription, the tokens are transferred from their account immediately. +Those tokens are later on distributed among the providers that served that consumer, and the delegators that staked those providers. + +### Advance Month + +One of the primary processes within this module involves the function `advanceMonth`, which is invoked upon the expiration of a timer set by the subscription. This function holds significance as it manages the logic that occurs at the conclusion of each month of a subscription. + +Here's a high-level flow of the function: + +1. **Update the CU Tracker Timer:** + + - Add a CU tracker timer for the subscription. + +2. **Check Subscription Duration:** + + - If the subscription’s remaining duration (`DurationLeft`) is zero: + - This is a bug! Log this and extend by 1 month to for smoother operation and investigation. + - If there is still time left (`DurationLeft` > 0): + - reduce it by one to reflect the passage of a month. + - Increase the total duration (`DurationTotal`) by one. + - Reset and update the subscription details. + - If no time is left (`DurationLeft` = 0): + - Subscription is expired. Refer to the next step. + +3. **Handle Special Cases for Subscriptions:** + + - If a future subscription is set: + - Activate the future subscription by updating current subscription details with those of the future subscription and restart the subscription. + - Else, if auto-renewal is enabled: + - Attempt to renew the subscription for another month. + - If renewal fails, remove the expired subscription. + - If there is no future subscription or auto-renewal, remove the expired subscription. + +### Subscription Upgrade + +A subscription can be upgraded to a more expensive plan. That also means, that if a consumer buys a plan, and then decides to downgrade their plan, the way to do it is either buy more months in the cheaper subscription, so the amount that the consumer will pay will be larger than the one +using the command the same command as shown above, with the only caveat - the `plan-index` must be different than the currently active subscription's plan, and, it must be with a higher price. + +The tokens will be taken out of the creator account immediately, and the new plan will take effect in the next epoch. +The old subscription will still be saved in the fixation store, for buffering purposes (the amount of blocks that the subscription will be saved for, is determined in the `BlocksToSave` function, under the [EpochStorage](https://github.com/lavanet/lava/blob/main/x/epochstorage/README.md) module). + +More ways to upgrade are by making an [Advance Purchase](#advance-purchase) or enabling [Auto Renewal](#auto-renewal). + +### Subscription Renewal + +Users have the option to renew their subscription either manually or automatically. To renew manually, users can utilize the same command as demonstrated above. They simply need to adjust the 'plan-index' to match the plan of their currently active subscription. Additionally, they can set the duration, allowing the new duration to be added to the remaining duration of the active subscription. +To renew automatically, please refer to [Auto Renewal](#auto-renewal). + +### Advance Purchase + +Users can purchase several months of subscription in advance, using the subscription buy transaction command like so: + +```bash +lavad tx subscription buy --advance-purchase [plan-index] [optional: consumer] [optional: duration(months)] [flags] +``` + +This will create the `FutureSubscription` object inside the `Subscription` object, as can be seen above. +The new subscription will be triggered once the current active subscription expires. + +Users can decide on the duration of this future subscription, and they'll pay the full amount for the entire period immediately. +If the plan changes during that period, users who bought this plan before it changed using the `--advance-purchase` flag, they won't be affected by that change. + +If a user tries to replace the future subscription with another, the new plan's price must be higher, considering the amount of days bought. +Meaning, if user originally bough X days of a plan with price A, and now wants to advance purchase Y days of a different plan with price B, than the following must be suffice: + +$$ +Y * B > X * A +$$ + +### Auto Renewal + +Users can decide to if they want the subscription to auto-renew itself every month, using the subscription buy transaction command: + +```bash +lavad tx subscription auto-renewal [true/false] [optional: plan-index] [optional: consumer] [flags] +``` + +Here, users can decide to renew their subscription to any plan index, wether it's cheaper or more expensive than the current active subscription's plan. + +The tokens will be taken from the user's account only when at the end of each month, prior to renewing the subscription, and for one month only. +If the user does not have sufficient funds for the renewal, the subscription will expire. + +I a user has a future subscription configured, and the auto renewal is set as well, then the auto renewal will take effect only when the future subscription will expire. + +## Parameters + +The subscription module does not contain parameters. + +## Queries + +The subscription module supports the following queries: + +| Query | Arguments | What it does | +| ---------------------- | --------------------- | -------------------------------------------------------------- | +| `current` | consumer (string) | Shows the current subscription of a consumer to a service plan | +| `list` | subscription (string) | Shows all current subscriptions | +| `list-projects` | none | Shows all the subscription's projects | +| `next-to-month-expiry` | none | Shows the subscriptions with the closest month expiry | +| `params` | none | Shows the parameters of the module | + +## Transactions + +All the transactions below require setting the `--from` flag and gas related flags. + +The subscription module supports the following transactions: + +| Transaction | Arguments | What it does | +| --------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | +| `add-project` | project-name (string) | Add a new project to a subscription | +| `auto-renewal ` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | +| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | +| `del-project` | project-name (string) | Delete a project from a subscription | + +Note that the `buy` transaction also support advance purchase and immediate upgrade. Refer to the help section of the commands for more details. + +## Proposals + +The subscription module does not support any proposals. From 1d348b47efe58e312b6cd9685a44bfda5e6e812f Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 11:08:41 -0500 Subject: [PATCH 10/52] CNS-783 From 38e840262b4707869f7dd4a1e9e28c932ff5490d Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 12:47:04 -0500 Subject: [PATCH 11/52] Small fix to timerstore query doc --- x/timerstore/client/cli/query_store_keys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/timerstore/client/cli/query_store_keys.go b/x/timerstore/client/cli/query_store_keys.go index 1d1f894c5b..c7482b6721 100644 --- a/x/timerstore/client/cli/query_store_keys.go +++ b/x/timerstore/client/cli/query_store_keys.go @@ -14,7 +14,7 @@ var _ = strconv.Itoa(0) func CmdStoreKeys() *cobra.Command { cmd := &cobra.Command{ Use: "store-keys", - Short: "Query for all timer store keys", + Short: "Query all timer store keys", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientQueryContext(cmd) From 719b7a652eb157ed36f0dac18cbf26e24c86bede Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 12:49:54 -0500 Subject: [PATCH 12/52] Add timerstore module README --- x/timerstore/README.md | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 x/timerstore/README.md diff --git a/x/timerstore/README.md b/x/timerstore/README.md new file mode 100644 index 0000000000..fe4d04376c --- /dev/null +++ b/x/timerstore/README.md @@ -0,0 +1,88 @@ +# `x/timerstore` + +## Abstract + +This document specifies the `timerstore` module of Lava Protocol. + +This module is not a standard module that the user can interact with, except for a few queries for debugging purposes. +The module is used as a powerful utility for other modules. + +The timerstore allows other modules to create timers, that triggers a callback functions. +A timer defined in this module can be of two types: BlockHeight or BlockTime. +The callback function can be triggered either at BeginBlock or EndBlock. + +## Contents + +- [Concepts](#concepts) + - [TimerStore](#timerstore) + - [BeginBlock & EndBlock](#beginblock--endblock) + - [BlockHeight & BlockTime](#blockheight--blocktime) + - [Trigger](#trigger) +- [Parameters](#parameters) +- [Queries](#queries) +- [Transactions](#transactions) +- [Proposals](#proposals) + +## Concepts + +### TimerStore + +The `TimerStore` object is the main entity of this module, and hold all of it's logic. +The `timerstore` module's keeper is pretty straight forward: + +```go +type Keeper struct { + timerStoresBegin []*timerstoretypes.TimerStore + timerStoresEnd []*timerstoretypes.TimerStore + cdc codec.BinaryCodec +} +``` + +Whenever a module creates a new timer, it will be stored in `timerStoresBegin` or in `timerStoresEnd`, depending on the function that was used to create the timer. + +### BeginBlock & EndBlock + +A module can decide wether to trigger a certain timer at the start of the block, or at the end of it. +The function `NewTimerStoreBeginBlock` will create a new `TimerStore` that will trigger at the `BeginBlock` when the time is right, and the function `NewTimerStoreEndBlock` will create a new `TimerStore` that will trigger at the `EndBlock` when the time is right. + +### BlockHeight & BlockTime + +After calling the function `NewTimerStoreBeginBlock` or `NewTimerStoreEndBlock` the calling procedure can use the returned `TimerStore` object to set the callback of the timer. The callback has to be of the signature `func(ctx sdk.Context, key, data []byte)`, more on that in the next section. + +To set the callback, the procedure needs to call `WithCallbackByBlockHeight` or `WithCallbackByBlockTime`. +The `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockHeight` kind, and the `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockTime` kind. + +The `BlockHeight` callbacks, can be triggered at a given block, and the `BlockTime` callback can be triggered at the first block that it's time is past the given time. + +### Trigger + +As stated in the previous section, timers can be one of two kinds: `BlockHeight` or `BlockTime`. +After a callback is created in the `TimerStore`, a procedure can create a new timer for it, depending on the kind. + +For `BlockHeight` callbacks, the function `AddTimerByBlockHeight(ctx sdk.Context, block uint64, key, data []byte)` will create a timer that will trigger at `block`, with the given `key` and `data`. + +For `BlockTimer` callbacks, the function `AddTimerByBlockTime(ctx sdk.Context, timestamp uint64, key, data []byte)` will create a timer that will trigger at the first block, which has a time that passed the given `timestamp`, with the given `key` and `data`. + +Once the timer is triggered, it is deleted. So for a recurring timer, a new timer is need to be created every time it's triggered. + +## Parameters + +The `timerstore` module does not contain parameters. + +## Queries + +The `timerstore` module supports the following queries: + +| Query | Arguments | What it does | +| ------------ | ----------------------------------- | ------------------------------------------------ | +| `all-timers` | store-key (string), prefix (string) | Shows all timers of a specific timer store | +| `next` | store-key (string), prefix (string) | Shows the next timeout of a specific timer store | +| `store-keys` | none | Shows all timer store keys | + +## Transactions + +The `timerstore` module does not support any transaction. + +## Proposals + +The `timerstore` module does not support any proposals. From fb5a99bad31f3b7a45587115379629772c291d80 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 12:56:42 -0500 Subject: [PATCH 13/52] Grammar fixes --- x/timerstore/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index fe4d04376c..e1aaaa6096 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -4,8 +4,7 @@ This document specifies the `timerstore` module of Lava Protocol. -This module is not a standard module that the user can interact with, except for a few queries for debugging purposes. -The module is used as a powerful utility for other modules. +This module, primarily a utility for other modules, is not a standard module with which users can interact, except for a few queries intended for debugging purposes. The module is used as a powerful utility for other modules. The timerstore allows other modules to create timers, that triggers a callback functions. A timer defined in this module can be of two types: BlockHeight or BlockTime. @@ -27,7 +26,7 @@ The callback function can be triggered either at BeginBlock or EndBlock. ### TimerStore -The `TimerStore` object is the main entity of this module, and hold all of it's logic. +The `TimerStore` object is the main entity of this module, and holds all of its logic. The `timerstore` module's keeper is pretty straight forward: ```go @@ -42,7 +41,7 @@ Whenever a module creates a new timer, it will be stored in `timerStoresBegin` o ### BeginBlock & EndBlock -A module can decide wether to trigger a certain timer at the start of the block, or at the end of it. +A module can decide whether to trigger a certain timer at the start of the block or at the end of it. The function `NewTimerStoreBeginBlock` will create a new `TimerStore` that will trigger at the `BeginBlock` when the time is right, and the function `NewTimerStoreEndBlock` will create a new `TimerStore` that will trigger at the `EndBlock` when the time is right. ### BlockHeight & BlockTime @@ -50,18 +49,18 @@ The function `NewTimerStoreBeginBlock` will create a new `TimerStore` that will After calling the function `NewTimerStoreBeginBlock` or `NewTimerStoreEndBlock` the calling procedure can use the returned `TimerStore` object to set the callback of the timer. The callback has to be of the signature `func(ctx sdk.Context, key, data []byte)`, more on that in the next section. To set the callback, the procedure needs to call `WithCallbackByBlockHeight` or `WithCallbackByBlockTime`. -The `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockHeight` kind, and the `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockTime` kind. +The `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockHeight` kind, and the `WithCallbackByBlockTime` function will add the callback to the `TimerStore` object, under the `BlockTime` kind. The `BlockHeight` callbacks, can be triggered at a given block, and the `BlockTime` callback can be triggered at the first block that it's time is past the given time. ### Trigger As stated in the previous section, timers can be one of two kinds: `BlockHeight` or `BlockTime`. -After a callback is created in the `TimerStore`, a procedure can create a new timer for it, depending on the kind. +Once a callback is created in the TimerStore, a procedure can then create a new timer corresponding to its kind. For `BlockHeight` callbacks, the function `AddTimerByBlockHeight(ctx sdk.Context, block uint64, key, data []byte)` will create a timer that will trigger at `block`, with the given `key` and `data`. -For `BlockTimer` callbacks, the function `AddTimerByBlockTime(ctx sdk.Context, timestamp uint64, key, data []byte)` will create a timer that will trigger at the first block, which has a time that passed the given `timestamp`, with the given `key` and `data`. +For `BlockTime` callbacks, the function `AddTimerByBlockTime(ctx sdk.Context, timestamp uint64, key, data []byte)` will create a timer that will trigger at the first block, which has a time that passed the given `timestamp`, with the given `key` and `data`. Once the timer is triggered, it is deleted. So for a recurring timer, a new timer is need to be created every time it's triggered. From f3cdd14eac7d0df20237e76e3869eabb0a2591ad Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Thu, 28 Dec 2023 13:07:41 -0500 Subject: [PATCH 14/52] Grammar fix --- x/subscription/README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index 1f158ff5c2..654455816c 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -5,9 +5,9 @@ This document specifies the subscription module of Lava Protocol. The subscription module is responsible for managing Lava's consumer subscription. -When a consumer wants to use Lava, they need to purchase a subscription. -The subscription is monthly - meaning - it starts in the next block after it was bought, and ends 1 month later. -If the user has bought more than 1 month, the subscription will reset at the end of each month until it expires. +To use Lava, consumers must purchase a subscription. +The subscription operates on a monthly basis, starting from the next block after purchase and ending one month later. +If a user purchases a subscription for more than one month, it will reset at the end of each month until its expiration. When a subscription is reset, the monthly left CUs is set to the plan's monthly CU. A subscription can be renewed, manually or automatically. @@ -39,7 +39,7 @@ To fully understand this module, it is highly recommended to read Plans and Proj ### Subscription In order for a consumer to purchase a subscription, they first must choose a plan to use. -Once a plan has been chosen, to perform the purchase, they can use the transaction command: +After choosing a plan, a consumer can perform the purchase using the transaction command: ```bash lavad tx subscription buy [plan-index] [optional: consumer] [optional: duration(months)] [flags] @@ -76,7 +76,7 @@ struct FutureSubscription { ``` When a consumer buys a subscription, an admin project is automatically generated for them. -When the creator and the consumer are not equal - meaning - someone is buying a subscription for somebody else, the tokens will be taken out of the creator's account. +When the creator and consumer are different individuals, indicating that someone is purchasing a subscription for another user, the tokens will be taken out of the creator's account. Subscriptions are saved in a fixation store (see [FixationStore](https://github.com/lavanet/lava/blob/main/x/fixationstore/README.md) module for reference). @@ -97,7 +97,7 @@ Here's a high-level flow of the function: 2. **Check Subscription Duration:** - If the subscription’s remaining duration (`DurationLeft`) is zero: - - This is a bug! Log this and extend by 1 month to for smoother operation and investigation. + - If this occurs, it's considered a bug. Log the issue and extend the subscription by one month for smoother operation and investigation. - If there is still time left (`DurationLeft` > 0): - reduce it by one to reflect the passage of a month. - Increase the total duration (`DurationTotal`) by one. @@ -116,10 +116,11 @@ Here's a high-level flow of the function: ### Subscription Upgrade -A subscription can be upgraded to a more expensive plan. That also means, that if a consumer buys a plan, and then decides to downgrade their plan, the way to do it is either buy more months in the cheaper subscription, so the amount that the consumer will pay will be larger than the one -using the command the same command as shown above, with the only caveat - the `plan-index` must be different than the currently active subscription's plan, and, it must be with a higher price. +A subscription can be upgraded to a more expensive plan. -The tokens will be taken out of the creator account immediately, and the new plan will take effect in the next epoch. +That also means, that if a consumer buys a plan, and then decides to downgrade their plan, the way to do it is either buy more months in the cheaper subscription, so the amount that the consumer will pay will be larger than the one using the same command as shown above, with the only caveat - the `plan-index` must be different than the currently active subscription's plan, and, it must be with a higher price. + +Tokens are deducted from the creator's account immediately, and the new plan becomes effective in the next epoch. The old subscription will still be saved in the fixation store, for buffering purposes (the amount of blocks that the subscription will be saved for, is determined in the `BlocksToSave` function, under the [EpochStorage](https://github.com/lavanet/lava/blob/main/x/epochstorage/README.md) module). More ways to upgrade are by making an [Advance Purchase](#advance-purchase) or enabling [Auto Renewal](#auto-renewal). @@ -163,7 +164,7 @@ Here, users can decide to renew their subscription to any plan index, wether it' The tokens will be taken from the user's account only when at the end of each month, prior to renewing the subscription, and for one month only. If the user does not have sufficient funds for the renewal, the subscription will expire. -I a user has a future subscription configured, and the auto renewal is set as well, then the auto renewal will take effect only when the future subscription will expire. +If a user has a future subscription configured and auto-renewal is also set, then auto-renewal will take effect only after the future subscription expires. ## Parameters @@ -187,12 +188,12 @@ All the transactions below require setting the `--from` flag and gas related fla The subscription module supports the following transactions: -| Transaction | Arguments | What it does | -| --------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | -| `add-project` | project-name (string) | Add a new project to a subscription | -| `auto-renewal ` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | -| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | -| `del-project` | project-name (string) | Delete a project from a subscription | +| Transaction | Arguments | What it does | +| -------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | +| `add-project` | project-name (string) | Add a new project to a subscription | +| `auto-renewal` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | +| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | +| `del-project` | project-name (string) | Delete a project from a subscription | Note that the `buy` transaction also support advance purchase and immediate upgrade. Refer to the help section of the commands for more details. From 44587f822d9424eff93226dde153f5f57b09ad2b Mon Sep 17 00:00:00 2001 From: oren-lava Date: Fri, 29 Dec 2023 12:07:01 +0200 Subject: [PATCH 15/52] CNS-770: payment README --- x/pairing/README.md | 402 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 x/pairing/README.md diff --git a/x/pairing/README.md b/x/pairing/README.md new file mode 100644 index 0000000000..1676e6c19a --- /dev/null +++ b/x/pairing/README.md @@ -0,0 +1,402 @@ +# `x/pairing` + +## Abstract + +This document specifies the pairing module of Lava Protocol. + +The pairing module is responsible for handling providers staking, providers freezing, calulating the providers pairing list for consumers, punishing unresponsive providers and handling providers relay payment requests. + +The Pairing Engine is a sophisticated algorithmic mechanism designed to match consumers with the most appropriate service providers, considering a wide array of inputs and constraints. It upholds the network's principles of fairness, security, and quality of service, while ensuring each consumer's experience is personalized and in line with their specific requirements. + +The pairing module is one of Lava's core modules and is closely connected to the subscription, dualstaking and epochstorage modules. To fully understand they all work together, please also refer to their respective READMEs. + +## Contents + +* [Concepts](#concepts) + * [Providers](#providers) + * [Stake](#stake) + * [Unstake](#unstake) + * [Freeze](#freeze) + * [Pairing](#pairing) + * [Filters](#filters) + * [Scores](#scores) + * [Quality Of Service](#quality-of-service) + * [Pairing Verification](#pairing-verification) + * [Unresponsiveness](#unresponsiveness) + * [Static Providers](#static-providers) + * [Payments](#payments) + * [CU Tracking](#cu-tracking) + * [Providers Payment](#providers-payment) + * [Storage](#storage) +* [Parameters](#parameters) + * [QoSWeight](#qosweight) + * [EpochBlocksOverlap](#epochblocksoverlap) + * [FraudSlashingAmount](#fraudslashingamount) + * [FraudStakeSlashingFactor](#fraudstakeslashingfactor) + * [RecommendedEpochNumToCollectPayment](#recommendedepochnumtocollectpayment) +* [Queries](#queries) +* [Transactions](#transactions) +* [Proposals](#proposals) +* [Events](#events) + +## Concepts + +### Providers + +Providers are entities that have access to blockchains and want to monetize that access by providing services to consumers. Providers stake tokens under a geolocation and supported specifications (e.g. Ethereum JSON-RPC in U.S. East), once active they must provide service to consumers. Providers run the lava process and the desired blockchain or service (e.g. Ethereum JSON-RPC) they are providing access for. + +#### Stake + +When a provider stakes, a new stake entry is created on-chain. A stake entry is defined as follows: + +```go +type StakeEntry struct { + Stake Coin // stake amount + Address string // provider Lava address + StakeAppliedBlock uint64 // the block in which the stake is active + Endpoints []Endpoint // a list of endpoints + Geolocation int32 // supported geolocation + Chain string // chain in which the provider is staked on + Moniker string // non-unique human name + DelegateTotal Coin // total delegations + DelegateLimit Coin // max amount of delegations the provider accepts + DelegateCommission uint64 // commission for delegation +} +``` + +Note, the `Coin` type is from Cosmos-SDK (`cosmos.base.v1beta1.Coin`). A provider can accept delegations to increase its effective stake, which increases its chances of being selected in the pairing process. The provider can also set a delegation limit, which determines the maximum value of delegations they can accept. This limit is in place to prevent delegators from increasing the provider's effective stake to a level where the provider is overwhelmed with more consumers than they can handle in the pairing process. For more details about delegations, refer to the dualstaking module README. + +An provider's endpoint is defined as follows: + +```go +type Endpoint struct { + IPPORT string // IP + port + Geolocation int32 // supported geolocation + Addons []string // list of supported add-ons + ApiInterfaces []string // list of supported API interfaces + Extensions []string // list of supported extensions +} +``` + +A consumer sends requests to a provider's endpoint to communicate with them. The aggregated geolocations of all the provider's endpoints should be equal to the geolocation field present in the provider's stake entry. Other than that, the `Addons`, `ApiInterfaces` and `Extensions` specify the "extra" features the provider supports. To get more details about those, see the spec module README. + +Stake entries' storage is managed by the epochstorage module. For more details, see its README. + +#### Unstake + +A provider can unstake and retrieve their coins. When a provider unstakes, they are removed from the pairing list starting from the next epoch. After a specified number of blocks called `UnstakeHoldBlocks` (a parameter of the epochstorage module), the provider is eligible to receive their coins back. + +#### Freeze + +Freeze Mode enables the Provider to temporarily suspend their node's operation during maintenance to avoid bad Quality of Service (QoS). Freeze/Unfreeze is applied on the next Epoch. The Provider can initiate multiple Freeze actions, but only one Freeze-per-chain can be take effect at a time. + +### Pairing + +The Pairing Engine is a core component of the Lava Network, responsible for connecting consumers with the most suitable service providers. It operates on a complex array of inputs, including the strictest policies defined at the plan, subscription, and project levels. These policies set the boundaries for service provisioning, ensuring that consumers' specific requirements are met while adhering to the network's overarching rules. + +The Pairing Engine takes into account a diverse range of parameters to make informed matchmaking decisions. This includes a list of available providers, each with their unique service offerings, preferred geolocation, available addons and extensions, and additional factors. Importantly, this pairing mechanism is deterministic, and recalculates the pairing list every epoch. + +Furthermore, the Pairing Engine has the ability to filter out irrelevant providers, such as those that may be frozen or jailed due to non-compliance with network rules. Additionally, it factors in the stake held by each provider, giving those with higher stakes a greater chance of being selected. + +The selection process is also enriched by pseudorandomization, enhancing the security and unpredictability of the pairings. A changing seed, derived from the Lava blockchain hashes, is introduced to the selection algorithm. This seed is unique per consumer, achieved by incorporating the consumer's address as a "salt," which ensures that each consumer's pairings are distinct and less susceptible to prediction or manipulation. + +Finally, providers are assigned scores based on QoS metrics, including latency, availability, and synchronization. This scoring system ensures that the Pairing Engine selects providers that best match the specific QoS requirements of the consumer's cluster. Providers with superior QoS scores receive preference in the pairing process. + +#### Filters + +When calculating the pairing list of providers for a consumer, filters are applied to exclude unwanted providers. These providers may be frozen, jailed, not supporting the required geolocation, and more. All filters are initialized according to the effective (strictest) policy of the consumer's project. + +The current pairing filters are: + +| Filter | What it does | +| ---------- | ----------------------------------------------| +| `Freeze` | excludes frozen providers | +| `Add-on` | excludes providers not supporting required add-ons | +| `Geolocation` | excludes providers not supporting required geolocations | +| `Selected providers` | excludes providers that are not in the policy's selected providers allow-list | + +Some filters support a "mix" behavior, where the filter does not exclude all providers, but instead creates a mix of providers that pass the filter and providers that do not pass the filter. + +All filters are implementing the following interface: + +```go +type Filter interface { + Filter(ctx sdk.Context, providers []epochstoragetypes.StakeEntry, currentEpoch uint64) []bool + InitFilter(strictestPolicy planstypes.Policy) bool // return if filter is usable (by the policy) + IsMix() bool +} +``` + +#### Scores + +The scoring mechanism is used to select providers during the pairing process. This mechanism is applied after the full list of providers has been filtered using the pairing filters. + +The pairing scoring process involves the following steps: + 1. Collect pairing requirements and strategy from the policy. + 2. Generate pairing slots with requirements (one slot per provider). + 3. Compute the pairing score of each provider with respect to each slot. + 4. Pick a provider for each slot with a pseudo-random weighted choice. + +Pairing requirements describe the policy-imposed requirements for paired providers. Those include geolocation constraints, stake amount, and expectations regarding QoS ranking of selected providers. Pairing requirements must satisfy the `ScoreReq` interface: + +```go +type ScoreReq interface { + Init(policy planstypes.Policy) bool + Score(score PairingScore) math.Uint // calculates the score of a provider for a specific score requirement + GetName() string + Equal(other ScoreReq) bool + GetReqForSlot(policy planstypes.Policy, slotIdx int) ScoreReq // returns the score requirement for a slot given policy limitations +} +``` + +A pairing slot represents a single provider slot in the pairing list (The number of slots for pairing is defined by the policy). Each pairing slot holds a set of pairing requirements (a pairing slot may repeat). For example, a policy may state that the pairing list has 6 slots, and providers should be located in Asia and Europe. This can be satisfied with a pairing list that has 3 (identical) slots that require providers in Asia and 3 (identical) slots that require providers in Europe. A pairing slot is defined as follows: + +```go +type PairingSlot struct { + Reqs map[string]ScoreReq // example: "geoReq": geo Score req object with EU requirement + Index int +} +``` + +The `Reqs` map holds pairing requirements names that point to ScoreReq object. Continuing the example from above, slot A's map can contain: `"geoReq": GeoReq{geo: "EU"}` and slot B's map can contain: `"geoReq": GeoReq{geo: "AS"}` (the map is filled according to `GetReqForSlot()`'s output). + +A pairing score describes the suitability of a provider for a pairing slot (under a given strategy). The score depends on the slot's requirements: for example, given a slot which requires geolocation in Asia, a provider in Asia will generally get higher score than one in Europe. The score is calculated for each combination. + +A pairing score is defined as follows: + +```go +type PairingScore struct { + Provider *StakeEntry + Score math.Uint // total score according to all the pairing requirements + ScoreComponents map[string]math.Uint // score components by pairing requirements + SkipForSelection bool + SlotFiltering map[int]struct{} // slot indexes here are skipped + QosExcellenceReport pairingtypes.QualityOfServiceReport +} +``` + +Finally, we calculate the score of each provider for a specific slot and select a provider using a pseudo-random weighted choice. It should be noted that a provider that best meets the policy requirements will have a higher score and therefore a greater chance of being selected. Additionally, a provider's stake is a factor in the scoring process. Thus, a provider with a large stake will also have a higher chance of being chosen. Consequently, providers with a significant stake are more likely to be selected. However, the score mechanism also encourages providers to excel in other metrics in order to attract more consumers for pairing. + +#### Quality Of Service + +##### Excellence QoS + +The Lava Network places a strong emphasis on delivering exceptional Quality of Service (QoS) to its consumers. To ensure this, consumers actively participate in monitoring and customizing their QoS metrics. They gauge provider performance by measuring latency in provider responses relative to a benchmark, assessing data freshness in comparison to the fastest provider, and evaluating the percentage of error or timeout responses in the availability metric. These scores are diligently recorded and sent on-chain alongside the relay proofs of service, creating a transparent and accountable system. + +To further enhance the integrity of the QoS scores, updates are aggregated across all consumers in a manner that safeguards against false reports. Negative reports are weighted by usage, meaning that a consumer must actively use and pay a provider to diminish their QoS score. This mechanism discourages users from artificially lowering a provider's score. + +These QoS excellence metrics only affect pairings and are aggregated over time with a decay function that favors the latest data, meaning providers can improve, and those providers that their service fails will be impacted to affect fewer users. This approach ensures that the QoS system remains dynamic and responsive, benefiting providers striving to enhance their services while minimizing the impact of service failures on a broader scale. + +##### QoS + +In the Lava Network, alongside the comprehensive Quality of Service of Excellence metrics, there exists an additional metric known as Passable QoS. Unlike Excellence QoS, which offers a broad range of values, Passable QoS operates on a binary scale, either assigning a value of 0 or 1, averaged over relays. This metric simplifies the evaluation of service quality to a binary determination, indicating whether a relay meets the Passable QoS threshold, meaning it provides a level of service deemed acceptable for use. + +The Passable QoS score directly influences the total payout for a specific payment; however, it's important to note that only 50% of the payout is exposed to this metric (can be changed via governance). This allocation ensures a balance between incentivizing excellent service and discouraging poor performance. + +#### Pairing Verification + +The calculation of the pairing list is deterministic and depends on the current epoch. The pseudo-random factor only applies when pairing a specific provider with a consumer. Therefore, the pairing list can be recalculated using the consumer's address, block, and chain ID. + +Pairing verification is used by the provider to determine whether to offer service for a consumer request. If the provider decides to serve a consumer that fails pairing verification, it will not receive payment for the service it provided. + +#### Unresponsiveness + +Providers can get punished for being unresponsive to consumer requests. If a provider wishes to stop getting paired with consumers for any reason to avoid getting punished, it can freeze itself. Currently, the punishment for being unresponsive is freezing. In the future, providers will be jailed for this kind of behaviour. + +When a consumer is getting paired with a provider, it sends requests for service. If provider A is unresponsive after a few tries, the consumer switches to another provider from its pairing list, provider B, and send requests to it. When communicatting with provider B, the consumer appends the address of provider A to its request, thus adding the current request's CU to provider A's "complainers CU" counter. + +Every epoch start, the amount of complainers CU is compared with the amount of serviced CU of each provider across a few epochs back. If the complainers CU is higher, the provider is considered unresponsive and gets punished. The number of epochs back is determined by the recommendedEpochNumToCollectPayment parameter + +#### Static Providers + +Static providers are Lava chain providers that offer services to any consumer without relying on pairing. This feature allows new consumers to communicate with the Lava chain without a centralized provider. For example, when a new consumer wants to start using Lava, it needs to obtain its pairing list from a Lava node. However, since it initially does not have a list of providers to communicate with, it can use the static providers list to obtain its initial pairing list. + +To clarify, a consumer cannot randomly find a Lava provider's endpoint and receive service from it because a provider will only serve consumers whose pairing with it can be verified. Therefore, the consumer must obtain the providers' pairing list from a Lava node. + +Note that unstaking static providers will have their coins returned after a longer period of time compared to regular ("dynamic") providers due to their special status. + +### Payments + +The Lava network connects blockchain data providers with consumers who need access to that data. Once a pairing is established, a consumer can send a message to the provider to request a specific blockchain query. The provider then sends a relay request to the blockchain, retrieves the required information, and forwards it to the consumer. After completing the requested service, the provider sends a relay payment transaction to the Lava network to receive payment. + +Here is the workflow for Lava's payment mechanism: + +1. The provider sends a relay payment transaction, which is signed by the consumer. +2. The reported CU (compute units) from the relay payment transaction is tracked. +3. On a monthly basis, all the providers' tracked CU are reviewed, and payments are sent for their accumulated services. + +#### CU Tracking + +The goal of CU tracking is to keep records of serviced CUs by the providers, in order to determine the amount of payment they should receive at the end of the month. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each month, the CU tracker is reset for all providers. + +#### Providers Payment + +Providers receive payments once a month. These payments are funded by the funds that consumers paid for their subscriptions. The amount of coins to be sent to each provider depends on the number of tracked CUs they had over the previous month. The payment is calculated using the following formula: + +```math +Payment = subscription\_price * \frac{provider\_tracked\_CU}{total\_CU\_used\_by\_subscription} +``` + +Explaining the formula, the provider receives a portion of the subscription price. This portion is calculated by dividing the amount of CU that the provider serviced for the consumer by the total amount of CU that the consumer used, which may involve multiple providers. It is important to note that the total CU and tracked CU are counted separately for each chain, such as ETH and STRK. + +The rewards for providers can increase if the consumer exceeds the allotted CU for their subscription. For more detailed information, please refer to the README of the subscription module. + +Additionally, it should be noted that the providers' rewards are influenced by factors such as adjustment factor, community/validators' contributions and bonus rewards. For further information, please consult the README of the rewards module. + +#### Storage + +Successful provider payments are stored on-chain. This is done to avoid double spend attacks (in which a provider tries to get paid twice for the same relay) and to track CU for the current epoch (and limit it by the policy restrictions). + +Payments are stored using the following objects: + +```go +// holds all the payments of a specific epoch +type EpochPayments struct { + Index string + ProviderPaymentStorageKeys []string +} +``` + +```go +// holds all the payments of a specific epoch for a specific provider +type ProviderPaymentStorage struct { + Index string + Epoch uint64 + UniquePaymentStorageClientProviderKeys []string // list of payments + ComplainersTotalCu uint64 // amount of complainers CU for unresponsiveness +} +``` + +```go +// holds a payment for a specific provider in a specific epoch +type UniquePaymentStorageClientProvider struct { + Index string + Block uint64 + UsedCU uint64 +} +``` + +## Parameters + +The pairing module contains the following parameters: + +| Key | Type | Default Value | +| -------------------------------------- | ----------------------- | -----------------| +| QoSWeight | math.LegacyDec | 0.5 | +| EpochBlocksOverlap | uint64 | 5 | +| FraudSlashingAmount | uint64 | 0 | +| FraudStakeSlashingFactor | math.LegacyDec | 0 | +| RecommendedEpochNumToCollectPayment | uint64 | 3 | + +### QoSWeight + +QoSWeight determines the weight of passable QoS score in the provider payment calculation. For example, say that a provider has a lower QoS than the passable QoS, the QoS weight determines how much of the provider's payment will be deducted from not passing the minimum QoS (QoS weight = 40% --> 40% of the provider's payment is deducted). + +### EpochBlocksOverlap + +EpochBlocksOverlap is the number of blocks a consumer waits before interacting with a provider from a new pairing list to let providers that are behind the latest block to catch up with the chain. + +### FraudSlashingAmount + +FraudSlashingAmount is for future features. + +### FraudStakeSlashingFactor + +FraudStakeSlashingFactor is for future features. + +### RecommendedEpochNumToCollectPayment + +RecommendedEpochNumToCollectPayment is the recommended max number of epochs for providers to claim payments. It's also used for determining unresponsiveness. + +## Queries + +The pairing module supports the following queries: + +| Query | Arguments | What it does | +| ---------- | --------------- | ----------------------------------------------| +| `account-info` | address (string) | detailed summary of a Lava account | +| `effective-policy` | chain-id (string), project developer/index (string) | shows a project's effective policy | +| `get-pairing` | chain-id (string), consumer (string) | shows a consumer's providers pairing list | +| `list-epoch-payments` | none | show all epochPayment objects | +| `list-provider-payment-storage` | none | show all providerPaymentStorage objects | +| `list-unique-payment-storage-client-provider` | none | show all uniquePaymentStorageClientProvider objects | +| `provider-monthly-payout` | provider (string) | show the current monthly payout for a specific provider | +| `providers` | chain-id (string) | show all the providers staked on a specific chain | +| `sdk-pairing` | none | query used by Lava-SDK to get all the required pairing info | +| `show-epoch-payments` | index (string) | show an epochPayment object by index | +| `show-provider-payment-storage` | index (string) | show a providerPaymentStorage object by index | +| `show-unique-payment-storage-client-provider` | index (string) | show an uniquePaymentStorageClientProvider object by index | +| `static-providers-list` | chain-id (string) | show the list of static providers for a specific chain | +| `subscription-monthly-payout` | consumer (string) | show the current monthly payout for a specific consumer | +| `user-entry` | consumer (string), chain-id (string), block (uint64) | show the remaining allowed CU for the current epoch for a consumer | +| `verify-pairing` | chain-id (string), consumer (string), provider (string), block (uint64) | verify the provider was in the consumer's pairing list on a specific block | +| `params` | none | shows the module's parameters | + +For more details on payment storage objects (`epochPayment`, `providerPaymentStorage`, `uniquePaymentStorageClientProvider`), see the `EpochStorage` module's README. + +## Transactions + +All the transactions below require setting the `--from` flag and gas related flags (other required flags of specific commands will be shown in the TX arguments). Also, state changes from these transactions are applied at different times, depending on the transaction. Some changes take effect immediately, while others may have a delay before they are applied. + +The pairing module supports the following transactions: + +| Transaction | Arguments | What it does | +| ---------- | --------------- | ----------------------------------------------| +| `bulk-stake-provider` | chain-ids ([]string), amount (Coin), endpoints ([]Endpoint), geolocation (int32), {repeat args for another bulk}, validator (string, optional), --provider-moniker (string) | stake provider in multiple chains with multiple endpoints with one command | +| `freeze` | chain-ids ([]string) | freeze a provider in multiple chains | +| `modify-provider` | chain-id (string) | modify a provider's stake entry (use the TX optional flags) | +| `relay-payment` | chain-id (string) | automatically generated TX used by a provider to request payment for their service | +| `simulate-relay-payment` | consumer-key (string), chainId (string) | simulate a relay payment TX | +| `stake-provider` | chain-id (string), amount (Coin), endpoints ([]Endpoint), geolocation (int32), validator (string, optional), --provider-moniker (string) | stake a provider in a chain with multiple endpoints | +| `unfreeze` | chain-ids ([]string) | unfreeze a provider in multiple chains | +| `unstake-provider` | chain-ids ([]string), validator (string, optional) | unstake a provider from multiple chains | + +Note, the `Coin` type is from Cosmos-SDK (`cosmos.base.v1beta1.Coin`). From the CLI, use `100ulava` to assign a `Coin` argument. The `Endpoint` type defines a provider endpoint. From the CLI, use "my-provider-grpc-addr.com:9090,1" for one endpoint (includes the endpoint's URL+port and the endpoint's geolocation). When it comes to staking-related transactions, the geolocation argument should encompass the geolocations of all the endpoints combined. + +## Proposals + +The pairing module supports the provider unstake proposal, which is designed to address malicious providers and compel them to unstake from the chain. + +To send the proposal, use the following commands: + +``` +lavad tx gov submit-legacy-proposal unstake , --from alice +lavad tx gov vote yes --from alice +``` + +A valid `unstake` JSON proposal format: + +```json +{ + "proposal": { + "title": "Unstake proposal", + "description": "A proposal for unstaking providers", + "providers_info": [ + { + "provider": "lava@1q0q74n8hgme7hrmhrvjgfq2dzhchjulqr526y7", + "chain_id": "*" + }, + { + "provider": "lava@1rx5g4j65ztzu29pv5wrn4s2rcjkhkhcxztplv9", + "chain_id": "ETH1" + } + ] + }, + "deposit": "10000000ulava" +} +``` + +## Events + +The pairing module has the following events: + +| Event | When it happens | +| ---------- | --------------- | +| `stake_new_provider` | a successful provider stake | +| `stake_update_provider` | a successful provider stake entry modification | +| `provider_unstake_commit` | a successful provider unstake (before receiving the funds back) | +| `relay_payment` | a successful relay payment | +| `provider_reported` | a successful provider report for unresponsiveness | +| `provider_latest_block_report` | a successful report of latest block of a provider | +| `rejected_cu` | a successful relay payment that rejected some of the relays | +| `unstake_gov_proposal` | a successful unstake gov proposal | \ No newline at end of file From 6745135627f30d9d165507fdfd241ac399aa4596 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 12:21:37 +0200 Subject: [PATCH 16/52] CNS-770: fix rewards unit test --- x/rewards/keeper/pool_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x/rewards/keeper/pool_test.go b/x/rewards/keeper/pool_test.go index 0c86d8a077..5cc17ac407 100644 --- a/x/rewards/keeper/pool_test.go +++ b/x/rewards/keeper/pool_test.go @@ -374,8 +374,9 @@ func TestRefillPoolsTimerStore(t *testing.T) { require.Equal(t, expectedMonthsLeft, monthsLeft) ts.AdvanceMonths(1) - month = ts.GetNextMonth(ts.BlockTime()) - ts.BlockTime().UTC().Unix() ts.AdvanceBlock() - testkeeper.EndBlock(ts.Ctx, ts.Keepers) + // testkeeper.EndBlock(ts.Ctx, ts.Keepers) + defaultBlockTime := ts.Keepers.Downtime.GetParams(ts.Ctx).DowntimeDuration.Seconds() + month = ts.GetNextMonth(ts.BlockTime()) - ts.BlockTime().UTC().Unix() - int64(defaultBlockTime) } } From 6de4aabf932a0ccf06e72626912782c8de252add Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 14:14:57 +0200 Subject: [PATCH 17/52] CNS-770: PR changes --- x/pairing/README.md | 6 +++--- x/pairing/keeper/msg_server_relay_payment.go | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x/pairing/README.md b/x/pairing/README.md index 1676e6c19a..4bd073479a 100644 --- a/x/pairing/README.md +++ b/x/pairing/README.md @@ -88,7 +88,7 @@ A provider can unstake and retrieve their coins. When a provider unstakes, they #### Freeze -Freeze Mode enables the Provider to temporarily suspend their node's operation during maintenance to avoid bad Quality of Service (QoS). Freeze/Unfreeze is applied on the next Epoch. The Provider can initiate multiple Freeze actions, but only one Freeze-per-chain can be take effect at a time. +Freeze Mode enables the Provider to temporarily suspend their node's operation during maintenance to avoid bad Quality of Service (QoS). Freeze/Unfreeze is applied on the next Epoch. The Provider can initiate multiple Freeze actions with one command. ### Pairing @@ -223,11 +223,11 @@ Here is the workflow for Lava's payment mechanism: 1. The provider sends a relay payment transaction, which is signed by the consumer. 2. The reported CU (compute units) from the relay payment transaction is tracked. -3. On a monthly basis, all the providers' tracked CU are reviewed, and payments are sent for their accumulated services. +3. Upon a subscription's month expiry, all the providers' tracked CU are reviewed, and payments are sent for their accumulated services. #### CU Tracking -The goal of CU tracking is to keep records of serviced CUs by the providers, in order to determine the amount of payment they should receive at the end of the month. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each month, the CU tracker is reset for all providers. +The goal of CU tracking is to keep records of serviced CUs by the providers to a specific subscription, in order to determine the amount of payment they should receive at the month expiry of said subscription. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each month, the CU tracker is reset for all providers. #### Providers Payment diff --git a/x/pairing/keeper/msg_server_relay_payment.go b/x/pairing/keeper/msg_server_relay_payment.go index 4e800f313f..0dcdeedc15 100644 --- a/x/pairing/keeper/msg_server_relay_payment.go +++ b/x/pairing/keeper/msg_server_relay_payment.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "time" "github.com/cometbft/cometbft/libs/log" @@ -235,12 +236,13 @@ func (k msgServer) RelayPayment(goCtx context.Context, msg *types.MsgRelayPaymen // update provider payment storage with complainer's CU err = k.updateProviderPaymentStorageWithComplainerCU(ctx, relay.UnresponsiveProviders, logger, epochStart, relay.SpecId, relay.CuSum, servicersToPair, project.Index) if err != nil { - reportedProviders := "" + var reportedProviders []string for _, p := range relay.UnresponsiveProviders { - reportedProviders += p.String() + ", " + reportedProviders = append(reportedProviders, p.String()) } + reportedProvidersStr := strings.Join(reportedProviders, ",") utils.LavaFormatError("failed to update complainers CU for providers", err, - utils.Attribute{Key: "reported_providers", Value: reportedProviders}, + utils.Attribute{Key: "reported_providers", Value: reportedProvidersStr}, utils.Attribute{Key: "epoch", Value: strconv.FormatUint(epochStart, 10)}, utils.Attribute{Key: "chain_id", Value: relay.SpecId}, utils.Attribute{Key: "cu", Value: strconv.FormatUint(relay.CuSum, 10)}, From c3bb4e81bf75e7a4d876bfc3f00adca7211e24f4 Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Sun, 31 Dec 2023 12:17:44 +0000 Subject: [PATCH 18/52] fix downtime query flags --- x/downtime/client/cli/query.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/x/downtime/client/cli/query.go b/x/downtime/client/cli/query.go index 40f9b30a0f..fab5fd1727 100644 --- a/x/downtime/client/cli/query.go +++ b/x/downtime/client/cli/query.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/lavanet/lava/x/downtime/types" v1 "github.com/lavanet/lava/x/downtime/v1" "github.com/spf13/cobra" @@ -24,7 +25,7 @@ func NewQueryCmd() *cobra.Command { } func CmdQueryParams() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "params", Short: "Query downtime module params", RunE: func(cmd *cobra.Command, args []string) error { @@ -40,10 +41,13 @@ func CmdQueryParams() *cobra.Command { return clientCtx.PrintProto(resp) }, } + + flags.AddQueryFlagsToCmd(cmd) + return cmd } func CmdQueryDowntime() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "downtime [epoch_start_block]", Short: "Query downtime", Long: "Query downtime between blocks, if only start is provided then will query for downtime at the given block, if end is provided then it will query the full range", @@ -67,4 +71,7 @@ func CmdQueryDowntime() *cobra.Command { return clientCtx.PrintProto(resp) }, } + + flags.AddQueryFlagsToCmd(cmd) + return cmd } From 5d277900b55147245d0c6da83b214ef6e68246af Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 07:40:54 -0500 Subject: [PATCH 19/52] CR Fix: buy command --- x/subscription/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index 654455816c..4444444736 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -39,7 +39,7 @@ To fully understand this module, it is highly recommended to read Plans and Proj ### Subscription In order for a consumer to purchase a subscription, they first must choose a plan to use. -After choosing a plan, a consumer can perform the purchase using the transaction command: +After choosing a plan, a consumer can perform the purchase using the buy transaction command: ```bash lavad tx subscription buy [plan-index] [optional: consumer] [optional: duration(months)] [flags] From 041db8260c9355cad4ad2cad9309a5f9e96d1d1a Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 07:41:48 -0500 Subject: [PATCH 20/52] CR Fix: link to plans readme --- x/subscription/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index 4444444736..efc6259544 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -15,7 +15,7 @@ Additionally, a subscription plan can be bought in advance for months ahead. There are 2 other concepts that the subscription is connected to: -- [Plans](https://github.com/lavanet/lava/blob/main/x/pairing/README.md) +- [Plans](https://github.com/lavanet/lava/blob/main/x/plans/README.md) - [Projects](https://github.com/lavanet/lava/blob/main/x/projects/README.md) To fully understand this module, it is highly recommended to read Plans and Projects READMEs as well. From a7aac97a5d47ae53d57d6f06e37c0afe4b2e17bf Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 07:42:29 -0500 Subject: [PATCH 21/52] CR Fix: More info on the cu tracker --- x/subscription/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index efc6259544..bf621a53f4 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -92,7 +92,7 @@ Here's a high-level flow of the function: 1. **Update the CU Tracker Timer:** - - Add a CU tracker timer for the subscription. + - Add a CU tracker timer for the subscription. (More information about the CU tracker can be found in the [pairing module](https://github.com/lavanet/lava/blob/main/x/pairing/README.md)) 2. **Check Subscription Duration:** From df4e95e548a27ddbc43d5823e858c949e7b9d35d Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 07:48:05 -0500 Subject: [PATCH 22/52] CR Fix: TMI --- x/subscription/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index bf621a53f4..693b0a2e2f 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -121,7 +121,6 @@ A subscription can be upgraded to a more expensive plan. That also means, that if a consumer buys a plan, and then decides to downgrade their plan, the way to do it is either buy more months in the cheaper subscription, so the amount that the consumer will pay will be larger than the one using the same command as shown above, with the only caveat - the `plan-index` must be different than the currently active subscription's plan, and, it must be with a higher price. Tokens are deducted from the creator's account immediately, and the new plan becomes effective in the next epoch. -The old subscription will still be saved in the fixation store, for buffering purposes (the amount of blocks that the subscription will be saved for, is determined in the `BlocksToSave` function, under the [EpochStorage](https://github.com/lavanet/lava/blob/main/x/epochstorage/README.md) module). More ways to upgrade are by making an [Advance Purchase](#advance-purchase) or enabling [Auto Renewal](#auto-renewal). From 7ef2ecf36129e8175e2ff9e0dd04eef924e6d1f2 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 07:56:13 -0500 Subject: [PATCH 23/52] CR Fix: Combine sections --- x/subscription/README.md | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index 693b0a2e2f..1d0e60fe6a 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -28,7 +28,6 @@ To fully understand this module, it is highly recommended to read Plans and Proj - [Subscription Upgrade](#subscription-upgrade) - [Subscription Renewal](#subscription-renewal) - [Advance Purchase](#advance-purchase) - - [Auto Renewal](#auto-renewal) - [Parameters](#parameters) - [Queries](#queries) - [Transactions](#transactions) @@ -39,7 +38,7 @@ To fully understand this module, it is highly recommended to read Plans and Proj ### Subscription In order for a consumer to purchase a subscription, they first must choose a plan to use. -After choosing a plan, a consumer can perform the purchase using the buy transaction command: +After choosing a plan, a consumer can perform the purchase using the `buy` transaction command: ```bash lavad tx subscription buy [plan-index] [optional: consumer] [optional: duration(months)] [flags] @@ -127,11 +126,22 @@ More ways to upgrade are by making an [Advance Purchase](#advance-purchase) or e ### Subscription Renewal Users have the option to renew their subscription either manually or automatically. To renew manually, users can utilize the same command as demonstrated above. They simply need to adjust the 'plan-index' to match the plan of their currently active subscription. Additionally, they can set the duration, allowing the new duration to be added to the remaining duration of the active subscription. -To renew automatically, please refer to [Auto Renewal](#auto-renewal). + +To renew automatically, users can use the subscription `auto-renewal` transaction command: + +```bash +lavad tx subscription auto-renewal [true/false] [optional: plan-index] [optional: consumer] [flags] +``` + +With auto renewal, users have the flexibility to renew their subscription to any plan, regardless of whether it's cheaper or more expensive than the current active plan. The necessary tokens for renewal are deducted from the user's account at the end of each month, covering the renewal for just one month at a time. If sufficient funds are not available, the subscription will expire. + +To disable auto-renewal, users can simply set the `auto-renewal` command to `false`. This will stop the subscription from renewing automatically at the end of its current cycle. + +Furthermore, if a user has configured a future subscription and also set auto renewal, the auto renewal will only take effect after the future subscription expires. ### Advance Purchase -Users can purchase several months of subscription in advance, using the subscription buy transaction command like so: +Users can purchase several months of subscription in advance, using the subscription `buy` transaction command like so: ```bash lavad tx subscription buy --advance-purchase [plan-index] [optional: consumer] [optional: duration(months)] [flags] @@ -150,21 +160,6 @@ $$ Y * B > X * A $$ -### Auto Renewal - -Users can decide to if they want the subscription to auto-renew itself every month, using the subscription buy transaction command: - -```bash -lavad tx subscription auto-renewal [true/false] [optional: plan-index] [optional: consumer] [flags] -``` - -Here, users can decide to renew their subscription to any plan index, wether it's cheaper or more expensive than the current active subscription's plan. - -The tokens will be taken from the user's account only when at the end of each month, prior to renewing the subscription, and for one month only. -If the user does not have sufficient funds for the renewal, the subscription will expire. - -If a user has a future subscription configured and auto-renewal is also set, then auto-renewal will take effect only after the future subscription expires. - ## Parameters The subscription module does not contain parameters. From 0a5a794e7ae01ee76829d072320e9783a435024d Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 08:00:18 -0500 Subject: [PATCH 24/52] CR Fix: typo --- x/subscription/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index 1d0e60fe6a..daf67bc6f9 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -154,7 +154,7 @@ Users can decide on the duration of this future subscription, and they'll pay th If the plan changes during that period, users who bought this plan before it changed using the `--advance-purchase` flag, they won't be affected by that change. If a user tries to replace the future subscription with another, the new plan's price must be higher, considering the amount of days bought. -Meaning, if user originally bough X days of a plan with price A, and now wants to advance purchase Y days of a different plan with price B, than the following must be suffice: +Meaning, if user originally bought X days of a plan with price A, and now wants to advance purchase Y days of a different plan with price B, than the following must be suffice: $$ Y * B > X * A From c26d2c9b0f58a4e7d315762a912e39e616bee8e1 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 08:18:18 -0500 Subject: [PATCH 25/52] CR Fix: Add "Effective in" to the transactions table --- x/subscription/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index daf67bc6f9..2805362f7f 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -182,12 +182,12 @@ All the transactions below require setting the `--from` flag and gas related fla The subscription module supports the following transactions: -| Transaction | Arguments | What it does | -| -------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | -| `add-project` | project-name (string) | Add a new project to a subscription | -| `auto-renewal` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | -| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | -| `del-project` | project-name (string) | Delete a project from a subscription | +| Transaction | Arguments | What it does | Effective in | +| -------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `add-project` | project-name (string) | Add a new project to a subscription | next block | +| `auto-renewal` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | next block | +| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | _new subscription_ - next block;
_upgrade subscription_ - next epoch;
_advance purchase_ - next block; | +| `del-project` | project-name (string) | Delete a project from a subscription | next epoch | Note that the `buy` transaction also support advance purchase and immediate upgrade. Refer to the help section of the commands for more details. From 23e7a77d48c405b8d3eb0e2ca59baceaf89601d0 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 08:25:38 -0500 Subject: [PATCH 26/52] Small fix to the auto-renewal cli --- x/subscription/client/cli/tx_auto_renewal.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/subscription/client/cli/tx_auto_renewal.go b/x/subscription/client/cli/tx_auto_renewal.go index 7aa1bdc552..a35957569e 100644 --- a/x/subscription/client/cli/tx_auto_renewal.go +++ b/x/subscription/client/cli/tx_auto_renewal.go @@ -1,6 +1,8 @@ package cli import ( + "fmt" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" @@ -27,6 +29,11 @@ lavad tx subscription auto-renewal false --from Date: Sun, 31 Dec 2023 08:31:03 -0500 Subject: [PATCH 27/52] CR Fix: Redundancy --- x/timerstore/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index e1aaaa6096..78b93229ee 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -4,9 +4,9 @@ This document specifies the `timerstore` module of Lava Protocol. -This module, primarily a utility for other modules, is not a standard module with which users can interact, except for a few queries intended for debugging purposes. The module is used as a powerful utility for other modules. +This module primarily serves as a utility for other modules in the Lava Protocol. It's not designed for direct user interaction, except for a limited set of queries intended for debugging. As such, it functions as an essential support utility within the protocol's ecosystem. -The timerstore allows other modules to create timers, that triggers a callback functions. +The `timerstore` allows other modules to create timers, that triggers a callback functions. A timer defined in this module can be of two types: BlockHeight or BlockTime. The callback function can be triggered either at BeginBlock or EndBlock. From 3e28660ee7800b52389e525aef295f0f973ceb5d Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 08:32:07 -0500 Subject: [PATCH 28/52] CR Fix: grammer --- x/timerstore/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index 78b93229ee..c4eff1e0f3 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -6,7 +6,7 @@ This document specifies the `timerstore` module of Lava Protocol. This module primarily serves as a utility for other modules in the Lava Protocol. It's not designed for direct user interaction, except for a limited set of queries intended for debugging. As such, it functions as an essential support utility within the protocol's ecosystem. -The `timerstore` allows other modules to create timers, that triggers a callback functions. +The `timerstore` allows other modules to create timers, that triggers callback functions. A timer defined in this module can be of two types: BlockHeight or BlockTime. The callback function can be triggered either at BeginBlock or EndBlock. From 29fdcf8b08e0915b8466a693da7bce139b0bf755 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 08:35:08 -0500 Subject: [PATCH 29/52] CR Fix: Grammer and elaboration --- x/timerstore/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index c4eff1e0f3..18c6769605 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -7,7 +7,8 @@ This document specifies the `timerstore` module of Lava Protocol. This module primarily serves as a utility for other modules in the Lava Protocol. It's not designed for direct user interaction, except for a limited set of queries intended for debugging. As such, it functions as an essential support utility within the protocol's ecosystem. The `timerstore` allows other modules to create timers, that triggers callback functions. -A timer defined in this module can be of two types: BlockHeight or BlockTime. +A timer defined in this module can be one of two types: BlockHeight or BlockTime. +The BlockHeight timers operate based on block counts, while BlockTime timers are measured in seconds. The callback function can be triggered either at BeginBlock or EndBlock. ## Contents From 376efeb0cf937862f73d4c48045dda91eaec5f65 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 08:41:13 -0500 Subject: [PATCH 30/52] CR Fix: Snippet for timer callback --- x/timerstore/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index 18c6769605..3d2dc62d72 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -49,7 +49,20 @@ The function `NewTimerStoreBeginBlock` will create a new `TimerStore` that will After calling the function `NewTimerStoreBeginBlock` or `NewTimerStoreEndBlock` the calling procedure can use the returned `TimerStore` object to set the callback of the timer. The callback has to be of the signature `func(ctx sdk.Context, key, data []byte)`, more on that in the next section. -To set the callback, the procedure needs to call `WithCallbackByBlockHeight` or `WithCallbackByBlockTime`. +To configure the callback for a timer, one must invoke either `WithCallbackByBlockHeight` or `WithCallbackByBlockTime`, depending on the timer type, like so: + +```go +// For a BlockHeight timer +timerStore.WithCallbackByBlockHeight(func(ctx sdk.Context, key, data []byte) { + // callback logic for BlockHeight timer +}) + +// For a BlockTime timer +timerStore.WithCallbackByBlockTime(func(ctx sdk.Context, key, data []byte) { + // callback logic for BlockTime timer +}) +``` + The `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockHeight` kind, and the `WithCallbackByBlockTime` function will add the callback to the `TimerStore` object, under the `BlockTime` kind. The `BlockHeight` callbacks, can be triggered at a given block, and the `BlockTime` callback can be triggered at the first block that it's time is past the given time. From 272adae70739738321e7643a4014861cb94142ea Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Sun, 31 Dec 2023 13:56:26 +0000 Subject: [PATCH 31/52] pr changes --- x/spec/README.md | 97 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/x/spec/README.md b/x/spec/README.md index fcd6cda3a6..e69f5cec90 100644 --- a/x/spec/README.md +++ b/x/spec/README.md @@ -14,8 +14,13 @@ This document focuses on the specs' technical aspects and does not include curre * [Spec](#spec) * [ApiCollections](#apicollection) * [CollectionData](#collectiondata) + * [Extension](#extension) + * [Api](#api) + * [SpecCategory](#speccategory) + * [BlockParsing](#blockparsing) * [ParseDirective](#parsedirective) * [Verification](#verification) + * [Header](#header) * [Import](#import) * [Parameters](#parameters) * [Queries](#queries) @@ -29,7 +34,7 @@ This document focuses on the specs' technical aspects and does not include curre A chain spec consists of general properties of the chain and a list of interfaces it supports. To simplify the creation and maintenance of specs, they can import APIs from another spec. For example, the X testnet spec can import from the X mainnet spec, eliminating the need to redefine all of the interfaces. -``` +```go type Spec struct { Index string // chain unique index Name string // description string of the spec @@ -59,14 +64,14 @@ A `Contributor` is a member of the Lava community who can earn token commissions ApiCollection is a struct that defines an interface, such as REST, JSON, etc., along with all of its APIs and properties. -``` +```go type ApiCollection struct { Enabled bool // enables/disables the collection CollectionData CollectionData // defines the properties of the collection, also acts as a unique key Apis []*Api // list of api's in the collection Headers []*Header // list of headers supported by the interface and their behaviour InheritanceApis []*CollectionData // list of other ApiCollection to inherite from - ParseDirectives []*[ParseDirective](#parsedirective) // list of parsing instructions of specific api's + ParseDirectives []*ParseDirective // list of parsing instructions of specific api's Extensions []*Extension // list of extensions that providers can support in addition to the basic behaviour (for example, archive node) Verifications []*Verification // list of verifications that providers must pass to make sure they provide full functionality } @@ -74,14 +79,14 @@ type ApiCollection struct { ### CollectionData -CollectionData defines the api properties and acts as a unique key for the [api collection](#apicollection) +CollectionData defines the api properties and acts as a unique key for the [api collection](#apicollection). -``` +```go type CollectionData struct { ApiInterface string // defines the connection interface (rest/json/grpc etc...) InternalPath string // defines internal path of the node for this specific ApiCollection Type string // type of api (POST/GET) - AddOn string // + AddOn string // optional APIs support } ``` @@ -91,13 +96,25 @@ The `InternalPath` field is utilized for chains that have varying RPC API sets i The `Type` field lets the user define APIs that have different functionalities depending on their type. the valid types are: `GET` and `POST`. An example of such API is Cosmos' `/cosmos/tx/v1beta1/txs` API. If it's sent as a `GET` request, it fetches transactions by event and if it's sent as a `POST` request, it sends a transaction. -The `AddOn` field lets you use additional optional APIs like debug, trace, +The `AddOn` field lets you use additional optional APIs like debug, trace etc. -### Api +### Extension -Api define a specific api in the api collection +this field defines an extansion for the api collection. +```go +type Extension struct { + Name string // name of the extension (archive) + CuMultiplier float32 // cu factor for supporting this extension + Rule *Rule // describes the rules when this extension is active +} ``` + +### Api + +Api define a specific api in the api collection. + +```go type Api struct { Enabled bool // enable/disable the api Name string // api name @@ -105,7 +122,7 @@ type Api struct { ExtraComputeUnits uint64 // not used Category SpecCategory // defines the property of the api BlockParsing BlockParser // specify how to parse the block from the api request - TimeoutMs uint64 // specifies the timeout expected for the api + TimeoutMs uint64 // specifies the timeout expected for the api (mseconds) } ``` @@ -131,11 +148,51 @@ example of an api definition: }, ``` -### ParseDirective +### SpecCategory + +This struct defines properties of an api. + +```go +type SpecCategory struct { + Deterministic bool // if this api have the same response across nodes + Local bool // TBD + Subscription bool // subscription base api + Stateful uint32 // TBD + HangingApi bool // marks this api with longer timeout +} +``` -ParseDirective is a struct that defines a function needed by the provider in a generic way. it describes how for a specific api collection how to get information from the node. for example, how to get the latest block of an EVM node. +### BlockParsing +This struct defines how to extract the block number from the api request. + +```go +type BlockParser struct { + ParserArg []string // describes where is the block number in the request + ParserFunc PARSER_FUNC // how to parse the request + DefaultValue string // the expected defualt value + Encoding string // number encoding (base64|Hex) +} ``` + +ParserFunc instructs how to parse the request to fetch the block number. + +```go +const ( + PARSER_FUNC_EMPTY PARSER_FUNC = 0 + PARSER_FUNC_PARSE_BY_ARG PARSER_FUNC = 1 + PARSER_FUNC_PARSE_CANONICAL PARSER_FUNC = 2 + PARSER_FUNC_PARSE_DICTIONARY PARSER_FUNC = 3 + PARSER_FUNC_PARSE_DICTIONARY_OR_ORDERED PARSER_FUNC = 4 + PARSER_FUNC_DEFAULT PARSER_FUNC = 6 +) +``` + +### ParseDirective + +ParseDirective is a struct that defines for the provider in a generic way how to fetch specific data from the node (for example: latest block height, block hash, ctv...). it describes for the api collection how to get information from the node. + +```go type ParseDirective struct { FunctionTag FUNCTION_TAG // defines what the function this serves for FunctionTemplate string // api template to fill and send to the node @@ -159,7 +216,7 @@ const ( The Verification struct defines how to verify a specific property of the API collection. For example, it can be used to verify the chain ID of the node. -``` +```go type Verification struct { Name string // verification name ParseDirective *ParseDirective // ParseDirective to get the the value to verify from the node @@ -168,6 +225,18 @@ type Verification struct { } ``` +### Headers + +Thie struct defines for the provider what action to take on the headers of the relayed message. + +```go +type Header struct { + Name string // name of the header + Kind Header_HeaderType // what action to take + FunctionTag FUNCTION_TAG // what is the function of the header +} +``` + ### Import Specs can import from other specs, which allows for easy creation and maintenance of specs. Specs import the api collections of the base specs. @@ -295,4 +364,4 @@ The plans module has the following events: | ---------- | --------------- | | `spec_add` | a successful addition of a spec | | `spec_modify` | a successful modification of an existing spec | -| `spec_refresh` | a spec was redreshed since it had a imported spec modified| \ No newline at end of file +| `spec_refresh` | a spec was rereshed since it had a imported spec modified| \ No newline at end of file From 3f29fca4d0ffa1ccc68834152285cee30a8135d9 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:02:16 -0500 Subject: [PATCH 32/52] CR Fix: Reorganise the Concepts section --- x/timerstore/README.md | 81 ++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index 3d2dc62d72..dde4038c73 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -14,10 +14,15 @@ The callback function can be triggered either at BeginBlock or EndBlock. ## Contents - [Concepts](#concepts) - - [TimerStore](#timerstore) - - [BeginBlock & EndBlock](#beginblock--endblock) - - [BlockHeight & BlockTime](#blockheight--blocktime) - - [Trigger](#trigger) + - [Creating the TimerStore](#creating-the-timerstore) + - [TimerStore Object](#timerstore-object) + - [Initialization Methods](#initialization-methods) + - [Setting Callbacks](#setting-callbacks) + - [Using the TimerStore](#using-the-timerstore) + - [Overview of Timer Usage](#overview-of-timer-usage) + - [Timer Types](#timer-types) + - [Adding Timers to TimerStore](#adding-timers-to-timerstore) + - [Timer Lifecycle](#timer-lifecycle) - [Parameters](#parameters) - [Queries](#queries) - [Transactions](#transactions) @@ -25,10 +30,11 @@ The callback function can be triggered either at BeginBlock or EndBlock. ## Concepts -### TimerStore +### Creating the TimerStore -The `TimerStore` object is the main entity of this module, and holds all of its logic. -The `timerstore` module's keeper is pretty straight forward: +#### TimerStore Object + +The core of this module is the `TimerStore` object, encapsulating all essential logic: ```go type Keeper struct { @@ -38,45 +44,58 @@ type Keeper struct { } ``` -Whenever a module creates a new timer, it will be stored in `timerStoresBegin` or in `timerStoresEnd`, depending on the function that was used to create the timer. +This structure reflects the dual nature of timer triggers: at the beginning or end of a block. -### BeginBlock & EndBlock +#### Initialization Methods -A module can decide whether to trigger a certain timer at the start of the block or at the end of it. -The function `NewTimerStoreBeginBlock` will create a new `TimerStore` that will trigger at the `BeginBlock` when the time is right, and the function `NewTimerStoreEndBlock` will create a new `TimerStore` that will trigger at the `EndBlock` when the time is right. +To initialize a `TimerStore`, use `NewTimerStoreBeginBlock` or `NewTimerStoreEndBlock`. These methods set up the store to trigger timers at either the start or end of a block, respectively. -### BlockHeight & BlockTime +#### Setting Callbacks -After calling the function `NewTimerStoreBeginBlock` or `NewTimerStoreEndBlock` the calling procedure can use the returned `TimerStore` object to set the callback of the timer. The callback has to be of the signature `func(ctx sdk.Context, key, data []byte)`, more on that in the next section. +Callbacks are set using `WithCallbackByBlockHeight` for block-height-based timers or `WithCallbackByBlockTime` for time-based timers. These methods link the specified callback function to the timer, ensuring it's executed at the right moment. -To configure the callback for a timer, one must invoke either `WithCallbackByBlockHeight` or `WithCallbackByBlockTime`, depending on the timer type, like so: +**Note:** The following code snippet demonstrates a common pattern used in the initialization of a timer in the codebase. This approach typically involves creating a `TimerStore` instance and immediately configuring its callback function, streamlining the setup process: ```go -// For a BlockHeight timer -timerStore.WithCallbackByBlockHeight(func(ctx sdk.Context, key, data []byte) { - // callback logic for BlockHeight timer -}) - -// For a BlockTime timer -timerStore.WithCallbackByBlockTime(func(ctx sdk.Context, key, data []byte) { - // callback logic for BlockTime timer -}) +callbackFunction := func(ctx sdk.Context, key, data []byte) { + // callback logic here +} + +timerStore := timerStoreKeeper.NewTimerStoreBeginBlock(storeKey, timerPrefix). + WithCallbackByBlockTime(callbackFunction) ``` -The `WithCallbackByBlockHeight` function will add the callback to the `TimerStore` object, under the `BlockHeight` kind, and the `WithCallbackByBlockTime` function will add the callback to the `TimerStore` object, under the `BlockTime` kind. +### Using the TimerStore + +#### Overview of Timer Usage + +After establishing the `TimerStore`, it's used to create and manage individual timers. This section delves into the operational aspect of the `TimerStore`. + +#### Timer Types -The `BlockHeight` callbacks, can be triggered at a given block, and the `BlockTime` callback can be triggered at the first block that it's time is past the given time. +There are two types of timers: -### Trigger +- `BlockHeight` timers trigger based on the count of blocks. +- `BlockTime` timers operate based on elapsed time in seconds. -As stated in the previous section, timers can be one of two kinds: `BlockHeight` or `BlockTime`. -Once a callback is created in the TimerStore, a procedure can then create a new timer corresponding to its kind. +Each type serves different use cases, offering flexibility in scheduling tasks. -For `BlockHeight` callbacks, the function `AddTimerByBlockHeight(ctx sdk.Context, block uint64, key, data []byte)` will create a timer that will trigger at `block`, with the given `key` and `data`. +#### Adding Timers to TimerStore + +Timers are added to the `TimerStore` via `AddTimerByBlockHeight` and `AddTimerByBlockTime`. These functions create timers that will trigger either at a specified block height or after a set time: + +```go +// Add a BlockHeight timer +AddTimerByBlockHeight(ctx sdk.Context, block uint64, key, data []byte) + +// Add a BlockTime timer +AddTimerByBlockTime(ctx sdk.Context, timestamp uint64, key, data []byte) + +``` -For `BlockTime` callbacks, the function `AddTimerByBlockTime(ctx sdk.Context, timestamp uint64, key, data []byte)` will create a timer that will trigger at the first block, which has a time that passed the given `timestamp`, with the given `key` and `data`. +#### Timer Lifecycle -Once the timer is triggered, it is deleted. So for a recurring timer, a new timer is need to be created every time it's triggered. +Once set, a timer remains active until triggered, after which it's automatically deleted. For recurring events, it's necessary to create a new timer each time. ## Parameters From 620343f3452b5248733823547ecdc6d7fc62010c Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:08:52 -0500 Subject: [PATCH 33/52] CR Fix: Key and Data explanation --- x/timerstore/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index dde4038c73..f0cea8e9ab 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -65,6 +65,11 @@ timerStore := timerStoreKeeper.NewTimerStoreBeginBlock(storeKey, timerPrefix). WithCallbackByBlockTime(callbackFunction) ``` +**Note on Timer Keys and Data:** + +- **Subkey as Timer Identifier:** The `key` argument in the callback function refers to the timer's subkey. It's important to note that this subkey does not need to be unique across all timers. The `timerstore` module internally manages a unique primary key for each timer, ensuring distinct identification. +- **Data Attachment:** The `data` argument allows for attaching relevant information to the timer. This data is then accessible when the callback function is triggered, enabling context-specific actions based on the timer's purpose. + ### Using the TimerStore #### Overview of Timer Usage @@ -93,6 +98,8 @@ AddTimerByBlockTime(ctx sdk.Context, timestamp uint64, key, data []byte) ``` +When using `AddTimerByBlockHeight` and `AddTimerByBlockTime`, it's important to understand the `key` argument is the timer's subkey, which doesn't need to be unique. The unique primary key of each timer is internally managed by the timerstore. The `data` argument is for attaching additional information to the timer, available when the callback is triggered. + #### Timer Lifecycle Once set, a timer remains active until triggered, after which it's automatically deleted. For recurring events, it's necessary to create a new timer each time. From 1c3e89403e64df0743f3d8ea1cf3d33c0bfd5627 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:10:21 -0500 Subject: [PATCH 34/52] CR Fix: "... and prefixes" --- x/timerstore/README.md | 2 +- x/timerstore/client/cli/query_store_keys.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x/timerstore/README.md b/x/timerstore/README.md index f0cea8e9ab..3fb1cf012d 100644 --- a/x/timerstore/README.md +++ b/x/timerstore/README.md @@ -116,7 +116,7 @@ The `timerstore` module supports the following queries: | ------------ | ----------------------------------- | ------------------------------------------------ | | `all-timers` | store-key (string), prefix (string) | Shows all timers of a specific timer store | | `next` | store-key (string), prefix (string) | Shows the next timeout of a specific timer store | -| `store-keys` | none | Shows all timer store keys | +| `store-keys` | none | Shows all timer store keys and prefixes | ## Transactions diff --git a/x/timerstore/client/cli/query_store_keys.go b/x/timerstore/client/cli/query_store_keys.go index c7482b6721..f169a71539 100644 --- a/x/timerstore/client/cli/query_store_keys.go +++ b/x/timerstore/client/cli/query_store_keys.go @@ -14,7 +14,7 @@ var _ = strconv.Itoa(0) func CmdStoreKeys() *cobra.Command { cmd := &cobra.Command{ Use: "store-keys", - Short: "Query all timer store keys", + Short: "Query all timer store keys and prefixes", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) (err error) { clientCtx, err := client.GetClientQueryContext(cmd) From 06546433e0edde392a20ae4b18d69e0837cbb1a6 Mon Sep 17 00:00:00 2001 From: Yarom Swisa Date: Sun, 31 Dec 2023 14:14:26 +0000 Subject: [PATCH 35/52] added coockbook url --- x/spec/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/spec/README.md b/x/spec/README.md index e69f5cec90..f7a3f7c527 100644 --- a/x/spec/README.md +++ b/x/spec/README.md @@ -33,6 +33,8 @@ This document focuses on the specs' technical aspects and does not include curre ### Spec A chain spec consists of general properties of the chain and a list of interfaces it supports. To simplify the creation and maintenance of specs, they can import APIs from another spec. For example, the X testnet spec can import from the X mainnet spec, eliminating the need to redefine all of the interfaces. +For more instructions on how to build a spec visit: https://github.com/lavanet/lava/blob/main/cookbook/README.md + ```go type Spec struct { @@ -170,7 +172,7 @@ This struct defines how to extract the block number from the api request. type BlockParser struct { ParserArg []string // describes where is the block number in the request ParserFunc PARSER_FUNC // how to parse the request - DefaultValue string // the expected defualt value + DefaultValue string // the expected default value Encoding string // number encoding (base64|Hex) } ``` From 011f46b2c3eb33a95483503900b308647cdbe068 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:17:59 -0500 Subject: [PATCH 36/52] CR Fix: Remove redundant line --- x/subscription/README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index 2805362f7f..e07e483e8b 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -44,8 +44,6 @@ After choosing a plan, a consumer can perform the purchase using the `buy` trans lavad tx subscription buy [plan-index] [optional: consumer] [optional: duration(months)] [flags] ``` -More on the `buy` transaction is under the [Transactions](#transactions) part under Buy. - Once a consumer bought a subscription, a new instance of Subscription object will be saved into the state: ```go @@ -182,12 +180,12 @@ All the transactions below require setting the `--from` flag and gas related fla The subscription module supports the following transactions: -| Transaction | Arguments | What it does | Effective in | -| -------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| `add-project` | project-name (string) | Add a new project to a subscription | next block | -| `auto-renewal` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | next block | -| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | _new subscription_ - next block;
_upgrade subscription_ - next epoch;
_advance purchase_ - next block; | -| `del-project` | project-name (string) | Delete a project from a subscription | next epoch | +| Transaction | Arguments | What it does | Effective in | +| -------------- | --------------------------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| `add-project` | project-name (string) | Add a new project to a subscription | next block | +| `auto-renewal` | [true, false] (bool), plan-index (string, optional), consumer (optional) | Enable/Disable auto-renewal to a subscription | next block | +| `buy` | plan-index (string), consumer (string, optional), duration (in months) (int , optional) | Buy a service plan | _new subscription_ - next block;
_upgrade subscription_ - next epoch;
_advance purchase_ - next block; | +| `del-project` | project-name (string) | Delete a project from a subscription | next epoch | Note that the `buy` transaction also support advance purchase and immediate upgrade. Refer to the help section of the commands for more details. From 2d67e86bff8057cd2d2c51bdbf80951cea403795 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:25:42 -0500 Subject: [PATCH 37/52] CR Fix: Simplify Advance Month section --- x/subscription/README.md | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index e07e483e8b..c9fe31ee69 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -85,31 +85,18 @@ Those tokens are later on distributed among the providers that served that consu One of the primary processes within this module involves the function `advanceMonth`, which is invoked upon the expiration of a timer set by the subscription. This function holds significance as it manages the logic that occurs at the conclusion of each month of a subscription. -Here's a high-level flow of the function: +The advanceMonth function plays a crucial role in the subscription lifecycle, performing several important tasks: -1. **Update the CU Tracker Timer:** +1. **CU Tracker Timer Update**: It updates the CU tracker timer, ensuring accurate tracking of Compute Units (CUs) for the subscription. - - Add a CU tracker timer for the subscription. (More information about the CU tracker can be found in the [pairing module](https://github.com/lavanet/lava/blob/main/x/pairing/README.md)) +2. **Subscription Duration Management**: The function checks the subscription's remaining duration. If there is time left, it deducts one month from the remaining duration and updates the total duration count, reflecting the passage of time. The details of the subscription, including CU allocations, are reset for the upcoming month. + In cases where the subscription has reached its end (no more remaining months), different scenarios are addressed: -2. **Check Subscription Duration:** + - Activation of a future subscription: If a future subscription is queued, it becomes active, renewing the service. + - Auto-renewal: If auto-renewal is enabled, an attempt is made to renew the subscription for another month. If this fails, the expired subscription is removed. + - No future subscription or auto-renewal: The expired subscription is removed from the system. - - If the subscription’s remaining duration (`DurationLeft`) is zero: - - If this occurs, it's considered a bug. Log the issue and extend the subscription by one month for smoother operation and investigation. - - If there is still time left (`DurationLeft` > 0): - - reduce it by one to reflect the passage of a month. - - Increase the total duration (`DurationTotal`) by one. - - Reset and update the subscription details. - - If no time is left (`DurationLeft` = 0): - - Subscription is expired. Refer to the next step. - -3. **Handle Special Cases for Subscriptions:** - - - If a future subscription is set: - - Activate the future subscription by updating current subscription details with those of the future subscription and restart the subscription. - - Else, if auto-renewal is enabled: - - Attempt to renew the subscription for another month. - - If renewal fails, remove the expired subscription. - - If there is no future subscription or auto-renewal, remove the expired subscription. +3. **Special Cases Handling**: The function also handles special cases, such as the transition to a future subscription or the implementation of auto-renewal, ensuring continuous service or appropriate termination of the subscription. ### Subscription Upgrade @@ -117,7 +104,7 @@ A subscription can be upgraded to a more expensive plan. That also means, that if a consumer buys a plan, and then decides to downgrade their plan, the way to do it is either buy more months in the cheaper subscription, so the amount that the consumer will pay will be larger than the one using the same command as shown above, with the only caveat - the `plan-index` must be different than the currently active subscription's plan, and, it must be with a higher price. -Tokens are deducted from the creator's account immediately, and the new plan becomes effective in the next epoch. +Tokens are deducted from the creator's account immediately, and the new plan becomes effective in the next epoch. More ways to upgrade are by making an [Advance Purchase](#advance-purchase) or enabling [Auto Renewal](#auto-renewal). From 0209b1a3defe1341632864c7780f1adb17052c70 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:29:41 -0500 Subject: [PATCH 38/52] CR Fix: Rephrase the 'downgrade' paragraph --- x/subscription/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index c9fe31ee69..a7e508fe2b 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -102,7 +102,9 @@ The advanceMonth function plays a crucial role in the subscription lifecycle, pe A subscription can be upgraded to a more expensive plan. -That also means, that if a consumer buys a plan, and then decides to downgrade their plan, the way to do it is either buy more months in the cheaper subscription, so the amount that the consumer will pay will be larger than the one using the same command as shown above, with the only caveat - the `plan-index` must be different than the currently active subscription's plan, and, it must be with a higher price. +While direct downgrading of a subscription plan is not officially supported, consumers seeking a lower-tier plan can still achieve a similar effect. +This can be done by purchasing additional months under a less expensive plan. It's important to note that the new plan's `plan-index` must differ from the currently active one, and the total payment for the extended period under the cheaper plan should exceed the amount of the original, higher-priced plan. +This workaround allows consumers to effectively 'downgrade' their subscription within the system's constraints. Tokens are deducted from the creator's account immediately, and the new plan becomes effective in the next epoch. From 151e51f0462996dd83f355d61c69c3b84947dab5 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:33:05 -0500 Subject: [PATCH 39/52] CR Fix: Clarification in the Abstract section --- x/subscription/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index a7e508fe2b..dc910d9cf1 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -7,8 +7,10 @@ This document specifies the subscription module of Lava Protocol. The subscription module is responsible for managing Lava's consumer subscription. To use Lava, consumers must purchase a subscription. The subscription operates on a monthly basis, starting from the next block after purchase and ending one month later. -If a user purchases a subscription for more than one month, it will reset at the end of each month until its expiration. -When a subscription is reset, the monthly left CUs is set to the plan's monthly CU. +If a user purchases a subscription for more than one month, it will reset at the end of each month until its expiration, with the monthly remaining Compute Units (CUs) being reset to the plan's monthly CU allocation. + +Subscriptions are subject to limitations set by the current policy, including a cap on monthly Compute Units (CUs). +These CUs reset with each new subscription month, ensuring users get a fresh allocation based on their plan. A subscription can be renewed, manually or automatically. Additionally, a subscription plan can be bought in advance for months ahead. From b9f8c10b614eaefa91c6169e87591036b50a29cf Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 09:55:21 -0500 Subject: [PATCH 40/52] Remove downgrading paragraph --- x/subscription/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index dc910d9cf1..19a1252dc4 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -103,13 +103,7 @@ The advanceMonth function plays a crucial role in the subscription lifecycle, pe ### Subscription Upgrade A subscription can be upgraded to a more expensive plan. - -While direct downgrading of a subscription plan is not officially supported, consumers seeking a lower-tier plan can still achieve a similar effect. -This can be done by purchasing additional months under a less expensive plan. It's important to note that the new plan's `plan-index` must differ from the currently active one, and the total payment for the extended period under the cheaper plan should exceed the amount of the original, higher-priced plan. -This workaround allows consumers to effectively 'downgrade' their subscription within the system's constraints. - Tokens are deducted from the creator's account immediately, and the new plan becomes effective in the next epoch. - More ways to upgrade are by making an [Advance Purchase](#advance-purchase) or enabling [Auto Renewal](#auto-renewal). ### Subscription Renewal From 9bee5553d7176c3b1fb01c4ec76df0af09c6b503 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Sun, 31 Dec 2023 11:22:54 -0500 Subject: [PATCH 41/52] CR Fix: Reference to pairing module --- x/subscription/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/x/subscription/README.md b/x/subscription/README.md index 19a1252dc4..dfe87f2b28 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -90,6 +90,7 @@ One of the primary processes within this module involves the function `advanceMo The advanceMonth function plays a crucial role in the subscription lifecycle, performing several important tasks: 1. **CU Tracker Timer Update**: It updates the CU tracker timer, ensuring accurate tracking of Compute Units (CUs) for the subscription. + For more information on the CU tracker, please refer to the [Pairing module](https://github.com/lavanet/lava/blob/main/x/pairing/README.md). 2. **Subscription Duration Management**: The function checks the subscription's remaining duration. If there is time left, it deducts one month from the remaining duration and updates the total duration count, reflecting the passage of time. The details of the subscription, including CU allocations, are reset for the upcoming month. In cases where the subscription has reached its end (no more remaining months), different scenarios are addressed: From fc175ce7c50468156dedd1b765097b957161f826 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 18:30:44 +0200 Subject: [PATCH 42/52] CNS-787: fix downtime query --- x/downtime/client/cli/query.go | 4 ++-- x/downtime/keeper/keeper.go | 1 + x/downtime/keeper/query_server.go | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/x/downtime/client/cli/query.go b/x/downtime/client/cli/query.go index fab5fd1727..c99b1e9ea1 100644 --- a/x/downtime/client/cli/query.go +++ b/x/downtime/client/cli/query.go @@ -48,9 +48,9 @@ func CmdQueryParams() *cobra.Command { func CmdQueryDowntime() *cobra.Command { cmd := &cobra.Command{ - Use: "downtime [epoch_start_block]", + Use: "downtime [block]", Short: "Query downtime", - Long: "Query downtime between blocks, if only start is provided then will query for downtime at the given block, if end is provided then it will query the full range", + Long: "Query downtime between blocks, if only start is provided then will query for downtime at the given epoch (the epoch of the block)", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { start, err := strconv.ParseUint(args[0], 10, 64) diff --git a/x/downtime/keeper/keeper.go b/x/downtime/keeper/keeper.go index eab399a8f2..1acd760f59 100644 --- a/x/downtime/keeper/keeper.go +++ b/x/downtime/keeper/keeper.go @@ -18,6 +18,7 @@ type EpochStorageKeeper interface { IsEpochStart(ctx sdk.Context) bool GetEpochStart(ctx sdk.Context) (epochStartBlock uint64) GetDeletedEpochs(ctx sdk.Context) []uint64 + GetEpochStartForBlock(ctx sdk.Context, block uint64) (epochStart uint64, blockInEpoch uint64, err error) } func NewKeeper(cdc codec.BinaryCodec, sk storetypes.StoreKey, ps paramtypes.Subspace, esk EpochStorageKeeper) Keeper { diff --git a/x/downtime/keeper/query_server.go b/x/downtime/keeper/query_server.go index 51c0180452..5bb31ca76a 100644 --- a/x/downtime/keeper/query_server.go +++ b/x/downtime/keeper/query_server.go @@ -19,7 +19,12 @@ func (q queryServer) QueryParams(ctx context.Context, request *v1.QueryParamsReq } func (q queryServer) QueryDowntime(ctx context.Context, request *v1.QueryDowntimeRequest) (*v1.QueryDowntimeResponse, error) { - dt, _ := q.k.GetDowntime(sdk.UnwrapSDKContext(ctx), request.EpochStartBlock) + _ctx := sdk.UnwrapSDKContext(ctx) + epochStart, _, err := q.k.epochStorageKeeper.GetEpochStartForBlock(_ctx, request.EpochStartBlock) + if err != nil { + return nil, err + } + dt, _ := q.k.GetDowntime(sdk.UnwrapSDKContext(ctx), epochStart) return &v1.QueryDowntimeResponse{CumulativeDowntimeDuration: dt}, nil } From 122824b0f02ec155643e3c3dc1376567982eb3d0 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 18:38:06 +0200 Subject: [PATCH 43/52] CNS-787: fix query help --- x/downtime/client/cli/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/downtime/client/cli/query.go b/x/downtime/client/cli/query.go index c99b1e9ea1..a06024ba0b 100644 --- a/x/downtime/client/cli/query.go +++ b/x/downtime/client/cli/query.go @@ -50,7 +50,7 @@ func CmdQueryDowntime() *cobra.Command { cmd := &cobra.Command{ Use: "downtime [block]", Short: "Query downtime", - Long: "Query downtime between blocks, if only start is provided then will query for downtime at the given epoch (the epoch of the block)", + Long: "Query downtime at the given epoch (the epoch of the block)", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { start, err := strconv.ParseUint(args[0], 10, 64) From ac1b94b703a8185f59a53d84086fa80921f9cab9 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 19:00:28 +0200 Subject: [PATCH 44/52] CNS-787: downtime README --- x/downtime/README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 x/downtime/README.md diff --git a/x/downtime/README.md b/x/downtime/README.md new file mode 100644 index 0000000000..4cc1e72a6e --- /dev/null +++ b/x/downtime/README.md @@ -0,0 +1,49 @@ +# `x/downtime` + +## Abstract + +This document specifies the downtime module of Lava Protocol. + +The downtime module is responsible handling provider rewards in case of a chain halt. Lava protocol lets consumers send relays and providers to accept them even if blocks are not advancing because of consensus failure. When the blockchain is back to normal the providers should be able to receive rewards on the relays they served. The downtime is also used to determine the validators' block rewards. + +## Contents +* [Parameters](#parameters) + * [DowntimeDuration](#downtimeduration) + * [EpochDuration](#epochduration) +* [Queries](#queries) +* [Transactions](#transactions) +* [Proposals](#proposals) + +## Parameters + +The downtime module contains the following parameters: + +| Key | Type | Default Value | +| -------------------------------------- | ----------------------- | -----------------| +| DowntimeDuration | time.Duration | 5min | +| EpochDuration | time.Duration | 30min | + +### DowntimeDuration + +DowntimeDuration defines the minimum time elapsed between blocks that we consider the chain to be down. + +### EpochDuration + +EpochDuration defines an estimation of the time elapsed between epochs. + +## Queries + +The downtime module supports the following queries: + +| Query | Arguments | What it does | +| ---------- | --------------- | ----------------------------------------------| +| `downtime` | block (uint64) | shows downtime at the given epoch (the epoch of the block) | +| `params` | none | shows the module's parameters | + +## Transactions + +The downtime module does not support any transactions. + +## Proposals + +The downtime module does not support any proposals. From 064adb47c801d9c5d1727d2a4ad454e92dc5e1d7 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 19:42:42 +0200 Subject: [PATCH 45/52] CNS-787: fix unit test --- x/downtime/keeper/query_server_test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/x/downtime/keeper/query_server_test.go b/x/downtime/keeper/query_server_test.go index 945b85466e..359a850d21 100644 --- a/x/downtime/keeper/query_server_test.go +++ b/x/downtime/keeper/query_server_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "encoding/binary" "testing" "time" @@ -8,6 +9,7 @@ import ( "github.com/lavanet/lava/app" "github.com/lavanet/lava/x/downtime/keeper" v1 "github.com/lavanet/lava/x/downtime/v1" + "github.com/lavanet/lava/x/epochstorage/types" "github.com/stretchr/testify/require" ) @@ -18,10 +20,24 @@ func TestQueryServer_QueryDowntime(t *testing.T) { // set some downtimes downtime := &v1.Downtime{ - Block: 1, + Block: 0, Duration: 50 * time.Minute, } + app.EpochstorageKeeper.SetEpochDetails(ctx, types.EpochDetails{ + StartBlock: uint64(ctx.BlockHeight()), + EarliestStart: uint64(ctx.BlockHeight()), + DeletedEpochs: []uint64{}, + }) + app.EpochstorageKeeper.SetParams(ctx, types.DefaultParams()) + + raw := make([]byte, 8) + binary.LittleEndian.PutUint64(raw, 20) + app.EpochstorageKeeper.SetFixatedParams(ctx, types.FixatedParams{ + Index: string(types.KeyEpochBlocks) + "0", + Parameter: raw, + FixationBlock: uint64(ctx.BlockHeight()), + }) dk.SetDowntime(ctx, downtime.Block, downtime.Duration) t.Run("ok", func(t *testing.T) { From e1d6ae7890b1139cd2c1c4f8040963694bfe49f6 Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 19:44:19 +0200 Subject: [PATCH 46/52] CNS-770: PR changes --- x/pairing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/pairing/README.md b/x/pairing/README.md index 4bd073479a..7e5f909a95 100644 --- a/x/pairing/README.md +++ b/x/pairing/README.md @@ -227,7 +227,7 @@ Here is the workflow for Lava's payment mechanism: #### CU Tracking -The goal of CU tracking is to keep records of serviced CUs by the providers to a specific subscription, in order to determine the amount of payment they should receive at the month expiry of said subscription. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each month, the CU tracker is reset for all providers. +The goal of CU tracking is to keep records of serviced CUs by the providers to a specific subscription, in order to determine the amount of payment they should receive at the end of the month of said subscription. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each month, the CU tracker is reset for all providers. #### Providers Payment From 76131d5da5b76b464957f5516626f5add8cc0d1a Mon Sep 17 00:00:00 2001 From: oren-lava Date: Sun, 31 Dec 2023 19:52:57 +0200 Subject: [PATCH 47/52] CNS-770: more fixes --- x/pairing/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/pairing/README.md b/x/pairing/README.md index 7e5f909a95..63c4467437 100644 --- a/x/pairing/README.md +++ b/x/pairing/README.md @@ -227,7 +227,7 @@ Here is the workflow for Lava's payment mechanism: #### CU Tracking -The goal of CU tracking is to keep records of serviced CUs by the providers to a specific subscription, in order to determine the amount of payment they should receive at the end of the month of said subscription. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each month, the CU tracker is reset for all providers. +The goal of CU tracking is to keep records of serviced CUs by the providers to a specific subscription, in order to determine the amount of payment they should receive at the end of the month of said subscription. When a relay payment transaction occurs, the number of CUs associated with each relay is counted and saved under the provider who initiated the transaction. At the end of each subscription end of month, the CU tracker is reset for all providers that serviced this subscription. #### Providers Payment From abc96ed102734bf0ac5031a6d16fd2320d2b31f5 Mon Sep 17 00:00:00 2001 From: Ran Mishael <106548467+ranlavanet@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:53:40 +0100 Subject: [PATCH 48/52] PRT - fix e2e race condition and invalid behavior (#1085) * fix init_e2e race condition and invalid behavior * better stability * adding more info to debug * test adjustment * fix e2e * fix * fix e2e adding another block wait because of begin block event order * adding delay because github action is so slow. * fix a bug related to qos excellence report. * fix optimizer race condition on unitests * upgrade lavajs to v0.32.4 * increase protocol patch version to 32.5 * using rlock instead * fix division of zero * fix lint * fixed race in init_e2e * changed sync score to one instead of 0 in excellence due to 0 being ideal value and 1 being 0 contribution --- cookbook/plans/test_plans/temporary-add.json | 67 +++++++------------ ecosystem/lavajs/package.json | 3 +- protocol/lavasession/consumer_types.go | 2 +- .../provideroptimizer/provider_optimizer.go | 11 ++- .../provider_optimizer_test.go | 24 ++++++- scripts/init_chain.sh | 12 ++-- scripts/init_e2e.sh | 36 +++++++--- x/pairing/types/QualityOfServiceReport.go | 9 +++ .../types/QualityOfServiceReport_test.go | 48 +++++++++++++ x/protocol/types/params.go | 2 +- 10 files changed, 150 insertions(+), 64 deletions(-) create mode 100644 x/pairing/types/QualityOfServiceReport_test.go diff --git a/cookbook/plans/test_plans/temporary-add.json b/cookbook/plans/test_plans/temporary-add.json index 807f01197f..1e73a143dd 100755 --- a/cookbook/plans/test_plans/temporary-add.json +++ b/cookbook/plans/test_plans/temporary-add.json @@ -1,45 +1,30 @@ { "proposal": { - "title": "Add temporary to-delete plan proposal", - "description": "A proposal of a temporary to-delete plan", - "plans": [ - { - "index": "to_delete_plan", - "description": "This plan has no restrictions", - "type": "rpc", - "price": { - "denom": "ulava", - "amount": "100000" - }, - "annual_discount_percentage": 20, - "allow_overuse": true, - "overuse_rate": 2, - "plan_policy": { - "chain_policies": [ - { - "chain_id": "LAV1", - "apis": [ - ] - }, - { - "chain_id": "ETH1", - "apis": [ - "eth_blockNumber", - "eth_accounts" - ] - } - ], - "geolocation_profile": "AU", - "total_cu_limit": 1000000, - "epoch_cu_limit": 100000, - "max_providers_to_pair": 3, - "selected_providers_mode": "MIXED", - "selected_providers": [ - "lava@1wvn4slrf2r7cm92fnqdhvl3x470944uev92squ" - ] - } - } - ] + "title": "to_delete_plan", + "description": "to_delete_plan", + "plans": [ + { + "index": "to_delete_plan", + "description": "to_delete_plan", + "type": "rpc", + "price": { + "denom": "ulava", + "amount": "10000" + }, + "annual_discount_percentage": 20, + "allow_overuse": false, + "overuse_rate": 0, + "plan_policy": { + "chain_policies": [], + "geolocation_profile": "GL", + "total_cu_limit": 100000, + "epoch_cu_limit": 50, + "max_providers_to_pair": 5, + "selected_providers_mode": "ALLOWED", + "selected_providers": [] + } + } + ] }, "deposit": "10000000ulava" -} + } \ No newline at end of file diff --git a/ecosystem/lavajs/package.json b/ecosystem/lavajs/package.json index c098e13b7c..80992f193d 100644 --- a/ecosystem/lavajs/package.json +++ b/ecosystem/lavajs/package.json @@ -1,6 +1,6 @@ { "name": "@lavanet/lavajs", - "version": "0.22.5", + "version": "0.32.4", "description": "lavajs", "author": "Lava Network", "homepage": "https://github.com/lavanet/lava/tree/main/ecosystem/lavajs#readme", @@ -17,6 +17,7 @@ ], "scripts": { "e2e-setup": "./scripts/build_lavajs.sh -s", + "build_lava_js": "./scripts/build_lavajs.sh -s", "init": "./scripts/build_lavajs.sh -s", "clean:mjs": "rimraf mjs", "clean:dist": "rimraf dist", diff --git a/protocol/lavasession/consumer_types.go b/protocol/lavasession/consumer_types.go index 6f4a8459ee..4060d51efd 100644 --- a/protocol/lavasession/consumer_types.go +++ b/protocol/lavasession/consumer_types.go @@ -467,7 +467,7 @@ func (cs *SingleConsumerSession) CalculateExpectedLatency(timeoutGivenToRelay ti // cs should be locked here to use this method, returns the computed qos or zero if last qos is nil or failed to compute. func (cs *SingleConsumerSession) getQosComputedResultOrZero() sdk.Dec { if cs.QoSInfo.LastExcellenceQoSReport != nil { - qosComputed, errComputing := cs.QoSInfo.LastExcellenceQoSReport.ComputeQoS() + qosComputed, errComputing := cs.QoSInfo.LastExcellenceQoSReport.ComputeQoSExcellence() if errComputing == nil { // if we failed to compute the qos will be 0 so this provider wont be picked to return the error in case we get it return qosComputed } diff --git a/protocol/provideroptimizer/provider_optimizer.go b/protocol/provideroptimizer/provider_optimizer.go index d8d816fc2b..e78d7d1b6b 100644 --- a/protocol/provideroptimizer/provider_optimizer.go +++ b/protocol/provideroptimizer/provider_optimizer.go @@ -37,9 +37,14 @@ type ConcurrentBlockStore struct { Block uint64 } +type cacheInf interface { + Get(key interface{}) (interface{}, bool) + Set(key, value interface{}, cost int64) bool +} + type ProviderOptimizer struct { strategy Strategy - providersStorage *ristretto.Cache + providersStorage cacheInf providerRelayStats *ristretto.Cache // used to decide on the half time of the decay averageBlockTime time.Duration baseWorldLatency time.Duration @@ -467,6 +472,10 @@ func (po *ProviderOptimizer) GetExcellenceQoSReportForProvider(providerAddress s precision := WANTED_PRECISION latencyScore := turnFloatToDec(providerData.Latency.Num/providerData.Latency.Denom, precision) syncScore := turnFloatToDec(providerData.Sync.Num/providerData.Sync.Denom, precision) + // if our sync score is un initialized due to lack of providers + if syncScore.IsZero() { + syncScore = sdk.OneDec() + } availabilityScore := turnFloatToDec(providerData.Availability.Num/providerData.Availability.Denom, precision) ret := &pairingtypes.QualityOfServiceReport{ Latency: latencyScore, diff --git a/protocol/provideroptimizer/provider_optimizer_test.go b/protocol/provideroptimizer/provider_optimizer_test.go index 1298aa80c6..d4e393dd09 100644 --- a/protocol/provideroptimizer/provider_optimizer_test.go +++ b/protocol/provideroptimizer/provider_optimizer_test.go @@ -2,6 +2,7 @@ package provideroptimizer import ( "strconv" + "sync" "testing" "time" @@ -16,6 +17,25 @@ const ( TEST_BASE_WORLD_LATENCY = 150 * time.Millisecond ) +type providerOptimizerSyncCache struct { + value map[interface{}]interface{} + lock sync.RWMutex +} + +func (posc *providerOptimizerSyncCache) Get(key interface{}) (interface{}, bool) { + posc.lock.RLock() + defer posc.lock.RUnlock() + ret, ok := posc.value[key] + return ret, ok +} + +func (posc *providerOptimizerSyncCache) Set(key, value interface{}, cost int64) bool { + posc.lock.Lock() + defer posc.lock.Unlock() + posc.value[key] = value + return true +} + func setupProviderOptimizer(maxProvidersCount int) *ProviderOptimizer { averageBlockTIme := TEST_AVERAGE_BLOCK_TIME baseWorldLatency := TEST_BASE_WORLD_LATENCY @@ -214,6 +234,7 @@ func TestProviderOptimizerAvailabilityBlockError(t *testing.T) { pertrubationPercentage := 0.0 syncBlock := uint64(requestBlock) chosenIndex := rand.Intn(providersCount) + for i := range providersGen.providersAddresses { time.Sleep(4 * time.Millisecond) // give all providers a worse availability score @@ -242,12 +263,12 @@ func TestProviderOptimizerUpdatingLatency(t *testing.T) { requestCU := uint64(10) requestBlock := int64(1000) syncBlock := uint64(requestBlock) + providerOptimizer.providersStorage = &providerOptimizerSyncCache{value: map[interface{}]interface{}{}} // in this test we are repeatedly adding better results, and latency score should improve for i := 0; i < 10; i++ { providerData, _ := providerOptimizer.getProviderData(providerAddress) currentLatencyScore := providerOptimizer.calculateLatencyScore(providerData, requestCU, requestBlock) providerOptimizer.AppendProbeRelayData(providerAddress, TEST_BASE_WORLD_LATENCY, true) - time.Sleep(4 * time.Millisecond) providerData, found := providerOptimizer.getProviderData(providerAddress) require.True(t, found) newLatencyScore := providerOptimizer.calculateLatencyScore(providerData, requestCU, requestBlock) @@ -258,7 +279,6 @@ func TestProviderOptimizerUpdatingLatency(t *testing.T) { providerData, _ := providerOptimizer.getProviderData(providerAddress) currentLatencyScore := providerOptimizer.calculateLatencyScore(providerData, requestCU, requestBlock) providerOptimizer.AppendRelayData(providerAddress, TEST_BASE_WORLD_LATENCY, false, requestCU, syncBlock) - time.Sleep(4 * time.Millisecond) providerData, found := providerOptimizer.getProviderData(providerAddress) require.True(t, found) newLatencyScore := providerOptimizer.calculateLatencyScore(providerData, requestCU, requestBlock) diff --git a/scripts/init_chain.sh b/scripts/init_chain.sh index 21c05fa736..83d341f74e 100755 --- a/scripts/init_chain.sh +++ b/scripts/init_chain.sh @@ -27,8 +27,8 @@ if [ "$1" == "debug" ]; then data=$(cat "$path$genesis" \ | jq '.app_state.gov.params.min_deposit[0].denom = "ulava"' \ | jq '.app_state.gov.params.min_deposit[0].amount = "100"' \ - | jq '.app_state.gov.params.voting_period = "3s"' \ - | jq '.app_state.gov.params.expedited_voting_period = "1s"' \ + | jq '.app_state.gov.params.voting_period = "4s"' \ + | jq '.app_state.gov.params.expedited_voting_period = "3s"' \ | jq '.app_state.gov.params.expedited_min_deposit[0].denom = "ulava"' \ | jq '.app_state.gov.params.expedited_min_deposit[0].amount = "200"' \ | jq '.app_state.gov.params.expedited_threshold = "0.67"' \ @@ -43,8 +43,8 @@ else data=$(cat "$path$genesis" \ | jq '.app_state.gov.params.min_deposit[0].denom = "ulava"' \ | jq '.app_state.gov.params.min_deposit[0].amount = "100"' \ - | jq '.app_state.gov.params.voting_period = "3s"' \ - | jq '.app_state.gov.params.expedited_voting_period = "1s"' \ + | jq '.app_state.gov.params.voting_period = "4s"' \ + | jq '.app_state.gov.params.expedited_voting_period = "3s"' \ | jq '.app_state.gov.params.expedited_min_deposit[0].denom = "ulava"' \ | jq '.app_state.gov.params.expedited_min_deposit[0].amount = "200"' \ | jq '.app_state.gov.params.expedited_threshold = "0.67"' \ @@ -52,8 +52,8 @@ else | jq '.app_state.mint.params.mint_denom = "ulava"' \ | jq '.app_state.staking.params.bond_denom = "ulava"' \ | jq '.app_state.crisis.constant_fee.denom = "ulava"' \ - | jq '.app_state.downtime.params.downtime_duration = "10s"' \ - | jq '.app_state.downtime.params.epoch_duration = "20s"' \ + | jq '.app_state.downtime.params.downtime_duration = "6s"' \ + | jq '.app_state.downtime.params.epoch_duration = "10s"' \ ) fi diff --git a/scripts/init_e2e.sh b/scripts/init_e2e.sh index 8070c216dc..87ff7e216d 100755 --- a/scripts/init_e2e.sh +++ b/scripts/init_e2e.sh @@ -11,10 +11,7 @@ GASPRICE="0.000000001ulava" echo ---- Specs proposal ---- lavad tx gov submit-legacy-proposal spec-add ./cookbook/specs/spec_add_ethereum.json,./cookbook/specs/spec_add_ibc.json,./cookbook/specs/spec_add_cosmossdk.json,./cookbook/specs/spec_add_lava.json --lava-dev-test -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE wait_next_block -lavad tx gov deposit 1 100ulava -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE -wait_next_block lavad tx gov vote 1 yes -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE - sleep 6 # need to sleep because plan policies need the specs when setting chain policies verifications # Plans proposal @@ -22,18 +19,15 @@ echo ---- Plans proposal ---- wait_next_block lavad tx gov submit-legacy-proposal plans-add ./cookbook/plans/test_plans/default.json,./cookbook/plans/test_plans/emergency-mode.json,./cookbook/plans/test_plans/temporary-add.json -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE wait_next_block -lavad tx gov deposit 2 100ulava -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE -wait_next_block lavad tx gov vote 2 yes -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE sleep 6 # Plan removal (of one) echo ---- Plans removal ---- wait_next_block +# delete plan that deletes "temporary add" plan lavad tx gov submit-legacy-proposal plans-del ./cookbook/plans/test_plans/temporary-del.json -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE wait_next_block -lavad tx gov deposit 3 1ulava -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE -wait_next_block lavad tx gov vote 3 yes -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE STAKE="500000000000ulava" @@ -62,14 +56,18 @@ lavad tx subscription buy "EmergencyModePlan" -y --from user5 --gas-adjustment " # Test plan upgrade echo ---- Subscription plan upgrade ---- wait_next_block -lavad tx subscription buy "DefaultPlan" -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE +# test we have the plan active. plan_index=$(lavad q subscription current $(lavad keys show user1 -a) | yq .sub.plan_index) -if [ "$plan_index" != "EmergencyModePlan" ]; then "echo subscription ${user1addr}: wrong plan index $plane_index instead of EmergencyModePlan"; exit 1; fi +if [ "$plan_index" != "EmergencyModePlan" ]; then "echo subscription ${user1addr}: wrong plan index $plan_index .sub.plan_index doesn't contain EmergencyModePlan"; exit 1; fi +# buy the upgraded subscription +lavad tx subscription buy "DefaultPlan" -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE +# wait for the new subscription to take effect (1 epoch + 1 block as changes happen to subscription module after epochstorage module on the begin block events) +wait_next_block # wait block is here in case 1 block before epoch change we commit but the effect happens only on the epoch change meaning we didn't really wait an epoch for the changes to take effect. sleep_until_next_epoch - +# validate the new subscription is the default plan and not emergency mode plan. plan_index=$(lavad q subscription current $(lavad keys show user1 -a) | yq .sub.plan_index) -if [ "$plan_index" != "DefaultPlan" ]; then "echo subscription ${user1addr}: wrong plan index $plane_index instead of DefaultPlan"; exit 1; fi +if [ "$plan_index" != "DefaultPlan" ]; then "echo subscription ${user1addr}: wrong plan index $plan_index .sub.plan_index doesn't contain DefaultPlan"; exit 1; fi user3addr=$(lavad keys show user3 -a) @@ -93,4 +91,20 @@ sleep_until_next_epoch count=$(lavad q subscription list-projects ${user3addr} | grep "lava@" | wc -l) if [ "$count" -ne 2 ]; then "echo subscription ${user3addr}: wrong project count $count instead of 2"; exit 1; fi + +# validate deleted plan is removed. +# Fetch the plans list +plans_list=$(lavad q plan list | yq .plans_info) + +# Check if "to_delete_plan" exists +if echo "$plans_list" | grep -q '"index": "to_delete_plan"'; then + echo "Index 'to_delete_plan' exists." + exit 1 # fail test. +else + echo "Index 'to_delete_plan' was removed successfully validation passed." +fi + + + # the end + diff --git a/x/pairing/types/QualityOfServiceReport.go b/x/pairing/types/QualityOfServiceReport.go index ea3c868826..d5bb0b4836 100644 --- a/x/pairing/types/QualityOfServiceReport.go +++ b/x/pairing/types/QualityOfServiceReport.go @@ -15,3 +15,12 @@ func (qos *QualityOfServiceReport) ComputeQoS() (sdk.Dec, error) { return qos.Availability.Mul(qos.Sync).Mul(qos.Latency).ApproxRoot(3) } + +func (qos *QualityOfServiceReport) ComputeQoSExcellence() (sdk.Dec, error) { + if qos.Availability.LTE(sdk.ZeroDec()) || + qos.Latency.LTE(sdk.ZeroDec()) || + qos.Sync.LTE(sdk.ZeroDec()) { + return sdk.ZeroDec(), fmt.Errorf("QoS excellence scores is below 0") + } + return qos.Availability.Quo(qos.Sync).Quo(qos.Latency).ApproxRoot(3) +} diff --git a/x/pairing/types/QualityOfServiceReport_test.go b/x/pairing/types/QualityOfServiceReport_test.go new file mode 100644 index 0000000000..83a15ce6d6 --- /dev/null +++ b/x/pairing/types/QualityOfServiceReport_test.go @@ -0,0 +1,48 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestQosReport(t *testing.T) { + qos1 := &QualityOfServiceReport{ + Latency: sdk.MustNewDecFromStr("1.5"), + Availability: sdk.MustNewDecFromStr("1"), + Sync: sdk.MustNewDecFromStr("0.1"), + } + qos2 := &QualityOfServiceReport{ + Latency: sdk.MustNewDecFromStr("0.2"), + Availability: sdk.MustNewDecFromStr("1"), + Sync: sdk.MustNewDecFromStr("0.1"), + } + qos3 := &QualityOfServiceReport{ + Latency: sdk.MustNewDecFromStr("0.1"), + Availability: sdk.MustNewDecFromStr("1"), + Sync: sdk.MustNewDecFromStr("0.5"), + } + qos4 := &QualityOfServiceReport{ + Latency: sdk.MustNewDecFromStr("0.1"), + Availability: sdk.MustNewDecFromStr("0.5"), + Sync: sdk.MustNewDecFromStr("0.5"), + } + + qos1Res, errQos1 := qos1.ComputeQoSExcellence() + qos2Res, errQos2 := qos2.ComputeQoSExcellence() + qos3Res, errQos3 := qos3.ComputeQoSExcellence() + qos4Res, errQos4 := qos4.ComputeQoSExcellence() + require.NoError(t, errQos1) + require.NoError(t, errQos2) + require.NoError(t, errQos3) + require.NoError(t, errQos4) + require.True(t, qos1Res.LT(qos2Res)) + require.True(t, qos1Res.LT(qos3Res)) + require.True(t, qos1Res.LT(qos4Res)) + + require.True(t, qos2Res.GT(qos3Res)) + require.True(t, qos2Res.GT(qos4Res)) + + require.True(t, qos4Res.LT(qos3Res)) +} diff --git a/x/protocol/types/params.go b/x/protocol/types/params.go index bc409281e8..1ae9668e14 100644 --- a/x/protocol/types/params.go +++ b/x/protocol/types/params.go @@ -12,7 +12,7 @@ import ( var _ paramtypes.ParamSet = (*Params)(nil) const ( - TARGET_VERSION = "0.32.4" + TARGET_VERSION = "0.32.5" MIN_VERSION = "0.30.1" ) From 5d4c39f878dcc9e189c30c6515277ad934b98e01 Mon Sep 17 00:00:00 2001 From: Ran Mishael <106548467+ranlavanet@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:56:10 +0100 Subject: [PATCH 49/52] PRT-1040-add-policy-updater (#1058) * moving all updaters to its own package for better visibility * Implementing policy Updater * adding logs for better debugging and info * WIP - policy updater * change policy updater to use epoch updates created by pairing updater. * WIP test local * removing event from projects as its no longer used, * fix init_e2e race condition and invalid behavior * better stability * adding more info to debug * test adjustment * fix e2e * fix * fix e2e adding another block wait because of begin block event order * adding delay because github action is so slow. * fix a bug related to qos excellence report. * fix optimizer race condition on unitests * upgrade lavajs to v0.32.4 * increase protocol patch version to 32.5 * using rlock instead * fix division of zero * fix lint * fixed race in init_e2e * changed sync score to one instead of 0 in excellence due to 0 being ideal value and 1 being 0 contribution * fix all comments * fix warning --- protocol/chainlib/base_chain_parser.go | 72 ++++++++++++--- protocol/chainlib/chainlib.go | 3 +- protocol/chainlib/grpc.go | 2 +- protocol/chainlib/jsonRPC.go | 2 +- protocol/chainlib/jsonRPC_test.go | 3 +- protocol/chainlib/rest.go | 2 +- protocol/chainlib/tendermintRPC.go | 2 +- protocol/rpcconsumer/rpcconsumer.go | 18 ++++ protocol/rpcconsumer/rpcconsumer_server.go | 30 +------ protocol/statetracker/tx_sender.go | 1 + .../statetracker/updaters/event_tracker.go | 9 +- .../statetracker/updaters/policy_updater.go | 87 +++++++++++++++++++ .../statetracker/updaters/spec_updater.go | 9 +- .../pre_setups/init_evmos_only_with_node.sh | 8 +- .../init_lava_only_with_node_with_cache.sh | 3 +- x/projects/keeper/project.go | 1 - 16 files changed, 201 insertions(+), 51 deletions(-) create mode 100644 protocol/statetracker/updaters/policy_updater.go diff --git a/protocol/chainlib/base_chain_parser.go b/protocol/chainlib/base_chain_parser.go index f2be77602d..f9caafaf8e 100644 --- a/protocol/chainlib/base_chain_parser.go +++ b/protocol/chainlib/base_chain_parser.go @@ -10,6 +10,7 @@ import ( "github.com/lavanet/lava/protocol/chainlib/extensionslib" "github.com/lavanet/lava/utils" pairingtypes "github.com/lavanet/lava/x/pairing/types" + plantypes "github.com/lavanet/lava/x/plans/types" spectypes "github.com/lavanet/lava/x/spec/types" ) @@ -21,7 +22,7 @@ type BaseChainParser struct { apiCollections map[CollectionKey]*spectypes.ApiCollection headers map[ApiKey]*spectypes.Header verifications map[VerificationKey][]VerificationContainer - allowedAddons map[string]struct{} + allowedAddons map[string]bool extensionParser extensionslib.ExtensionParser active bool } @@ -78,23 +79,68 @@ func (bcp *BaseChainParser) isExtension(extension string) bool { return bcp.extensionParser.AllowedExtension(extension) } -func (bcp *BaseChainParser) SetConfiguredExtensions(extensions map[string]struct{}) error { - bcp.rwLock.Lock() - defer bcp.rwLock.Unlock() - for extension := range extensions { - if !bcp.isExtension(extension) { - return utils.LavaFormatError("configured extensions contain an extension that is not allowed in the spec", nil, utils.Attribute{Key: "spec", Value: bcp.spec.Index}, utils.Attribute{Key: "configured extensions", Value: extensions}, utils.Attribute{Key: "allowed extensions", Value: bcp.extensionParser.AllowedExtensions}) +// use while bcp locked. +func (bcp *BaseChainParser) validateAddons(nodeMessage *baseChainMessageContainer) error { + var addon string + if addon = GetAddon(nodeMessage); addon != "" { // check we have an addon + if allowed := bcp.allowedAddons[addon]; !allowed { // check addon is allowed + return utils.LavaFormatError("consumer policy does not allow addon", nil, + utils.LogAttr("addon", addon), + ) } } + // no addons to validate or validation completed successfully + return nil +} + +func (bcp *BaseChainParser) Validate(nodeMessage *baseChainMessageContainer) error { + bcp.rwLock.RLock() + defer bcp.rwLock.RUnlock() + err := bcp.validateAddons(nodeMessage) + // add more validations in the future here. + return err +} + +func (bcp *BaseChainParser) BuildMapFromPolicyQuery(policy *plantypes.Policy, chainId string, apiInterface string) (map[string]struct{}, error) { + addons, err := policy.GetSupportedAddons(chainId) + if err != nil { + return nil, err + } + extensions, err := policy.GetSupportedExtensions(chainId) + if err != nil { + return nil, err + } + services := make(map[string]struct{}) + for _, addon := range addons { + services[addon] = struct{}{} + } + for _, consumerExtension := range extensions { + // store only relevant apiInterface extensions + if consumerExtension.ApiInterface == apiInterface { + services[consumerExtension.Extension] = struct{}{} + } + } + return services, nil +} + +// policy information contains all configured services (extensions and addons) allowed to be used by the consumer +func (bcp *BaseChainParser) SetPolicy(policy *plantypes.Policy, chainId string, apiInterface string) error { + policyInformation, err := bcp.BuildMapFromPolicyQuery(policy, chainId, apiInterface) + if err != nil { + return err + } + bcp.rwLock.Lock() + defer bcp.rwLock.Unlock() // reset the current one in case we configured it previously configuredExtensions := make(map[extensionslib.ExtensionKey]*spectypes.Extension) for collectionKey, apiCollection := range bcp.apiCollections { + // manage extensions for _, extension := range apiCollection.Extensions { if extension.Name == "" { // skip empty extensions continue } - if _, ok := extensions[extension.Name]; ok { + if _, ok := policyInformation[extension.Name]; ok { extensionKey := extensionslib.ExtensionKey{ Extension: extension.Name, ConnectionType: collectionKey.ConnectionType, @@ -106,6 +152,12 @@ func (bcp *BaseChainParser) SetConfiguredExtensions(extensions map[string]struct } } bcp.extensionParser.SetConfiguredExtensions(configuredExtensions) + // manage allowed addons + for addon := range bcp.allowedAddons { + if _, ok := policyInformation[addon]; ok { + bcp.allowedAddons[addon] = true + } + } return nil } @@ -170,13 +222,13 @@ func (bcp *BaseChainParser) Construct(spec spectypes.Spec, taggedApis map[specty bcp.headers = headers bcp.apiCollections = apiCollections bcp.verifications = verifications - allowedAddons := map[string]struct{}{} + allowedAddons := map[string]bool{} allowedExtensions := map[string]struct{}{} for _, apoCollection := range apiCollections { for _, extension := range apoCollection.Extensions { allowedExtensions[extension.Name] = struct{}{} } - allowedAddons[apoCollection.CollectionData.AddOn] = struct{}{} + allowedAddons[apoCollection.CollectionData.AddOn] = false } bcp.allowedAddons = allowedAddons bcp.extensionParser = extensionslib.ExtensionParser{AllowedExtensions: allowedExtensions} diff --git a/protocol/chainlib/chainlib.go b/protocol/chainlib/chainlib.go index 436dca3cc4..5ce8e35ee5 100644 --- a/protocol/chainlib/chainlib.go +++ b/protocol/chainlib/chainlib.go @@ -12,6 +12,7 @@ import ( "github.com/lavanet/lava/protocol/lavasession" "github.com/lavanet/lava/protocol/metrics" pairingtypes "github.com/lavanet/lava/x/pairing/types" + plantypes "github.com/lavanet/lava/x/plans/types" spectypes "github.com/lavanet/lava/x/spec/types" ) @@ -59,7 +60,7 @@ type ChainParser interface { HandleHeaders(metadata []pairingtypes.Metadata, apiCollection *spectypes.ApiCollection, headersDirection spectypes.Header_HeaderType) (filtered []pairingtypes.Metadata, overwriteReqBlock string, ignoredMetadata []pairingtypes.Metadata) GetVerifications(supported []string) ([]VerificationContainer, error) SeparateAddonsExtensions(supported []string) (addons, extensions []string, err error) - SetConfiguredExtensions(extensions map[string]struct{}) error + SetPolicy(policy *plantypes.Policy, chainId string, apiInterface string) error Active() bool Activate() UpdateBlockTime(newBlockTime time.Duration) diff --git a/protocol/chainlib/grpc.go b/protocol/chainlib/grpc.go index 0445def4e6..200c0216df 100644 --- a/protocol/chainlib/grpc.go +++ b/protocol/chainlib/grpc.go @@ -185,7 +185,7 @@ func (apip *GrpcChainParser) ParseMsg(url string, data []byte, connectionType st nodeMsg := apip.newChainMessage(apiCont.api, requestedBlock, &grpcMessage, apiCollection) apip.BaseChainParser.ExtensionParsing(apiCollection.CollectionData.AddOn, nodeMsg, latestBlock) - return nodeMsg, nil + return nodeMsg, apip.BaseChainParser.Validate(nodeMsg) } func (*GrpcChainParser) newChainMessage(api *spectypes.Api, requestedBlock int64, grpcMessage *rpcInterfaceMessages.GrpcMessage, apiCollection *spectypes.ApiCollection) *baseChainMessageContainer { diff --git a/protocol/chainlib/jsonRPC.go b/protocol/chainlib/jsonRPC.go index 6cda46d3b4..aaf4b682a5 100644 --- a/protocol/chainlib/jsonRPC.go +++ b/protocol/chainlib/jsonRPC.go @@ -184,7 +184,7 @@ func (apip *JsonRPCChainParser) ParseMsg(url string, data []byte, connectionType } } apip.BaseChainParser.ExtensionParsing(apiCollection.CollectionData.AddOn, nodeMsg, latestBlock) - return nodeMsg, nil + return nodeMsg, apip.BaseChainParser.Validate(nodeMsg) } func (*JsonRPCChainParser) newBatchChainMessage(serviceApi *spectypes.Api, requestedBlock int64, earliestRequestedBlock int64, msgs []rpcInterfaceMessages.JsonrpcMessage, apiCollection *spectypes.ApiCollection) (*baseChainMessageContainer, error) { diff --git a/protocol/chainlib/jsonRPC_test.go b/protocol/chainlib/jsonRPC_test.go index e7d9f78e1b..a886445321 100644 --- a/protocol/chainlib/jsonRPC_test.go +++ b/protocol/chainlib/jsonRPC_test.go @@ -11,6 +11,7 @@ import ( "github.com/lavanet/lava/protocol/chainlib/chainproxy/rpcInterfaceMessages" keepertest "github.com/lavanet/lava/testutil/keeper" + plantypes "github.com/lavanet/lava/x/plans/types" spectypes "github.com/lavanet/lava/x/spec/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -206,7 +207,7 @@ func TestExtensions(t *testing.T) { spec, err := keepertest.GetASpec(specname, "../../", nil, nil) require.NoError(t, err) - chainParser.SetConfiguredExtensions(configuredExtensions) + chainParser.SetPolicy(&plantypes.Policy{ChainPolicies: []plantypes.ChainPolicy{{ChainId: specname, Requirements: []plantypes.ChainRequirement{{Collection: spectypes.CollectionData{ApiInterface: "jsonrpc"}, Extensions: []string{"archive"}}}}}}, specname, "jsonrpc") parsingForCrafting, collectionData, ok := chainParser.GetParsingByTag(spectypes.FUNCTION_TAG_GET_BLOCK_BY_NUM) require.True(t, ok) cuCost := uint64(0) diff --git a/protocol/chainlib/rest.go b/protocol/chainlib/rest.go index 5137019223..bc81477289 100644 --- a/protocol/chainlib/rest.go +++ b/protocol/chainlib/rest.go @@ -131,7 +131,7 @@ func (apip *RestChainParser) ParseMsg(urlPath string, data []byte, connectionTyp nodeMsg := apip.newChainMessage(apiCont.api, requestedBlock, &restMessage, apiCollection) apip.BaseChainParser.ExtensionParsing(apiCollection.CollectionData.AddOn, nodeMsg, latestBlock) - return nodeMsg, nil + return nodeMsg, apip.BaseChainParser.Validate(nodeMsg) } func (*RestChainParser) newChainMessage(serviceApi *spectypes.Api, requestBlock int64, restMessage *rpcInterfaceMessages.RestMessage, apiCollection *spectypes.ApiCollection) *baseChainMessageContainer { diff --git a/protocol/chainlib/tendermintRPC.go b/protocol/chainlib/tendermintRPC.go index 9ba5b2cd3c..b2db6eb535 100644 --- a/protocol/chainlib/tendermintRPC.go +++ b/protocol/chainlib/tendermintRPC.go @@ -222,7 +222,7 @@ func (apip *TendermintChainParser) ParseMsg(urlPath string, data []byte, connect } apip.BaseChainParser.ExtensionParsing(apiCollection.CollectionData.AddOn, nodeMsg, latestBlock) - return nodeMsg, nil + return nodeMsg, apip.BaseChainParser.Validate(nodeMsg) } func (*TendermintChainParser) newBatchChainMessage(serviceApi *spectypes.Api, requestedBlock int64, earliestRequestedBlock int64, msgs []rpcInterfaceMessages.JsonrpcMessage, apiCollection *spectypes.ApiCollection) (*baseChainMessageContainer, error) { diff --git a/protocol/rpcconsumer/rpcconsumer.go b/protocol/rpcconsumer/rpcconsumer.go index 00df175a16..236dd104ea 100644 --- a/protocol/rpcconsumer/rpcconsumer.go +++ b/protocol/rpcconsumer/rpcconsumer.go @@ -159,6 +159,7 @@ func (rpcc *RPCConsumer) Start(ctx context.Context, txFactory tx.Factory, client utils.LavaFormatFatal("failed fetching protocol version from node", err) } consumerStateTracker.RegisterForVersionUpdates(ctx, version.Version, &upgrade.ProtocolVersion{}) + policyUpdaters := make(map[string]*updaters.PolicyUpdater) // per chainId we have one policy updater for _, rpcEndpoint := range rpcEndpoints { go func(rpcEndpoint *lavasession.RPCEndpoint) error { @@ -170,12 +171,24 @@ func (rpcc *RPCConsumer) Start(ctx context.Context, txFactory tx.Factory, client return err } chainID := rpcEndpoint.ChainID + // create policyUpdaters per chain + if policyUpdater, ok := policyUpdaters[rpcEndpoint.ChainID]; ok { + err := policyUpdater.AddPolicySetter(chainParser, *rpcEndpoint) + if err != nil { + errCh <- err + return utils.LavaFormatError("failed adding policy setter", err) + } + } else { + policyUpdaters[rpcEndpoint.ChainID] = updaters.NewPolicyUpdater(chainID, consumerStateTracker, consumerAddr.String(), chainParser, *rpcEndpoint) + } + // register for spec updates err = rpcc.consumerStateTracker.RegisterForSpecUpdates(ctx, chainParser, *rpcEndpoint) if err != nil { err = utils.LavaFormatError("failed registering for spec updates", err, utils.Attribute{Key: "endpoint", Value: rpcEndpoint}) errCh <- err return err } + _, averageBlockTime, _, _ := chainParser.ChainBlockStats() var optimizer *provideroptimizer.ProviderOptimizer var consumerConsistency *ConsumerConsistency @@ -263,6 +276,11 @@ func (rpcc *RPCConsumer) Start(ctx context.Context, txFactory tx.Factory, client return err } + utils.LavaFormatDebug("Starting Policy Updaters for all chains") + for _, policyUpdater := range policyUpdaters { + consumerStateTracker.RegisterForPairingUpdates(ctx, policyUpdater) + } + utils.LavaFormatInfo("RPCConsumer done setting up all endpoints, ready for requests") signalChan := make(chan os.Signal, 1) diff --git a/protocol/rpcconsumer/rpcconsumer_server.go b/protocol/rpcconsumer/rpcconsumer_server.go index c456ec29eb..f5455c2443 100644 --- a/protocol/rpcconsumer/rpcconsumer_server.go +++ b/protocol/rpcconsumer/rpcconsumer_server.go @@ -81,29 +81,7 @@ func (rpccs *RPCConsumerServer) ServeRPCRequests(ctx context.Context, listenEndp rpccs.finalizationConsensus = finalizationConsensus rpccs.consumerAddress = consumerAddress rpccs.consumerConsistency = consumerConsistency - consumerPolicy, err := rpccs.consumerTxSender.GetConsumerPolicy(ctx, consumerAddress.String(), listenEndpoint.ChainID) - if err != nil { - return err - } - consumerAddons, err := consumerPolicy.GetSupportedAddons(listenEndpoint.ChainID) - if err != nil { - return err - } - consumerExtensions, err := consumerPolicy.GetSupportedExtensions(listenEndpoint.ChainID) - if err != nil { - return err - } - rpccs.consumerServices = make(map[string]struct{}) - for _, consumerAddon := range consumerAddons { - rpccs.consumerServices[consumerAddon] = struct{}{} - } - for _, consumerExtension := range consumerExtensions { - // store only relevant apiInterface extensions - if consumerExtension.ApiInterface == listenEndpoint.ApiInterface { - rpccs.consumerServices[consumerExtension.Extension] = struct{}{} - } - } - rpccs.chainParser.SetConfiguredExtensions(rpccs.consumerServices) // configure possible extensions as set by the policy + chainListener, err := chainlib.NewChainListener(ctx, listenEndpoint, rpccs, rpcConsumerLogs, chainParser) if err != nil { return err @@ -213,12 +191,6 @@ func (rpccs *RPCConsumerServer) SendRelay( } rpccs.HandleDirectiveHeadersForMessage(chainMessage, directiveHeaders) - if _, ok := rpccs.consumerServices[chainlib.GetAddon(chainMessage)]; !ok { - utils.LavaFormatError("unsupported addon usage, consumer policy does not allow", nil, - utils.Attribute{Key: "addon", Value: chainlib.GetAddon(chainMessage)}, - utils.Attribute{Key: "allowed", Value: rpccs.consumerServices}, - ) - } // do this in a loop with retry attempts, configurable via a flag, limited by the number of providers in CSM reqBlock, _ := chainMessage.RequestedBlock() seenBlock, _ := rpccs.consumerConsistency.GetSeenBlock(dappID, consumerIp) diff --git a/protocol/statetracker/tx_sender.go b/protocol/statetracker/tx_sender.go index a95ed0bfa4..df6eeab561 100644 --- a/protocol/statetracker/tx_sender.go +++ b/protocol/statetracker/tx_sender.go @@ -237,6 +237,7 @@ func (ts *TxSender) waitForTxCommit(resultData common.TxResultData) (common.TxRe Txhash: resultData.Txhash, Code: int(txRes.TxResult.Code), } + utils.LavaFormatDebug("Tx Hash found on blockchain", utils.LogAttr("Txhash", string(resultData.Txhash)), utils.LogAttr("Code", resultData.Code)) break case <-time.After(5 * time.Minute): return common.TxResultData{}, utils.LavaFormatError("failed sending tx, wasn't found after timeout", nil, utils.Attribute{Key: "hash", Value: string(resultData.Txhash)}) diff --git a/protocol/statetracker/updaters/event_tracker.go b/protocol/statetracker/updaters/event_tracker.go index 29180d2174..2609ef932b 100644 --- a/protocol/statetracker/updaters/event_tracker.go +++ b/protocol/statetracker/updaters/event_tracker.go @@ -6,6 +6,8 @@ import ( "sync" "time" + "golang.org/x/exp/slices" + ctypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" "github.com/lavanet/lava/protocol/rpcprovider/reliabilitymanager" @@ -141,8 +143,13 @@ func (et *EventTracker) getLatestSpecModifyEvents(latestBlock int64) (updated bo if et.latestUpdatedBlock != latestBlock { return false, utils.LavaFormatWarning("event results are different than expected", nil, utils.Attribute{Key: "requested latestBlock", Value: latestBlock}, utils.Attribute{Key: "current latestBlock", Value: et.latestUpdatedBlock}) } + eventsListToListenTo := []string{ + utils.EventPrefix + spectypes.SpecModifyEventName, + utils.EventPrefix + spectypes.SpecRefreshEventName, + } for _, event := range et.blockResults.EndBlockEvents { - if event.Type == utils.EventPrefix+spectypes.SpecModifyEventName { + if slices.Contains(eventsListToListenTo, event.Type) { + utils.LavaFormatInfo("Spec update event identified", utils.LogAttr("Event", event.Type)) return true, nil } } diff --git a/protocol/statetracker/updaters/policy_updater.go b/protocol/statetracker/updaters/policy_updater.go new file mode 100644 index 0000000000..b4e8785bcf --- /dev/null +++ b/protocol/statetracker/updaters/policy_updater.go @@ -0,0 +1,87 @@ +package updaters + +import ( + "context" + "sync" + "time" + + "github.com/lavanet/lava/protocol/lavasession" + "github.com/lavanet/lava/utils" + plantypes "github.com/lavanet/lava/x/plans/types" +) + +const ( + CallbackKeyForPolicyUpdate = "policy-update" +) + +type PolicySetter interface { + SetPolicy(policy *plantypes.Policy, chainId string, apiInterface string) error +} + +type PolicyFetcher interface { + GetConsumerPolicy(ctx context.Context, consumerAddress, chainID string) (*plantypes.Policy, error) +} + +type PolicyUpdater struct { + lock sync.RWMutex + chainId string + consumerAddress string + lastTimeUpdatedPolicy uint64 + policyFetcher PolicyFetcher + policyUpdatables map[string]PolicySetter // key is apiInterface. +} + +func NewPolicyUpdater(chainId string, policyFetcher PolicyFetcher, consumerAddress string, policyUpdatable PolicySetter, endpoint lavasession.RPCEndpoint) *PolicyUpdater { + return &PolicyUpdater{ + chainId: chainId, + policyFetcher: policyFetcher, + policyUpdatables: map[string]PolicySetter{endpoint.ApiInterface: policyUpdatable}, + consumerAddress: consumerAddress, + lastTimeUpdatedPolicy: 0, + } +} + +func (pu *PolicyUpdater) AddPolicySetter(policyUpdatable PolicySetter, endpoint lavasession.RPCEndpoint) error { + pu.lock.Lock() + defer pu.lock.Unlock() + existingPolicySetter, found := pu.policyUpdatables[endpoint.ApiInterface] + if found { + return utils.LavaFormatError("Trying to register to policy updates on already registered api interface", nil, + utils.Attribute{Key: "endpoint", Value: endpoint}, + utils.Attribute{Key: "policyUpdatable", Value: existingPolicySetter}) + } + pu.policyUpdatables[endpoint.ApiInterface] = policyUpdatable + return nil +} + +func (pu *PolicyUpdater) UpdaterKey() string { + return CallbackKeyForPolicyUpdate + pu.chainId +} + +func (pu *PolicyUpdater) setPolicy(policyUpdatable PolicySetter, policy *plantypes.Policy, apiInterface string) error { + err := policyUpdatable.SetPolicy(policy, pu.chainId, apiInterface) + if err != nil { + return utils.LavaFormatError("panic level error, failed setting policy on policy updatable", err, utils.LogAttr("chainId", pu.chainId), utils.LogAttr("api_interface", apiInterface)) + } + return nil +} + +func (pu *PolicyUpdater) UpdateEpoch(epoch uint64) { + pu.lock.Lock() + defer pu.lock.Unlock() + // update policy now + utils.LavaFormatDebug("PolicyUpdater, fetching current policy and updating the effective policy", utils.LogAttr("epoch", epoch), utils.LogAttr("chainId", pu.chainId)) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + policy, err := pu.policyFetcher.GetConsumerPolicy(ctx, pu.consumerAddress, pu.chainId) + if err != nil { + utils.LavaFormatError("could not get GetConsumerPolicy updated, did not update policy", err, utils.LogAttr("epoch", epoch)) + return + } + for apiInterface, policyUpdatable := range pu.policyUpdatables { + err = pu.setPolicy(policyUpdatable, policy, apiInterface) + if err != nil { + utils.LavaFormatError("Failed Updating policy", err, utils.LogAttr("apiInterface", apiInterface), utils.LogAttr("chainId", pu.chainId)) + } + } +} diff --git a/protocol/statetracker/updaters/spec_updater.go b/protocol/statetracker/updaters/spec_updater.go index 721774000b..b6dfc26459 100644 --- a/protocol/statetracker/updaters/spec_updater.go +++ b/protocol/statetracker/updaters/spec_updater.go @@ -4,6 +4,7 @@ import ( "context" "strings" "sync" + "sync/atomic" "github.com/lavanet/lava/protocol/lavasession" "github.com/lavanet/lava/utils" @@ -35,7 +36,7 @@ type SpecUpdater struct { chainId string specGetter SpecGetter blockLastUpdated uint64 - specUpdatables map[string]*SpecUpdatable + specUpdatables map[string]*SpecUpdatable // key is api interface specVerifiers map[string]*SpecVerifier spec *spectypes.Spec } @@ -118,6 +119,10 @@ func (su *SpecUpdater) RegisterSpecVerifier(ctx context.Context, specVerifier *S return nil } +func (su *SpecUpdater) setBlockLastUpdatedAtomically(block uint64) { + atomic.StoreUint64(&su.blockLastUpdated, block) +} + func (su *SpecUpdater) Update(latestBlock int64) { su.lock.RLock() defer su.lock.RUnlock() @@ -129,7 +134,7 @@ func (su *SpecUpdater) Update(latestBlock int64) { return } if spec.BlockLastUpdated > su.blockLastUpdated { - su.blockLastUpdated = spec.BlockLastUpdated + su.setBlockLastUpdatedAtomically(spec.BlockLastUpdated) } for _, specUpdatable := range su.specUpdatables { utils.LavaFormatDebug("SpecUpdater: updating spec for chainId", diff --git a/scripts/pre_setups/init_evmos_only_with_node.sh b/scripts/pre_setups/init_evmos_only_with_node.sh index 2d581f40c0..4773e59830 100755 --- a/scripts/pre_setups/init_evmos_only_with_node.sh +++ b/scripts/pre_setups/init_evmos_only_with_node.sh @@ -44,6 +44,7 @@ lavad tx subscription buy DefaultPlan $(lavad keys show user1 -a) -y --from user wait_next_block lavad tx pairing stake-provider "EVMOS" $PROVIDERSTAKE "$PROVIDER1_LISTENER,1" 1 -y --from servicer1 --provider-moniker "dummyMoniker" --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE +# lavad tx project set-policy $(lavad keys show user1 -a)-admin ./cookbook/projects/policy_all_chains_with_addon.yml -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE sleep_until_next_epoch screen -d -m -S provider1 bash -c "source ~/.bashrc; lavap rpcprovider \ @@ -58,4 +59,9 @@ screen -d -m -S consumers bash -c "source ~/.bashrc; lavap rpcconsumer \ $EXTRA_PORTAL_FLAGS --geolocation 1 --log_level debug --from user1 --chain-id lava --allow-insecure-provider-dialing --metrics-listen-address ":7779" 2>&1 | tee $LOGS_DIR/CONSUMERS.log" && sleep 0.25 echo "--- setting up screens done ---" -screen -ls \ No newline at end of file +screen -ls + +# sleep 30 +# lavad tx project set-policy $(lavad keys show user1 -a)-admin ./cookbook/projects/policy_all_chains_with_extension.yml -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE +# sleep 30 +# lavad tx project set-policy $(lavad keys show user1 -a)-admin ./cookbook/projects/policy_all_chains_with_addon.yml -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE diff --git a/scripts/pre_setups/init_lava_only_with_node_with_cache.sh b/scripts/pre_setups/init_lava_only_with_node_with_cache.sh index fa6b5b3c3d..a172f1c8e8 100755 --- a/scripts/pre_setups/init_lava_only_with_node_with_cache.sh +++ b/scripts/pre_setups/init_lava_only_with_node_with_cache.sh @@ -60,4 +60,5 @@ screen -d -m -S consumers bash -c "source ~/.bashrc; lavap rpcconsumer \ $EXTRA_PORTAL_FLAGS --geolocation 1 --log_level debug --cache-be 127.0.0.1:20100 --from user1 --chain-id lava --allow-insecure-provider-dialing 2>&1 | tee $LOGS_DIR/CONSUMERS.log" && sleep 0.25 echo "--- setting up screens done ---" -screen -ls \ No newline at end of file +screen -ls + diff --git a/x/projects/keeper/project.go b/x/projects/keeper/project.go index 88a33c8e60..63ca7f7b65 100644 --- a/x/projects/keeper/project.go +++ b/x/projects/keeper/project.go @@ -327,7 +327,6 @@ func (k Keeper) SetProjectPolicy(ctx sdk.Context, projectIDs []string, policy *p ) } } - return nil } From 9abb2cf25ac995541b6da1a35c940ff3eda38a8d Mon Sep 17 00:00:00 2001 From: Omer <100387053+omerlavanet@users.noreply.github.com> Date: Sun, 31 Dec 2023 20:47:28 +0200 Subject: [PATCH 50/52] fix concurrency (#1090) --- protocol/rpcconsumer/policies_map.go | 28 ++++++++++++++++++++++++++++ protocol/rpcconsumer/rpcconsumer.go | 13 +++++++++---- x/protocol/types/params.go | 2 +- 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 protocol/rpcconsumer/policies_map.go diff --git a/protocol/rpcconsumer/policies_map.go b/protocol/rpcconsumer/policies_map.go new file mode 100644 index 0000000000..1dca055a12 --- /dev/null +++ b/protocol/rpcconsumer/policies_map.go @@ -0,0 +1,28 @@ +package rpcconsumer + +import ( + "sync" + + "github.com/lavanet/lava/protocol/statetracker/updaters" + "github.com/lavanet/lava/utils" +) + +type syncMapPolicyUpdaters struct { + localMap sync.Map +} + +func (sm *syncMapPolicyUpdaters) Store(key string, toSet *updaters.PolicyUpdater) { + sm.localMap.Store(key, toSet) +} + +func (sm *syncMapPolicyUpdaters) Load(key string) (ret *updaters.PolicyUpdater, ok bool) { + value, ok := sm.localMap.Load(key) + if !ok { + return nil, ok + } + ret, ok = value.(*updaters.PolicyUpdater) + if !ok { + utils.LavaFormatFatal("invalid usage of syncmap, could not cast result into a PolicyUpdater", nil) + } + return ret, true +} diff --git a/protocol/rpcconsumer/rpcconsumer.go b/protocol/rpcconsumer/rpcconsumer.go index 236dd104ea..16c98d564a 100644 --- a/protocol/rpcconsumer/rpcconsumer.go +++ b/protocol/rpcconsumer/rpcconsumer.go @@ -159,8 +159,8 @@ func (rpcc *RPCConsumer) Start(ctx context.Context, txFactory tx.Factory, client utils.LavaFormatFatal("failed fetching protocol version from node", err) } consumerStateTracker.RegisterForVersionUpdates(ctx, version.Version, &upgrade.ProtocolVersion{}) - policyUpdaters := make(map[string]*updaters.PolicyUpdater) // per chainId we have one policy updater + policyUpdaters := syncMapPolicyUpdaters{} for _, rpcEndpoint := range rpcEndpoints { go func(rpcEndpoint *lavasession.RPCEndpoint) error { defer wg.Done() @@ -172,14 +172,14 @@ func (rpcc *RPCConsumer) Start(ctx context.Context, txFactory tx.Factory, client } chainID := rpcEndpoint.ChainID // create policyUpdaters per chain - if policyUpdater, ok := policyUpdaters[rpcEndpoint.ChainID]; ok { + if policyUpdater, ok := policyUpdaters.Load(rpcEndpoint.ChainID); ok { err := policyUpdater.AddPolicySetter(chainParser, *rpcEndpoint) if err != nil { errCh <- err return utils.LavaFormatError("failed adding policy setter", err) } } else { - policyUpdaters[rpcEndpoint.ChainID] = updaters.NewPolicyUpdater(chainID, consumerStateTracker, consumerAddr.String(), chainParser, *rpcEndpoint) + policyUpdaters.Store(rpcEndpoint.ChainID, updaters.NewPolicyUpdater(chainID, consumerStateTracker, consumerAddr.String(), chainParser, *rpcEndpoint)) } // register for spec updates err = rpcc.consumerStateTracker.RegisterForSpecUpdates(ctx, chainParser, *rpcEndpoint) @@ -277,7 +277,12 @@ func (rpcc *RPCConsumer) Start(ctx context.Context, txFactory tx.Factory, client } utils.LavaFormatDebug("Starting Policy Updaters for all chains") - for _, policyUpdater := range policyUpdaters { + for chain := range chainMutexes { + policyUpdater, ok := policyUpdaters.Load(chain) + if !ok { + utils.LavaFormatError("could not load policy Updater for chain", nil, utils.LogAttr("chain", chain)) + continue + } consumerStateTracker.RegisterForPairingUpdates(ctx, policyUpdater) } diff --git a/x/protocol/types/params.go b/x/protocol/types/params.go index 1ae9668e14..f8092a9fbc 100644 --- a/x/protocol/types/params.go +++ b/x/protocol/types/params.go @@ -12,7 +12,7 @@ import ( var _ paramtypes.ParamSet = (*Params)(nil) const ( - TARGET_VERSION = "0.32.5" + TARGET_VERSION = "0.32.6" MIN_VERSION = "0.30.1" ) From 1898ee2ce8245519bcd5d0d2b24d8bba5d5e04f0 Mon Sep 17 00:00:00 2001 From: Elad Gildnur Date: Tue, 2 Jan 2024 08:05:45 -0500 Subject: [PATCH 51/52] CR Fix: More on the CU Tracker --- x/subscription/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/subscription/README.md b/x/subscription/README.md index dfe87f2b28..25c851908a 100644 --- a/x/subscription/README.md +++ b/x/subscription/README.md @@ -90,7 +90,8 @@ One of the primary processes within this module involves the function `advanceMo The advanceMonth function plays a crucial role in the subscription lifecycle, performing several important tasks: 1. **CU Tracker Timer Update**: It updates the CU tracker timer, ensuring accurate tracking of Compute Units (CUs) for the subscription. - For more information on the CU tracker, please refer to the [Pairing module](https://github.com/lavanet/lava/blob/main/x/pairing/README.md). + The CU tracker timer callback is responsible for paying the providers, and resetting the CU tracker data. + For more information on the CU tracker, please refer to the [Pairing module](https://github.com/lavanet/lava/blob/main/x/pairing/README.md#cu-tracking). 2. **Subscription Duration Management**: The function checks the subscription's remaining duration. If there is time left, it deducts one month from the remaining duration and updates the total duration count, reflecting the passage of time. The details of the subscription, including CU allocations, are reset for the upcoming month. In cases where the subscription has reached its end (no more remaining months), different scenarios are addressed: From 0228c06acc67ad1d9685b613676934d625040e17 Mon Sep 17 00:00:00 2001 From: oren-lava <111131399+oren-lava@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:46:47 +0200 Subject: [PATCH 52/52] CNS-796: Agoric spec (#1092) * CNS-796: agoric spec * CNS-796: fix unit test --- .../full_consumer_example.yml | 6 + cookbook/specs/spec_add_agoric.json | 444 ++++++++++++++++++ scripts/init_chain_commands.sh | 4 +- scripts/setup_providers.sh | 2 + x/rewards/keeper/pool_test.go | 8 +- 5 files changed, 458 insertions(+), 6 deletions(-) create mode 100644 cookbook/specs/spec_add_agoric.json diff --git a/config/consumer_examples/full_consumer_example.yml b/config/consumer_examples/full_consumer_example.yml index 30fbb1e67b..f6b0cd6cc7 100644 --- a/config/consumer_examples/full_consumer_example.yml +++ b/config/consumer_examples/full_consumer_example.yml @@ -128,4 +128,10 @@ endpoints: - chain-id: NEAR api-interface: jsonrpc network-address: 127.0.0.1:3385 + - chain-id: AGR + api-interface: rest + network-address: 127.0.0.1:3386 + - chain-id: AGR + api-interface: grpc + network-address: 127.0.0.1:3387 metrics-listen-address: ":7779" diff --git a/cookbook/specs/spec_add_agoric.json b/cookbook/specs/spec_add_agoric.json new file mode 100644 index 0000000000..f059cf7db7 --- /dev/null +++ b/cookbook/specs/spec_add_agoric.json @@ -0,0 +1,444 @@ +{ + "proposal": { + "title": "Add Specs: Agoric", + "description": "Adding new specification support for relaying agoric data on Lava", + "specs": [ + { + "index": "AGR", + "name": "agoric mainnet", + "enabled": true, + "imports": [ + "COSMOSSDK", + "COSMOSSDK45DEP" + ], + "reliability_threshold": 268435455, + "data_reliability_enabled": true, + "block_distance_for_finalized_data": 0, + "blocks_in_finalization_proof": 1, + "average_block_time": 5000, + "allowed_block_lag_for_qos_sync": 2, + "shares" : 1, + "min_stake_provider": { + "denom": "ulava", + "amount": "50000000000" + }, + "api_collections": [ + { + "enabled": true, + "collection_data": { + "api_interface": "rest", + "internal_path": "", + "type": "GET", + "add_on": "" + }, + "apis": [ + { + "name": "/agoric/swingset/egress/{peer}", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "/agoric/swingset/mailbox/{peer}", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "/agoric/swingset/params", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "/agoric/vbank/params", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "/agoric/vbank/state", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "/agoric/vstorage/children/{path}", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "/agoric/vstorage/data/{path}", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + } + ], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "agoric-3" + } + ] + } + ] + }, + { + "enabled": true, + "collection_data": { + "api_interface": "grpc", + "internal_path": "", + "type": "", + "add_on": "" + }, + "apis": [ + { + "name": "agoric.swingset.Query/Egress", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "agoric.swingset.Query/Mailbox", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "agoric.swingset.Query/Params", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "agoric.vbank.Query/Params", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "agoric.vbank.Query/State", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "agoric.vstorage.Query/Children", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + }, + { + "name": "agoric.vstorage.Query/Data", + "block_parsing": { + "parser_arg": [ + "latest" + ], + "parser_func": "DEFAULT" + }, + "compute_units": 10, + "enabled": true, + "category": { + "deterministic": true, + "local": false, + "subscription": false, + "stateful": 0 + }, + "extra_compute_units": 0 + } + ], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "agoric-3" + } + ] + } + ] + }, + { + "enabled": true, + "collection_data": { + "api_interface": "tendermintrpc", + "internal_path": "", + "type": "", + "add_on": "" + }, + "apis": [], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "agoric-3" + } + ] + } + ] + } + ] + }, + { + "index": "AGRT", + "name": "agoric testnet", + "enabled": true, + "imports": [ + "AGR" + ], + "reliability_threshold": 268435455, + "data_reliability_enabled": true, + "block_distance_for_finalized_data": 0, + "blocks_in_finalization_proof": 1, + "average_block_time": 5000, + "allowed_block_lag_for_qos_sync": 2, + "shares" : 1, + "min_stake_provider": { + "denom": "ulava", + "amount": "50000000000" + }, + "api_collections": [ + { + "enabled": true, + "collection_data": { + "api_interface": "rest", + "internal_path": "", + "type": "GET", + "add_on": "" + }, + "apis": [], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "agoric-emerynet-8" + } + ] + } + ] + }, + { + "enabled": true, + "collection_data": { + "api_interface": "grpc", + "internal_path": "", + "type": "", + "add_on": "" + }, + "apis": [], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "agoric-emerynet-8" + } + ] + } + ] + }, + { + "enabled": true, + "collection_data": { + "api_interface": "tendermintrpc", + "internal_path": "", + "type": "", + "add_on": "" + }, + "apis": [], + "headers": [], + "inheritance_apis": [], + "parse_directives": [], + "verifications": [ + { + "name": "chain-id", + "values": [ + { + "expected_value": "agoric-emerynet-8" + } + ] + } + ] + } + ] + } + ] + }, + "deposit": "10000000ulava" +} \ No newline at end of file diff --git a/scripts/init_chain_commands.sh b/scripts/init_chain_commands.sh index a8b910b634..0f25be30a4 100755 --- a/scripts/init_chain_commands.sh +++ b/scripts/init_chain_commands.sh @@ -10,7 +10,7 @@ GASPRICE="0.000000001ulava" echo; echo "#### Sending proposal for specs ####" # ,./cookbook/specs/spec_add_mantle.json -lavad tx gov submit-legacy-proposal spec-add ./cookbook/specs/spec_add_ibc.json,./cookbook/specs/spec_add_cosmoswasm.json,./cookbook/specs/spec_add_cosmossdk.json,./cookbook/specs/spec_add_cosmossdk_45.json,./cookbook/specs/spec_add_cosmossdk_full.json,./cookbook/specs/spec_add_ethereum.json,./cookbook/specs/spec_add_cosmoshub.json,./cookbook/specs/spec_add_lava.json,./cookbook/specs/spec_add_osmosis.json,./cookbook/specs/spec_add_fantom.json,./cookbook/specs/spec_add_celo.json,./cookbook/specs/spec_add_optimism.json,./cookbook/specs/spec_add_arbitrum.json,./cookbook/specs/spec_add_starknet.json,./cookbook/specs/spec_add_aptos.json,./cookbook/specs/spec_add_juno.json,./cookbook/specs/spec_add_polygon.json,./cookbook/specs/spec_add_evmos.json,./cookbook/specs/spec_add_base.json,./cookbook/specs/spec_add_canto.json,./cookbook/specs/spec_add_sui.json,./cookbook/specs/spec_add_solana.json,./cookbook/specs/spec_add_bsc.json,./cookbook/specs/spec_add_axelar.json,./cookbook/specs/spec_add_avalanche.json,./cookbook/specs/spec_add_fvm.json,./cookbook/specs/spec_add_near.json,./cookbook/specs/spec_add_sqdsubgraph.json --lava-dev-test -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE +lavad tx gov submit-legacy-proposal spec-add ./cookbook/specs/spec_add_ibc.json,./cookbook/specs/spec_add_cosmoswasm.json,./cookbook/specs/spec_add_cosmossdk.json,./cookbook/specs/spec_add_cosmossdk_45.json,./cookbook/specs/spec_add_cosmossdk_full.json,./cookbook/specs/spec_add_ethereum.json,./cookbook/specs/spec_add_cosmoshub.json,./cookbook/specs/spec_add_lava.json,./cookbook/specs/spec_add_osmosis.json,./cookbook/specs/spec_add_fantom.json,./cookbook/specs/spec_add_celo.json,./cookbook/specs/spec_add_optimism.json,./cookbook/specs/spec_add_arbitrum.json,./cookbook/specs/spec_add_starknet.json,./cookbook/specs/spec_add_aptos.json,./cookbook/specs/spec_add_juno.json,./cookbook/specs/spec_add_polygon.json,./cookbook/specs/spec_add_evmos.json,./cookbook/specs/spec_add_base.json,./cookbook/specs/spec_add_canto.json,./cookbook/specs/spec_add_sui.json,./cookbook/specs/spec_add_solana.json,./cookbook/specs/spec_add_bsc.json,./cookbook/specs/spec_add_axelar.json,./cookbook/specs/spec_add_avalanche.json,./cookbook/specs/spec_add_fvm.json,./cookbook/specs/spec_add_near.json,./cookbook/specs/spec_add_sqdsubgraph.json,./cookbook/specs/spec_add_agoric.json --lava-dev-test -y --from alice --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE echo; echo "#### Waiting 2 blocks ####" wait_count_blocks 2 @@ -69,7 +69,7 @@ lavad tx subscription buy DefaultPlan $(lavad keys show user1 -a) -y --from user # lavad tx project set-policy $(lavad keys show user1 -a)-admin ./cookbook/projects/policy_all_chains_with_addon.yml -y --from user1 --gas-adjustment "1.5" --gas "auto" --gas-prices $GASPRICE # MANTLE -CHAINS="ETH1,GTH1,SEP1,COS3,FTM250,CELO,LAV1,COS4,ALFAJORES,ARB1,ARBN,APT1,STRK,JUN1,COS5,POLYGON1,EVMOS,OPTM,BASET,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR,SQDSUBGRAPH" +CHAINS="ETH1,GTH1,SEP1,COS3,FTM250,CELO,LAV1,COS4,ALFAJORES,ARB1,ARBN,APT1,STRK,JUN1,COS5,POLYGON1,EVMOS,OPTM,BASET,CANTO,SUIT,SOLANA,BSC,AXELAR,AVAX,FVM,NEAR,SQDSUBGRAPH,AGR" BASE_CHAINS="ETH1,LAV1" # stake providers on all chains echo; echo "#### Staking provider 1 ####" diff --git a/scripts/setup_providers.sh b/scripts/setup_providers.sh index 33f2fc2db8..b078407653 100755 --- a/scripts/setup_providers.sh +++ b/scripts/setup_providers.sh @@ -65,6 +65,8 @@ $PROVIDER1_LISTENER AXELAR grpc '$AXELAR_GRPC' \ $PROVIDER1_LISTENER AVAX jsonrpc '$AVALANCH_PJRPC' \ $PROVIDER1_LISTENER FVM jsonrpc '$FVM_JRPC' \ $PROVIDER1_LISTENER NEAR jsonrpc '$NEAR_JRPC' \ +$PROVIDER1_LISTENER AGR rest '$AGORIC_REST' \ +$PROVIDER1_LISTENER AGR grpc '$AGORIC_GRPC' \ $EXTRA_PROVIDER_FLAGS --metrics-listen-address ":7780" --geolocation "$GEOLOCATION" --log_level debug --from servicer1 2>&1 | tee $LOGS_DIR/PROVIDER1.log" && sleep 0.25 # $PROVIDER1_LISTENER MANTLE jsonrpc '$MANTLE_JRPC' \ diff --git a/x/rewards/keeper/pool_test.go b/x/rewards/keeper/pool_test.go index 5cc17ac407..dfc5d5eb9c 100644 --- a/x/rewards/keeper/pool_test.go +++ b/x/rewards/keeper/pool_test.go @@ -356,6 +356,7 @@ func TestRefillPoolsTimerStore(t *testing.T) { Prefix: types.RefillRewardsPoolTimerPrefix, } // check everything throughout the entire lifetime of the allocation pool (and beyond) + defaultBlockTime := ts.Keepers.Downtime.GetParams(ts.Ctx).DowntimeDuration.Seconds() month := ts.GetNextMonth(ts.BlockTime()) - ts.BlockTime().UTC().Unix() for i := 0; i < int(lifetime+2); i++ { res, err := ts.Keepers.TimerStoreKeeper.AllTimers(ts.GoCtx, req) @@ -364,7 +365,7 @@ func TestRefillPoolsTimerStore(t *testing.T) { require.Equal(t, 0, len(res.BlockHeightTimers)) expiry := ts.Keepers.Rewards.TimeToNextTimerExpiry(ts.Ctx) - require.Equal(t, month, expiry) + require.InDelta(t, month, expiry, defaultBlockTime) var expectedMonthsLeft int64 if i < int(lifetime) { @@ -375,8 +376,7 @@ func TestRefillPoolsTimerStore(t *testing.T) { ts.AdvanceMonths(1) ts.AdvanceBlock() - // testkeeper.EndBlock(ts.Ctx, ts.Keepers) - defaultBlockTime := ts.Keepers.Downtime.GetParams(ts.Ctx).DowntimeDuration.Seconds() - month = ts.GetNextMonth(ts.BlockTime()) - ts.BlockTime().UTC().Unix() - int64(defaultBlockTime) + testkeeper.EndBlock(ts.Ctx, ts.Keepers) + month = ts.GetNextMonth(ts.BlockTime()) - ts.BlockTime().UTC().Unix() } }