Skip to content

Commit

Permalink
Merge branch 'main' into PRT-replace-cookbook-with-lava-config
Browse files Browse the repository at this point in the history
  • Loading branch information
Yaroms authored Dec 15, 2024
2 parents 1a6e53a + 4322087 commit 5ab8033
Show file tree
Hide file tree
Showing 17 changed files with 920 additions and 98 deletions.
4 changes: 3 additions & 1 deletion proto/lavanet/lava/dualstaking/delegate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ message Delegation {
string provider = 1; // provider receives the delegated funds
string delegator = 3; // delegator that owns the delegated funds
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
int64 timestamp = 5; // Unix timestamp of the delegation (+ month)
int64 timestamp = 5; // Unix timestamp of the last change
cosmos.base.v1beta1.Coin credit = 6 [(gogoproto.nullable) = false]; // amount of credit earned by the delegation over the period
int64 credit_timestamp = 7; // Unix timestamp of the delegation credit latest calculation capped at 30d
}

message Delegator {
Expand Down
8 changes: 8 additions & 0 deletions testutil/common/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,14 @@ func (ts *Tester) AdvanceMonthsFrom(from time.Time, months int) *Tester {
return ts
}

func (ts *Tester) AdvanceTimeHours(timeDelta time.Duration) *Tester {
endTime := ts.BlockTime().Add(timeDelta)
for ts.BlockTime().Before(endTime) {
ts.AdvanceBlock(time.Hour)
}
return ts
}

func (ts *Tester) BondDenom() string {
return ts.Keepers.StakingKeeper.BondDenom(sdk.UnwrapSDKContext(ts.Ctx))
}
Expand Down
5 changes: 3 additions & 2 deletions testutil/keeper/dualstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"testing"
"time"

tmdb "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
Expand Down Expand Up @@ -64,15 +65,15 @@ func DualstakingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
memStoreKey,
paramsSubspace,
&mockBankKeeper{},
nil,
&mockStakingKeeperEmpty{},
&mockAccountKeeper{},
epochstorageKeeper,
speckeeper.NewKeeper(cdc, nil, nil, paramsSubspaceSpec, nil),
fixationkeeper.NewKeeper(cdc, tsKeeper, epochstorageKeeper.BlocksToSaveRaw),
)

ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger())

ctx = ctx.WithBlockTime(time.Now().UTC())
// Initialize params
k.SetParams(ctx, types.DefaultParams())

Expand Down
56 changes: 56 additions & 0 deletions testutil/keeper/mock_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"fmt"
"time"

"cosmossdk.io/math"
tenderminttypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// account keeper mock
Expand Down Expand Up @@ -36,6 +38,60 @@ func (k mockAccountKeeper) SetModuleAccount(sdk.Context, authtypes.ModuleAccount
// mock bank keeper
var balance map[string]sdk.Coins = make(map[string]sdk.Coins)

type mockStakingKeeperEmpty struct{}

func (k mockStakingKeeperEmpty) ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingtypes.ValidatorI {
return nil
}

func (k mockStakingKeeperEmpty) UnbondingTime(ctx sdk.Context) time.Duration {
return time.Duration(0)
}

func (k mockStakingKeeperEmpty) GetAllDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress) []stakingtypes.Delegation {
return nil
}

func (k mockStakingKeeperEmpty) GetDelegatorValidator(ctx sdk.Context, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) (validator stakingtypes.Validator, err error) {
return stakingtypes.Validator{}, nil
}

func (k mockStakingKeeperEmpty) GetDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (delegation stakingtypes.Delegation, found bool) {
return stakingtypes.Delegation{}, false
}

func (k mockStakingKeeperEmpty) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) {
return stakingtypes.Validator{}, false
}

func (k mockStakingKeeperEmpty) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []stakingtypes.Delegation) {
return []stakingtypes.Delegation{}
}

func (k mockStakingKeeperEmpty) BondDenom(ctx sdk.Context) string {
return "ulava"
}

func (k mockStakingKeeperEmpty) ValidateUnbondAmount(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt math.Int) (shares sdk.Dec, err error) {
return sdk.Dec{}, nil
}

func (k mockStakingKeeperEmpty) Undelegate(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (time.Time, error) {
return time.Time{}, nil
}

func (k mockStakingKeeperEmpty) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt math.Int, tokenSrc stakingtypes.BondStatus, validator stakingtypes.Validator, subtractAccount bool) (newShares sdk.Dec, err error) {
return sdk.Dec{}, nil
}

func (k mockStakingKeeperEmpty) GetBondedValidatorsByPower(ctx sdk.Context) []stakingtypes.Validator {
return []stakingtypes.Validator{}
}

func (k mockStakingKeeperEmpty) GetAllValidators(ctx sdk.Context) (validators []stakingtypes.Validator) {
return []stakingtypes.Validator{}
}

type mockBankKeeper struct{}

func init_balance() {
Expand Down
53 changes: 36 additions & 17 deletions x/dualstaking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ This document specifies the dualstaking module of Lava Protocol.

In the Lava blockchain there are two kinds of staking users, the first ones are validators, legacy to cosmos, the second ones are providers.
Validators play a role in the consensus mechanism, while providers offer services to consumers and compete with other providers by staking tokens.
Since a lot of tokens are expected to be staked by providers, to enhance the security of the chain, Lava lets providers to participate in the consensus via dualstaking.
Since a lot of tokens are expected to be staked by providers, to enhance the security of the chain, Lava lets providers to participate in the consensus via dualstaking.
Dualstaking makes this happen by "duplicating" delegations, for each validator delegation a parallel provider delegation will be created for the delegator, As a result, providers gain power in the consensus, influencing governance and block creation.


## Contents

* [Concepts](#concepts)
* [Delegation](#delegation)
* [Empty Provider](#empty-provider)
* [Dualstaking](#dualstaking)
* [Validator Delegation](#validator-delegation)
* [Validator Unbonding](#validator-unbonding)
* [Validator Slashing](#validator-slashing)
* [Provider Delegation](#provider-delegation)
* [Provider Unbonding](#provider-unbonding)
* [Hooks](#hooks)
* [RedelegateFlag](#redelegateflag)
* [Rewards](#rewards)
* [Delegation](#delegation)
* [Empty Provider](#empty-provider)
* [Dualstaking](#dualstaking)
* [Validator Delegation](#validator-delegation)
* [Validator Unbonding](#validator-unbonding)
* [Validator Slashing](#validator-slashing)
* [Provider Delegation](#provider-delegation)
* [Provider Unbonding](#provider-unbonding)
* [Hooks](#hooks)
* [RedelegateFlag](#redelegateflag)
* [Credit](#credit)
* [Parameters](#parameters)
* [Queries](#queries)
* [Transactions](#transactions)
Expand All @@ -38,7 +38,7 @@ When a provider stakes tokens, they create a self-delegation entry. Whenever a p

### Empty Provider

The empty provider is a place holder for provider delegations that are issued by the staking module.
The empty provider is a place holder for provider delegations that are issued by the staking module.
To support the functionality of the legacy Staking module, when a user delegates to a validator (it can't define the provider to delegate to in the legacy message), the dual staking module will delegate the same ammount to the empty provider.
The user can than choose to redelegate from the empty provider to an actual provider.

Expand Down Expand Up @@ -84,16 +84,35 @@ The following are use cases of the dualstaking module:
### Hooks

Dual staking module uses [staking hooks](keeper/hooks.go) to achieve its functionality.

1. AfterDelegationModified: this hook is called whenever a delegation is changed, whether it is created, or modified (NOT when completly removed). it calculates the difference in providers and validators stake to determine the action of the user (delegation or unbonding) depending on who is higher and than does the same with provider delegation.
* If provider delegations > validator delegations: user unbonded, uniform unbond from providers delegations (priority to empty provider).
* If provider delegations < validator delegations: user delegation, delegate to the empty provider.
2. BeforeDelegationRemoved: this hook is called when a delegation to a validator is removed (unbonding of all the tokens). uniform unbond from providers delegations
3. BeforeValidatorSlashed: this hook is called when a validator is being slashed. to make sure the balance between validator and provider delegation is kept it uniform unbond from providers delegations the slashed amount.
3. BeforeValidatorSlashed: this hook is called when a validator is being slashed. to make sure the balance between validator and provider delegation is kept it uniform unbond from providers delegations the slashed amount.

### RedelegateFlag

To prevent the dual staking module from taking action in the case of validator redelegation, we utilize the [antehandler](ante/ante_handler.go). When a redelegation message is being processed, the RedelegateFlag is set to true, and the hooks will disregard any delegation changes. It is important to note that the RedelegateFlag is stored in memory and not in the chain’s state.

### Credit

Credit Mechanism Overview
The credit mechanism ensures fair reward distribution to delegators based on both the amount and duration of their delegation. It calculates rewards proportionally to the effective stake over time.

Key Components

Credit: Effective delegation for a delegator, adjusted for staking duration.
CreditTimestamp: Last update time for the credit, enabling accurate reward calculations.
How It Works

Credit is calculated when a delegation is made or modified, based on the current amount and elapsed time.
Rewards are normalized over a 30-day period for consistency.
Example
Alice delegates 100 tokens for a full month, earning a credit of 100 tokens. Bob delegates 200 tokens for half a month, also earning a credit of 100 tokens. With a total reward pool of 500 tokens, both receive 250 tokens, reflecting their credit-adjusted stakes.

If Alice increases her delegation to 150 tokens mid-month, her credit is updated to reflect rewards earned so far, and future rewards are calculated on the new amount. This ensures fair distribution based on both delegation amount and duration.

## Parameters

The dualstaking parameters:
Expand Down Expand Up @@ -128,19 +147,19 @@ The Dualstaking module supports the following transactions:
| `unbond` | validator-addr (string) provider-addr (string) amount (coin) | undong from validator and provider the given amount |
| `claim-rewards` | optional: provider-addr (string)| claim the rewards from a given provider or all rewards |


## Proposals

The Dualstaking module does not have proposals.

### Events

The Dualstaking module has the following events:

| Event | When it happens |
| ---------- | --------------- |
| `delegate_to_provider` | a successful provider delegation |
| `unbond_from_provider` | a successful provider delegation unbond |
| `redelegate_between_providers` | a successful provider redelegation|
| `delegator_claim_rewards` | a successful provider delegator reward claim|
| `contributor_rewards` | spec contributor got new rewards|
| `validator_slash` | validator slashed happened, providers slashed accordingly|
| `validator_slash` | validator slashed happened, providers slashed accordingly|
17 changes: 13 additions & 4 deletions x/dualstaking/keeper/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@ import (
// and updates the (epochstorage) stake-entry.
func (k Keeper) increaseDelegation(ctx sdk.Context, delegator, provider string, amount sdk.Coin, stake bool) error {
// get, update the delegation entry
delegation, err := k.delegations.Get(ctx, types.DelegationKey(provider, delegator))
if err != nil {
delegation, found := k.GetDelegation(ctx, provider, delegator)
if !found {
// new delegation (i.e. not increase of existing one)
delegation = types.NewDelegation(delegator, provider, ctx.BlockTime(), k.stakingKeeper.BondDenom(ctx))
}

delegation.AddAmount(amount)

err = k.delegations.Set(ctx, types.DelegationKey(provider, delegator), delegation)
err := k.SetDelegation(ctx, delegation)
if err != nil {
return err
}
Expand Down Expand Up @@ -364,7 +363,17 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) ([]types.Delegation, error) {
return iter.Values()
}

// this function overwrites the time tag with the ctx time upon writing the delegation
func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) error {
delegation.Timestamp = ctx.BlockTime().UTC().Unix()
existingDelegation, found := k.GetDelegation(ctx, delegation.Provider, delegation.Delegator)
if !found {
return k.delegations.Set(ctx, types.DelegationKey(delegation.Provider, delegation.Delegator), delegation)
}
// calculate credit based on the existing delegation before changes
credit, creditTimestamp := k.CalculateCredit(ctx, existingDelegation)
delegation.Credit = credit
delegation.CreditTimestamp = creditTimestamp
return k.delegations.Set(ctx, types.DelegationKey(delegation.Provider, delegation.Delegator), delegation)
}

Expand Down
Loading

0 comments on commit 5ab8033

Please sign in to comment.