Skip to content

Commit

Permalink
Epoch scheduler (#32)
Browse files Browse the repository at this point in the history
* Start epoch scheduler

* Better error handling in scheduler

* Register scheduled rebalance task for max limit contracts

* Check contract exists before accepting any rebalance sudo callback

* Better scheduler tests

* Cover repeat setting
  • Loading branch information
alpe authored Jun 8, 2023
1 parent 1d7f0e2 commit 188a930
Show file tree
Hide file tree
Showing 20 changed files with 744 additions and 15 deletions.
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ linters:
disable-all: true
enable:
- bodyclose
- depguard
- dogsled
- errcheck
- exportloopref
Expand Down
1 change: 1 addition & 0 deletions demo/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ func NewMeshApp(
keys[meshsectypes.StoreKey],
app.BankKeeper,
app.StakingKeeper,
&app.WasmKeeper, // ensure this is a pointer as we instantiate the keeper a bit later
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)

Expand Down
5 changes: 2 additions & 3 deletions tests/e2e/mvp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,8 @@ func TestMVP(t *testing.T) {

// when an epoch ends, the delegation rebalance is triggered
doRebalance := func() {
rebalanceMsg := []byte(`{"rebalance":{}}`)
_, err = consumerApp.WasmKeeper.Sudo(consumerChain.GetContext(), consumerContracts.staking, rebalanceMsg)
require.NoError(t, err)
epochLength := consumerApp.MeshSecKeeper.GetRebalanceEpochLength(consumerChain.GetContext())
coord.CommitNBlocks(consumerChain, epochLength)
}
doRebalance()

Expand Down
38 changes: 38 additions & 0 deletions x/meshsecurity/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package meshsecurity

import (
"fmt"
"time"

"github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/keeper"
"github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types"

"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// EndBlocker is called after every block
func EndBlocker(ctx sdk.Context, k *keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)
epochLength := k.GetRebalanceEpochLength(ctx)
results, err := k.ExecScheduledTasks(ctx, types.SchedulerTaskRebalance, epochLength, func(ctx sdk.Context, addr sdk.AccAddress) error {
return k.Rebalance(ctx, addr)
})
if err != nil {
panic(err) // todo: log or fail?
}
for _, r := range results {
logger := keeper.ModuleLogger(ctx).
With("contract", r.Contract.String())
switch {
case r.ExecErr != nil:
logger.Error("failed to execute scheduled task")
case r.RescheduleErr != nil: // todo: log or fail?
panic(fmt.Sprintf("failed to reschedule task for contract %s", r.Contract.String()))
case r.DeleteTaskErr != nil:
logger.Error("failed to delete scheduled task after completion")
default:
logger.Info("scheduled task executed successfully", "gas_used", r.GasUsed, "gas_limit", r.GasLimit)
}
}
}
5 changes: 5 additions & 0 deletions x/meshsecurity/contract/out_message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package contract

type SudoMsg struct {
Rebalance *struct{} `json:"rebalance"`
}
112 changes: 111 additions & 1 deletion x/meshsecurity/keeper/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ import (
"testing"
"time"

"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/cosmos/cosmos-sdk/baseapp"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
"github.com/cosmos/cosmos-sdk/x/feegrant"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
Expand All @@ -23,6 +40,8 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/gov"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
Expand Down Expand Up @@ -85,29 +104,45 @@ type TestKeepers struct {
EncodingConfig encodingConfig
MeshKeeper *Keeper
AccountKeeper authkeeper.AccountKeeper
WasmKeeper wasmkeeper.Keeper
Faucet *wasmkeeper.TestFaucet
}

func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) {
db := dbm.NewMemDB()
ms := store.NewCommitMultiStore(db)
keys := sdk.NewKVStoreKeys(
authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, types.StoreKey,
authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
minttypes.StoreKey, distributiontypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey,
evidencetypes.StoreKey, ibctransfertypes.StoreKey,
capabilitytypes.StoreKey, feegrant.StoreKey, authzkeeper.StoreKey,
wasmtypes.StoreKey, types.StoreKey,
)
for _, v := range keys {
ms.MountStoreWithDB(v, storetypes.StoreTypeIAVL, db)
}
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
for _, v := range memKeys {
ms.MountStoreWithDB(v, storetypes.StoreTypeMemory, db)
}
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
for _, v := range tkeys {
ms.MountStoreWithDB(v, storetypes.StoreTypeTransient, db)
}
require.NoError(t, ms.LoadLatestVersion())

encConfig := makeEncodingConfig(t)
appCodec := encConfig.Marshaler

maccPerms := map[string][]string{ // module account permissions
authtypes.FeeCollectorName: nil,
distributiontypes.ModuleName: nil,
minttypes.ModuleName: {authtypes.Minter},
stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking},
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
govtypes.ModuleName: {authtypes.Burner},
ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner},
types.ModuleName: {authtypes.Minter, authtypes.Burner},
}
authority := authtypes.NewModuleAddress(govtypes.ModuleName).String()
Expand Down Expand Up @@ -146,11 +181,85 @@ func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) {
)
require.NoError(t, stakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()))

distKeeper := distributionkeeper.NewKeeper(
appCodec,
keys[distributiontypes.StoreKey],
accountKeeper,
bankKeeper,
stakingKeeper,
authtypes.FeeCollectorName,
authtypes.NewModuleAddress(distributiontypes.ModuleName).String(),
)
require.NoError(t, distKeeper.SetParams(ctx, distributiontypes.DefaultParams()))

querier := baseapp.NewGRPCQueryRouter()
querier.SetInterfaceRegistry(encConfig.InterfaceRegistry)
msgRouter := baseapp.NewMsgServiceRouter()
msgRouter.SetInterfaceRegistry(encConfig.InterfaceRegistry)

capabilityKeeper := capabilitykeeper.NewKeeper(
appCodec,
keys[capabilitytypes.StoreKey],
memKeys[capabilitytypes.MemStoreKey],
)
scopedIBCKeeper := capabilityKeeper.ScopeToModule(ibcexported.ModuleName)
scopedWasmKeeper := capabilityKeeper.ScopeToModule(types.ModuleName)

paramsKeeper := paramskeeper.NewKeeper(
appCodec,
encConfig.Amino,
keys[paramstypes.StoreKey],
tkeys[paramstypes.TStoreKey],
)

upgradeKeeper := upgradekeeper.NewKeeper(
map[int64]bool{},
keys[upgradetypes.StoreKey],
appCodec,
t.TempDir(),
nil,
authtypes.NewModuleAddress(upgradetypes.ModuleName).String(),
)

ibcKeeper := ibckeeper.NewKeeper(
appCodec,
keys[ibcexported.StoreKey],
paramsKeeper.Subspace(ibcexported.ModuleName),
stakingKeeper,
upgradeKeeper,
scopedIBCKeeper,
)

cfg := sdk.GetConfig()
cfg.SetAddressVerifier(wasmtypes.VerifyAddressLen())

wasmKeeper := wasmkeeper.NewKeeper(
appCodec,
keys[wasmtypes.StoreKey],
accountKeeper,
bankKeeper,
stakingKeeper,
distributionkeeper.NewQuerier(distKeeper),
ibcKeeper.ChannelKeeper, // ICS4Wrapper
ibcKeeper.ChannelKeeper,
&ibcKeeper.PortKeeper,
scopedWasmKeeper,
wasmtesting.MockIBCTransferKeeper{},
msgRouter,
querier,
t.TempDir(),
wasmtypes.DefaultWasmConfig(),
"iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,virtual_staking",
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
require.NoError(t, wasmKeeper.SetParams(ctx, wasmtypes.DefaultParams()))

msKeeper := NewKeeper(
appCodec,
keys[types.StoreKey],
bankKeeper,
stakingKeeper,
wasmKeeper,
authority,
)
faucet := wasmkeeper.NewTestFaucet(t, ctx, bankKeeper, minttypes.ModuleName, sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000_000))
Expand All @@ -161,6 +270,7 @@ func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) {
StoreKey: keys[types.StoreKey],
EncodingConfig: encConfig,
MeshKeeper: msKeeper,
WasmKeeper: wasmKeeper,
Faucet: faucet,
}
}
19 changes: 16 additions & 3 deletions x/meshsecurity/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package keeper

import (
"fmt"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"

"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
Expand All @@ -17,6 +21,7 @@ type Keeper struct {
cdc codec.Codec
bank types.XBankKeeper
staking types.XStakingKeeper
wasm types.WasmKeeper
// the address capable of executing a MsgUpdateParams message. Typically, this
// should be the x/gov module account.
authority string
Expand All @@ -28,9 +33,10 @@ func NewKeeper(
storeKey storetypes.StoreKey,
bank types.SDKBankKeeper,
staking types.SDKStakingKeeper,
wasm types.WasmKeeper,
authority string,
) *Keeper {
return NewKeeperX(cdc, storeKey, NewBankKeeperAdapter(bank), NewStakingKeeperAdapter(staking, bank), authority)
return NewKeeperX(cdc, storeKey, NewBankKeeperAdapter(bank), NewStakingKeeperAdapter(staking, bank), wasm, authority)
}

// NewKeeperX constructor with extended Osmosis SDK keepers
Expand All @@ -39,13 +45,15 @@ func NewKeeperX(
storeKey storetypes.StoreKey,
bank types.XBankKeeper,
staking types.XStakingKeeper,
wasm types.WasmKeeper,
authority string,
) *Keeper {
return &Keeper{
storeKey: storeKey,
cdc: cdc,
bank: bank,
staking: staking,
wasm: wasm,
authority: authority,
}
}
Expand All @@ -70,7 +78,7 @@ func (k Keeper) GetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress) sdk.Coin {

// SetMaxCapLimit stores the max cap limit for the given contract address.
// Any existing limit for this contract will be overwritten
func (k Keeper) SetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress, newAmount sdk.Coin) error {
func (k Keeper) SetMaxCapLimit(ctx sdk.Context, contract sdk.AccAddress, newAmount sdk.Coin) error {
if k.staking.BondDenom(ctx) != newAmount.Denom {
return sdkerrors.ErrInvalidCoins
}
Expand All @@ -79,7 +87,7 @@ func (k Keeper) SetMaxCapLimit(ctx sdk.Context, actor sdk.AccAddress, newAmount
if err != nil { // always nil
return errorsmod.Wrap(err, "marshal amount")
}
store.Set(types.BuildMaxCapLimitKey(actor), bz)
store.Set(types.BuildMaxCapLimitKey(contract), bz)
return nil
}

Expand Down Expand Up @@ -140,3 +148,8 @@ func (k Keeper) IterateMaxCapLimit(ctx sdk.Context, cb func(sdk.AccAddress, math
}
}
}

// ModuleLogger returns logger with module attribute
func ModuleLogger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
28 changes: 28 additions & 0 deletions x/meshsecurity/keeper/mocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/osmosis-labs/mesh-security-sdk/x/meshsecurity/types"
)

var _ types.WasmKeeper = &MockWasmKeeper{}

type MockWasmKeeper struct {
SudoFn func(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error)
HasContractInfoFn func(ctx sdk.Context, contractAddress sdk.AccAddress) bool
}

func (m MockWasmKeeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) {
if m.SudoFn == nil {
panic("not expected to be called")
}
return m.SudoFn(ctx, contractAddress, msg)
}

func (m MockWasmKeeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool {
if m.HasContractInfoFn == nil {
panic("not expected to be called")
}
return m.HasContractInfoFn(ctx, contractAddress)
}
9 changes: 8 additions & 1 deletion x/meshsecurity/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@ func (m msgServer) SetVirtualStakingMaxCap(goCtx context.Context, req *types.Msg
if err != nil {
return nil, errorsmod.Wrap(err, "contract")
}
if err := m.k.SetMaxCapLimit(sdk.UnwrapSDKContext(goCtx), acc, req.MaxCap); err != nil {
ctx := sdk.UnwrapSDKContext(goCtx)

if err := m.k.SetMaxCapLimit(ctx, acc, req.MaxCap); err != nil {
return nil, err
}
if !m.k.HasScheduledTask(ctx, types.SchedulerTaskRebalance, acc) {
if err := m.k.ScheduleRebalanceTask(ctx, acc); err != nil {
return nil, errorsmod.Wrap(err, "failed to schedule rebalance task")
}
}
return &types.MsgSetVirtualStakingMaxCapResponse{}, nil
}
Loading

0 comments on commit 188a930

Please sign in to comment.