Skip to content

Commit

Permalink
feat: CNS-957-provider-jail (#1443)
Browse files Browse the repository at this point in the history
* add provider jail

* fix existing tests

* add jail to readme

* reset jail counter after 24H

* pr changes

* lint

* fix bug

* update readme

* prepare for testing

* add unitest

* fix bugs and unitest

* we have a big test for all jailed events

* change proto name

* fix unitest

* fix readme

* pr changes

---------

Co-authored-by: Yarom Swisa <yarom@lavanet.xyz git config --global user.name Yarom>
Co-authored-by: Yaroms <yaroms@lavanet.xyz>
  • Loading branch information
3 people authored May 31, 2024
1 parent b2ffd3b commit 3c93711
Show file tree
Hide file tree
Showing 13 changed files with 385 additions and 120 deletions.
2 changes: 2 additions & 0 deletions proto/lavanet/lava/epochstorage/stake_entry.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ message StakeEntry {
BlockReport block_report = 13;
string vault = 14;
cosmos.staking.v1beta1.Description description = 15 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
uint64 jails = 16;
int64 jail_end_time = 17;
}

// BlockReport holds the most up-to-date info regarding blocks of the provider
Expand Down
18 changes: 4 additions & 14 deletions testutil/keeper/keepers_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,9 @@ func AdvanceToBlock(ctx context.Context, ks *Keepers, block uint64, customBlockT
return ctx
}

if len(customBlockTime) > 0 {
for uint64(unwrapedCtx.BlockHeight()) < block {
ctx = AdvanceBlock(ctx, ks, customBlockTime...)
unwrapedCtx = sdk.UnwrapSDKContext(ctx)
}
} else {
for uint64(unwrapedCtx.BlockHeight()) < block {
ctx = AdvanceBlock(ctx, ks)
unwrapedCtx = sdk.UnwrapSDKContext(ctx)
}
for uint64(unwrapedCtx.BlockHeight()) < block {
ctx = AdvanceBlock(ctx, ks, customBlockTime...)
unwrapedCtx = sdk.UnwrapSDKContext(ctx)
}

return ctx
Expand All @@ -476,10 +469,7 @@ func AdvanceEpoch(ctx context.Context, ks *Keepers, customBlockTime ...time.Dura
if err != nil {
panic(err)
}
if len(customBlockTime) > 0 {
return AdvanceToBlock(ctx, ks, nextEpochBlockNum, customBlockTime...)
}
return AdvanceToBlock(ctx, ks, nextEpochBlockNum)
return AdvanceToBlock(ctx, ks, nextEpochBlockNum, customBlockTime...)
}

// Make sure you save the new context
Expand Down
2 changes: 1 addition & 1 deletion x/dualstaking/keeper/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func (k Keeper) modifyStakeEntryDelegation(ctx sdk.Context, delegator, provider,
details["min_spec_stake"] = k.specKeeper.GetMinStake(ctx, chainID).String()
utils.LogLavaEvent(ctx, k.Logger(ctx), types.FreezeFromUnbond, details, "freezing provider due to stake below min spec stake")
stakeEntry.Freeze()
} else if delegator == stakeEntry.Vault && stakeEntry.IsFrozen() {
} else if delegator == stakeEntry.Vault && stakeEntry.IsFrozen() && !stakeEntry.IsJailed(ctx.BlockTime().UTC().Unix()) {
stakeEntry.UnFreeze(k.epochstorageKeeper.GetCurrentNextEpoch(ctx) + 1)
}

Expand Down
4 changes: 4 additions & 0 deletions x/epochstorage/types/stake_entry.go

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

150 changes: 113 additions & 37 deletions x/epochstorage/types/stake_entry.pb.go

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

10 changes: 9 additions & 1 deletion x/pairing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type StakeEntry struct {
DelegateTotal types.Coin // total delegation to the provider (without self delegation)
DelegateLimit types.Coin // delegation total limit
DelegateCommission uint64 // commission from delegation rewards
Jails uint64 // number of times the provider has been jailed
JailTime int64 // the end of the jail time, after which the provider can return to service
}
```

Expand Down Expand Up @@ -207,12 +209,18 @@ Pairing verification is used by the provider to determine whether to offer servi

#### 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.
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 jailing.

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

#### Jail

If a provider is down and users report it, the provider will be jailed.
The first 2 instances of jailing are temporary, lasting 1 hour each, and will be automatically removed.
After 2 consecutive jailings, the provider will be jailed for 24 hours and set to a 'frozen' state. To resume activity, the provider must send an 'unfreeze' transaction after the jail time has ended.

#### 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.
Expand Down
6 changes: 6 additions & 0 deletions x/pairing/keeper/epoch_cu.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ func (k Keeper) GetProviderEpochComplainerCu(ctx sdk.Context, epoch uint64, prov
return val, true
}

// RemoveProviderEpochComplainerCu deletes a ProviderEpochComplainerCu in the store
func (k Keeper) RemoveProviderEpochComplainerCu(ctx sdk.Context, epoch uint64, provider string, chainID string) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ProviderEpochComplainerCuKeyPrefix())
store.Delete(types.ProviderEpochCuKey(epoch, provider, chainID))
}

// RemoveProviderEpochComplainerCu removes a ProviderEpochCu from the store
func (k Keeper) RemoveAllProviderEpochComplainerCu(ctx sdk.Context, epoch uint64) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ProviderEpochComplainerCuKeyPrefix())
Expand Down
25 changes: 19 additions & 6 deletions x/pairing/keeper/msg_server_unfreeze.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ func (k msgServer) UnfreezeProvider(goCtx context.Context, msg *types.MsgUnfreez
return nil, utils.LavaFormatWarning("Unfreeze_cant_get_stake_entry", types.FreezeStakeEntryNotFoundError, []utils.Attribute{{Key: "chainID", Value: chainId}, {Key: "providerAddress", Value: msg.GetCreator()}}...)
}

// the provider is not frozen (active or jailed), continue to other chainIDs
if !stakeEntry.IsFrozen() {
continue
}

minStake := k.Keeper.specKeeper.GetMinStake(ctx, chainId)
if stakeEntry.EffectiveStake().LT(minStake.Amount) {
return nil, utils.LavaFormatWarning("Unfreeze_insufficient_stake", types.UnFreezeInsufficientStakeError,
Expand All @@ -31,13 +36,21 @@ func (k msgServer) UnfreezeProvider(goCtx context.Context, msg *types.MsgUnfreez
}...)
}

if stakeEntry.StakeAppliedBlock > unfreezeBlock {
// unfreeze the provider by making the StakeAppliedBlock the current block. This will let the provider be added to the pairing list in the next epoch, when current entries becomes the front of epochStorage
stakeEntry.UnFreeze(unfreezeBlock)
k.epochStorageKeeper.ModifyStakeEntryCurrent(ctx, chainId, stakeEntry)
unfrozen_chains = append(unfrozen_chains, chainId)
if stakeEntry.IsJailed(ctx.BlockTime().UTC().Unix()) {
return nil, utils.LavaFormatWarning("Unfreeze_jailed_provider", types.UnFreezeJailedStakeError,
[]utils.Attribute{
{Key: "chainID", Value: chainId},
{Key: "providerAddress", Value: msg.GetCreator()},
{Key: "jailEnd", Value: stakeEntry.JailEndTime},
}...)
}
// else case does not throw an error because we don't want to fail unfreezing other chains

// unfreeze the provider by making the StakeAppliedBlock the current block. This will let the provider be added to the pairing list in the next epoch, when current entries becomes the front of epochStorage
stakeEntry.UnFreeze(unfreezeBlock)
stakeEntry.JailEndTime = 0
stakeEntry.Jails = 0
k.epochStorageKeeper.ModifyStakeEntryCurrent(ctx, chainId, stakeEntry)
unfrozen_chains = append(unfrozen_chains, chainId)
}
utils.LogLavaEvent(ctx, ctx.Logger(), "unfreeze_provider", map[string]string{"providerAddress": msg.GetCreator(), "chainIDs": strings.Join(unfrozen_chains, ",")}, "Provider Unfreeze")
return &types.MsgUnfreezeProviderResponse{}, nil
Expand Down
Loading

0 comments on commit 3c93711

Please sign in to comment.