diff --git a/app/app.go b/app/app.go index df05ce77e..98f875737 100644 --- a/app/app.go +++ b/app/app.go @@ -320,7 +320,7 @@ func NewBabylonApp( ), ) - app.EpochingKeeper = epochingkeeper.NewKeeper(appCodec, keys[epochingtypes.StoreKey], keys[epochingtypes.StoreKey], app.GetSubspace(epochingtypes.ModuleName)) + app.EpochingKeeper = epochingkeeper.NewKeeper(appCodec, keys[epochingtypes.StoreKey], keys[epochingtypes.StoreKey], app.GetSubspace(epochingtypes.ModuleName), &app.StakingKeeper) app.BTCLightClientKeeper = *btclightclientkeeper.NewKeeper(appCodec, keys[btclightclienttypes.StoreKey], keys[btclightclienttypes.MemStoreKey], app.GetSubspace(btclightclienttypes.ModuleName)) app.BtcCheckpointKeeper = btccheckpointkeeper.NewKeeper(appCodec, keys[btccheckpointtypes.StoreKey], keys[btccheckpointtypes.MemStoreKey], app.GetSubspace(btccheckpointtypes.ModuleName)) app.CheckpointingKeeper = checkpointingkeeper.NewKeeper(appCodec, keys[checkpointingtypes.StoreKey], keys[checkpointingtypes.MemStoreKey], app.GetSubspace(checkpointingtypes.ModuleName)) @@ -359,7 +359,7 @@ func NewBabylonApp( evidence.NewAppModule(app.EvidenceKeeper), params.NewAppModule(app.ParamsKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), - epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper), + epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), btclightclient.NewAppModule(appCodec, app.BTCLightClientKeeper, app.AccountKeeper, app.BankKeeper), btccheckpoint.NewAppModule(appCodec, app.BtcCheckpointKeeper, app.AccountKeeper, app.BankKeeper), checkpointing.NewAppModule(appCodec, app.CheckpointingKeeper, app.AccountKeeper, app.BankKeeper), @@ -381,21 +381,25 @@ func NewBabylonApp( btccheckpointtypes.ModuleName, checkpointingtypes.ModuleName, ) - app.mm.SetOrderEndBlockers( - crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, + // TODO: there will be an architecture design on whether to modify slashing/evidence, specifically + // - how many validators can we slash in a single epoch and + // - whether and when to jail slashed validators + // app.mm.OrderBeginBlockers = append(app.mm.OrderBeginBlockers[:4], app.mm.OrderBeginBlockers[4+1:]...) // remove slashingtypes.ModuleName + // app.mm.OrderBeginBlockers = append(app.mm.OrderBeginBlockers[:4], app.mm.OrderBeginBlockers[4+1:]...) // remove evidencetypes.ModuleName + + app.mm.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, minttypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName, - // TODO: BBL doesn't want the staking module to update the validator set at the end of each block. We consider two approaches to fix this: - // - remove stakingtypes.ModuleName from here, and let `epoching.EndBlock` do everything - // - call `epoching.EndBlock` first but only to dequeue the delayed staking requests, then let `staking.EndBlock` take care of executing them and return the changeset. epochingtypes.ModuleName, btclightclienttypes.ModuleName, btccheckpointtypes.ModuleName, checkpointingtypes.ModuleName, ) + // BBL does not want EndBlock processing in staking + app.mm.OrderEndBlockers = append(app.mm.OrderEndBlockers[:2], app.mm.OrderEndBlockers[2+1:]...) // remove stakingtypes.ModuleName // NOTE: The genutils module must occur after staking so that pools are // properly initialized with tokens from genesis accounts. @@ -442,7 +446,7 @@ func NewBabylonApp( params.NewAppModule(app.ParamsKeeper), evidence.NewAppModule(app.EvidenceKeeper), authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), - epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper), + epoching.NewAppModule(appCodec, app.EpochingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), btclightclient.NewAppModule(appCodec, app.BTCLightClientKeeper, app.AccountKeeper, app.BankKeeper), btccheckpoint.NewAppModule(appCodec, app.BtcCheckpointKeeper, app.AccountKeeper, app.BankKeeper), checkpointing.NewAppModule(appCodec, app.CheckpointingKeeper, app.AccountKeeper, app.BankKeeper), diff --git a/testutil/keeper/epoching.go b/testutil/keeper/epoching.go index 0dde20461..804456c8f 100644 --- a/testutil/keeper/epoching.go +++ b/testutil/keeper/epoching.go @@ -41,6 +41,8 @@ func EpochingKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { storeKey, memStoreKey, paramsSubspace, + // TODO: make this compile at the moment, will fix for integrated testing + nil, ) ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) diff --git a/x/epoching/abci.go b/x/epoching/abci.go new file mode 100644 index 000000000..9e7be1f5b --- /dev/null +++ b/x/epoching/abci.go @@ -0,0 +1,34 @@ +package epoching + +import ( + "time" + + "github.com/babylonchain/babylon/x/epoching/keeper" + "github.com/babylonchain/babylon/x/epoching/types" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + + // TODO: unimplemented: + // - if the first block of an epoch, + // - increment epoch number + // - trigger hook and emit event +} + +// Called every block, update validator set +func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + defer telemetry.ModuleMeasureSince(stakingtypes.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + // TODO: unimplemented: + // - if an epoch is newly checkpointed, make unbonding validators/delegations in this epoch unbonded + // - if reaching an epoch boundary, execute validator-related msgs (bonded -> unbonding) + return nil +} diff --git a/x/epoching/keeper/keeper.go b/x/epoching/keeper/keeper.go index a27f7badd..88777d60f 100644 --- a/x/epoching/keeper/keeper.go +++ b/x/epoching/keeper/keeper.go @@ -21,6 +21,7 @@ type ( memKey sdk.StoreKey hooks types.EpochingHooks paramstore paramtypes.Subspace + stk types.StakingKeeper } ) @@ -29,6 +30,7 @@ func NewKeeper( storeKey, memKey sdk.StoreKey, ps paramtypes.Subspace, + stk types.StakingKeeper, ) Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -41,6 +43,7 @@ func NewKeeper( memKey: memKey, paramstore: ps, hooks: nil, + stk: stk, } } diff --git a/x/epoching/keeper/modified_staking.go b/x/epoching/keeper/modified_staking.go new file mode 100644 index 000000000..29131a205 --- /dev/null +++ b/x/epoching/keeper/modified_staking.go @@ -0,0 +1,110 @@ +package keeper + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + +// ApplyMatureUnbonding +// - unbonds all mature validators/delegations, and +// - finishes all mature redelegations +// in the corresponding queues, where +// - an unbonding/redelegation becomes mature when its corresponding epoch and all previous epochs have been checkpointed. +// Triggered by the checkpointing module upon the above condition. +// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/staking/keeper/val_state_change.go#L32-L91) +func (k *Keeper) ApplyMatureUnbonding(ctx sdk.Context, epochBoundaryHeader tmproto.Header) { + currHeader := ctx.BlockHeader() + + // unbond all mature validators till the epoch boundary from the unbonding queue + ctx.WithBlockHeader(epochBoundaryHeader) + k.stk.UnbondAllMatureValidators(ctx) + ctx.WithBlockHeader(currHeader) + + // get all mature unbonding delegations the epoch boundary from the ubd queue. + ctx.WithBlockHeader(epochBoundaryHeader) + matureUnbonds := k.stk.DequeueAllMatureUBDQueue(ctx, epochBoundaryHeader.Time) + ctx.WithBlockHeader(currHeader) + // unbond all mature delegations + for _, dvPair := range matureUnbonds { + addr, err := sdk.ValAddressFromBech32(dvPair.ValidatorAddress) + if err != nil { + panic(err) + } + delegatorAddress, err := sdk.AccAddressFromBech32(dvPair.DelegatorAddress) + if err != nil { + panic(err) + } + balances, err := k.stk.CompleteUnbonding(ctx, delegatorAddress, addr) + if err != nil { + continue + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeCompleteUnbonding, + sdk.NewAttribute(sdk.AttributeKeyAmount, balances.String()), + sdk.NewAttribute(types.AttributeKeyValidator, dvPair.ValidatorAddress), + sdk.NewAttribute(types.AttributeKeyDelegator, dvPair.DelegatorAddress), + ), + ) + } + + // get all mature redelegations till the epoch boundary from the red queue. + ctx.WithBlockHeader(epochBoundaryHeader) + matureRedelegations := k.stk.DequeueAllMatureRedelegationQueue(ctx, epochBoundaryHeader.Time) + ctx.WithBlockHeader(currHeader) + // finish all mature redelegations + for _, dvvTriplet := range matureRedelegations { + valSrcAddr, err := sdk.ValAddressFromBech32(dvvTriplet.ValidatorSrcAddress) + if err != nil { + panic(err) + } + valDstAddr, err := sdk.ValAddressFromBech32(dvvTriplet.ValidatorDstAddress) + if err != nil { + panic(err) + } + delegatorAddress, err := sdk.AccAddressFromBech32(dvvTriplet.DelegatorAddress) + if err != nil { + panic(err) + } + balances, err := k.stk.CompleteRedelegation( + ctx, + delegatorAddress, + valSrcAddr, + valDstAddr, + ) + if err != nil { + continue + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeCompleteRedelegation, + sdk.NewAttribute(sdk.AttributeKeyAmount, balances.String()), + sdk.NewAttribute(types.AttributeKeyDelegator, dvvTriplet.DelegatorAddress), + sdk.NewAttribute(types.AttributeKeySrcValidator, dvvTriplet.ValidatorSrcAddress), + sdk.NewAttribute(types.AttributeKeyDstValidator, dvvTriplet.ValidatorDstAddress), + ), + ) + } +} + +// ApplyAndReturnValidatorSetUpdates applies and return accumulated updates to the bonded validator set, including +// * Updates the active validator set as keyed by LastValidatorPowerKey. +// * Updates the total power as keyed by LastTotalPowerKey. +// * Updates validator status' according to updated powers. +// * Updates the fee pool bonded vs not-bonded tokens. +// * Updates relevant indices. +// Triggered upon every epoch. +// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/staking/keeper/val_state_change.go#L18-L30) +func (k *Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) []abci.ValidatorUpdate { + validatorUpdates, err := k.stk.ApplyAndReturnValidatorSetUpdates(ctx) + if err != nil { + panic(err) + } + + return validatorUpdates +} diff --git a/x/epoching/module.go b/x/epoching/module.go index 1be564baf..2b62d06cd 100644 --- a/x/epoching/module.go +++ b/x/epoching/module.go @@ -102,7 +102,7 @@ type AppModule struct { keeper keeper.Keeper accountKeeper types.AccountKeeper bankKeeper types.BankKeeper - // TODO: add dependencies to staking, slashing and evidence + stakingKeeper types.StakingKeeper } func NewAppModule( @@ -110,12 +110,14 @@ func NewAppModule( keeper keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, + stakingKeeper types.StakingKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), keeper: keeper, accountKeeper: accountKeeper, bankKeeper: bankKeeper, + stakingKeeper: stakingKeeper, } } @@ -167,14 +169,10 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 2 } -// BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) { - // TODO: trigger BeginBlock stuff upon the first block of an epoch +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, am.keeper, req) } -// EndBlock executes all ABCI EndBlock logic respective to the capability module. It -// returns no validator updates. -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - // TODO: trigger EndBlock stuff upon the last block of an epoch - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return EndBlocker(ctx, am.keeper) } diff --git a/x/epoching/types/expected_keepers.go b/x/epoching/types/expected_keepers.go index c35ae0f88..a926b1a3c 100644 --- a/x/epoching/types/expected_keepers.go +++ b/x/epoching/types/expected_keepers.go @@ -1,13 +1,18 @@ package types import ( + "time" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + abci "github.com/tendermint/tendermint/abci/types" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) type AccountKeeper interface { - GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI // Methods imported from account should be defined here } @@ -17,7 +22,16 @@ type BankKeeper interface { // Methods imported from bank should be defined here } -// TODO: add interfaces of staking, slashing and evidence used in epoching +// StakingKeeper defines the staking module interface contract needed by the +// epoching module. +type StakingKeeper interface { + UnbondAllMatureValidators(ctx sdk.Context) + DequeueAllMatureUBDQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []stakingtypes.DVPair) + CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) + DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time.Time) (matureRedelegations []stakingtypes.DVVTriplet) + CompleteRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) (sdk.Coins, error) + ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []abci.ValidatorUpdate, err error) +} // Event Hooks // These can be utilized to communicate between a staking keeper and another