From d322e234253a6f54fd92d2b55ce8e1af55840fab Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Wed, 5 Jun 2019 00:06:58 +0200 Subject: [PATCH] Merge PR #4417: cleanup staking references from types pkg * cleanup staking references from types pkg * check interface on runtime * fixes * rename validator set from slashing keeper * final cleanup * fix test * fix querier * fix tests * rename interfaces * update bond status * fixes * expected pkg * ensure expected keepers match used funcs * cha cha cha --- .../sdk/3928-remove-types-staking | 2 + docs/spec/staking/04_end_block.md | 4 +- simapp/export.go | 5 +- types/staking.go | 156 +++--------------- x/distribution/keeper/allocation.go | 5 +- x/distribution/keeper/delegation.go | 7 +- x/distribution/keeper/hooks.go | 23 +-- x/distribution/keeper/invariants.go | 5 +- x/distribution/keeper/querier.go | 5 +- x/distribution/keeper/validator.go | 5 +- x/distribution/types/expected_keepers.go | 59 +++++-- x/gov/expected_keepers.go | 17 +- x/gov/keeper.go | 12 +- x/gov/tally.go | 9 +- x/slashing/expected_keepers.go | 39 ++++- x/slashing/genesis.go | 3 +- x/slashing/handler.go | 6 +- x/slashing/hooks.go | 6 +- x/slashing/keeper.go | 34 ++-- x/slashing/keeper_test.go | 2 +- x/slashing/querier.go | 2 +- x/staking/expected/expected.go | 37 +++++ x/staking/genesis.go | 9 +- x/staking/handler_test.go | 6 +- x/staking/keeper/alias_functions.go | 36 ++-- x/staking/keeper/delegation.go | 9 +- x/staking/keeper/hooks.go | 4 + x/staking/keeper/invariants.go | 3 +- x/staking/keeper/keeper.go | 10 +- x/staking/keeper/slash.go | 2 +- x/staking/keeper/val_state_change.go | 8 +- x/staking/keeper/validator.go | 6 +- x/staking/types/delegation.go | 9 +- x/staking/types/expected_keepers.go | 68 +++++++- x/staking/types/hooks.go | 4 +- x/staking/types/validator.go | 36 ++-- 36 files changed, 385 insertions(+), 268 deletions(-) create mode 100644 .pending/improvements/sdk/3928-remove-types-staking create mode 100644 x/staking/expected/expected.go diff --git a/.pending/improvements/sdk/3928-remove-types-staking b/.pending/improvements/sdk/3928-remove-types-staking new file mode 100644 index 0000000000..c7bc9f789d --- /dev/null +++ b/.pending/improvements/sdk/3928-remove-types-staking @@ -0,0 +1,2 @@ +#3928 remove staking references from types package +#1528 define local module interfaces instead of /types \ No newline at end of file diff --git a/docs/spec/staking/04_end_block.md b/docs/spec/staking/04_end_block.md index 08b5fd7335..22d9c05e96 100644 --- a/docs/spec/staking/04_end_block.md +++ b/docs/spec/staking/04_end_block.md @@ -42,8 +42,8 @@ Each block the validator queue is to be checked for mature unbonding validators (namely with a completion time <= current time). At this point any mature validators which do not have any delegations remaining are deleted from state. For all other mature unbonding validators that still have remaining -delegations, the validator.Status is switched from sdk.Unbonding to -sdk.Unbonded. +delegations, the `validator.Status` is switched from `sdk.Unbonding` to +`sdk.Unbonded`. ### Unbonding Delegations diff --git a/simapp/export.go b/simapp/export.go index 1447750901..8ce3358197 100644 --- a/simapp/export.go +++ b/simapp/export.go @@ -11,6 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // ExportAppStateAndValidators exports the state of the application for a genesis @@ -63,7 +64,7 @@ func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []str /* Handle fee distribution state. */ // withdraw all validator commission - app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val expected.ValidatorI) (stop bool) { _, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) return false }) @@ -85,7 +86,7 @@ func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []str ctx = ctx.WithBlockHeight(0) // reinitialize all validators - app.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + app.stakingKeeper.IterateValidators(ctx, func(_ int64, val expected.ValidatorI) (stop bool) { // donate any unwithdrawn outstanding reward fraction tokens to the community pool scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) diff --git a/types/staking.go b/types/staking.go index 3ff844b8c9..ca4111a1d0 100644 --- a/types/staking.go +++ b/types/staking.go @@ -2,19 +2,10 @@ package types import ( "math/big" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" ) -// status of a validator -type BondStatus byte - // staking constants const ( - Unbonded BondStatus = 0x00 - Unbonding BondStatus = 0x01 - Bonded BondStatus = 0x02 // default bond denomination DefaultBondDenom = "stake" @@ -25,26 +16,8 @@ const ( // Constant as this should not change without a hard fork. // TODO: Link to some Tendermint docs, this is very unobvious. ValidatorUpdateDelay int64 = 1 - - BondStatusUnbonded = "Unbonded" - BondStatusUnbonding = "Unbonding" - BondStatusBonded = "Bonded" ) -// String implements the Stringer interface for BondStatus. -func (b BondStatus) String() string { - switch b { - case 0x00: - return BondStatusUnbonded - case 0x01: - return BondStatusUnbonding - case 0x02: - return BondStatusBonded - default: - panic("invalid bond status") - } -} - // PowerReduction is the amount of staking tokens required for 1 unit of Tendermint power var PowerReduction = NewIntFromBigInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(6), nil)) @@ -58,112 +31,35 @@ func TokensFromTendermintPower(power int64) Int { return NewInt(power).Mul(PowerReduction) } -// nolint +// BondStatus is the status of a validator +type BondStatus byte + +// staking constants +const ( + Unbonded BondStatus = 0x00 + Unbonding BondStatus = 0x01 + Bonded BondStatus = 0x02 + + BondStatusUnbonded = "Unbonded" + BondStatusUnbonding = "Unbonding" + BondStatusBonded = "Bonded" +) + +// Equal compares two BondStatus instances func (b BondStatus) Equal(b2 BondStatus) bool { return byte(b) == byte(b2) } -// validator for a delegated proof of stake system -type Validator interface { - IsJailed() bool // whether the validator is jailed - GetMoniker() string // moniker of the validator - GetStatus() BondStatus // status of the validator - GetOperator() ValAddress // operator address to receive/return validators coins - GetConsPubKey() crypto.PubKey // validation consensus pubkey - GetConsAddr() ConsAddress // validation consensus address - GetTokens() Int // validation tokens - GetBondedTokens() Int // validator bonded tokens - GetTendermintPower() int64 // validation power in tendermint - GetCommission() Dec // validator commission rate - GetMinSelfDelegation() Int // validator minimum self delegation - GetDelegatorShares() Dec // total outstanding delegator shares - TokensFromShares(Dec) Dec // token worth of provided delegator shares - TokensFromSharesTruncated(Dec) Dec // token worth of provided delegator shares, truncated - TokensFromSharesRoundUp(Dec) Dec // token worth of provided delegator shares, rounded up - SharesFromTokens(amt Int) (Dec, Error) // shares worth of delegator's bond - SharesFromTokensTruncated(amt Int) (Dec, Error) // truncated shares worth of delegator's bond -} - -// validator which fulfills abci validator interface for use in Tendermint -func ABCIValidator(v Validator) abci.Validator { - return abci.Validator{ - Address: v.GetConsPubKey().Address(), - Power: v.GetTendermintPower(), +// String implements the Stringer interface for BondStatus. +func (b BondStatus) String() string { + switch b { + case 0x00: + return BondStatusUnbonded + case 0x01: + return BondStatusUnbonding + case 0x02: + return BondStatusBonded + default: + panic("invalid bond status") } } - -// properties for the set of all validators -type ValidatorSet interface { - // iterate through validators by operator address, execute func for each validator - IterateValidators(Context, - func(index int64, validator Validator) (stop bool)) - - // iterate through bonded validators by operator address, execute func for each validator - IterateBondedValidatorsByPower(Context, - func(index int64, validator Validator) (stop bool)) - - // iterate through the consensus validator set of the last block by operator address, execute func for each validator - IterateLastValidators(Context, - func(index int64, validator Validator) (stop bool)) - - Validator(Context, ValAddress) Validator // get a particular validator by operator address - ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address - TotalBondedTokens(Context) Int // total bonded tokens within the validator set - TotalTokens(Context) Int // total token supply - - // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction - Slash(Context, ConsAddress, int64, int64, Dec) - Jail(Context, ConsAddress) // jail a validator - Unjail(Context, ConsAddress) // unjail a validator - - // Delegation allows for getting a particular delegation for a given validator - // and delegator outside the scope of the staking module. - Delegation(Context, AccAddress, ValAddress) Delegation - - // MaxValidators returns the maximum amount of bonded validators - MaxValidators(Context) uint16 -} - -//_______________________________________________________________________________ - -// delegation bond for a delegated proof of stake system -type Delegation interface { - GetDelegatorAddr() AccAddress // delegator AccAddress for the bond - GetValidatorAddr() ValAddress // validator operator address - GetShares() Dec // amount of validator's shares held in this delegation -} - -// properties for the set of all delegations for a particular -type DelegationSet interface { - GetValidatorSet() ValidatorSet // validator set for which delegation set is based upon - - // iterate through all delegations from one delegator by validator-AccAddress, - // execute func for each validator - IterateDelegations(ctx Context, delegator AccAddress, - fn func(index int64, delegation Delegation) (stop bool)) -} - -//_______________________________________________________________________________ -// Event Hooks -// These can be utilized to communicate between a staking keeper and another -// keeper which must take particular actions when validators/delegators change -// state. The second keeper must implement this interface, which then the -// staking keeper can call. - -// TODO refactor event hooks out to the receiver modules - -// event hooks for staking validator object -type StakingHooks interface { - AfterValidatorCreated(ctx Context, valAddr ValAddress) // Must be called when a validator is created - BeforeValidatorModified(ctx Context, valAddr ValAddress) // Must be called when a validator's state changes - AfterValidatorRemoved(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is deleted - - AfterValidatorBonded(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator is bonded - AfterValidatorBeginUnbonding(ctx Context, consAddr ConsAddress, valAddr ValAddress) // Must be called when a validator begins unbonding - - BeforeDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is created - BeforeDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation's shares are modified - BeforeDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is removed - AfterDelegationModified(ctx Context, delAddr AccAddress, valAddr ValAddress) - BeforeValidatorSlashed(ctx Context, valAddr ValAddress, fraction Dec) -} diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index abc2575b2c..11e7b61485 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -6,6 +6,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // allocate fees handles distribution of the collected fees @@ -82,8 +83,8 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, sumPreviousPrecommitPower, total } -// allocate tokens to a particular validator, splitting according to commission -func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, tokens sdk.DecCoins) { +// AllocateTokensToValidator allocate tokens to a particular validator, splitting according to commission +func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val expected.ValidatorI, tokens sdk.DecCoins) { // split tokens between validator and delegators according to commission commission := tokens.MulDec(val.GetCommission()) diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 223d168797..11e01e2c75 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // initialize starting info for a new delegation @@ -27,7 +28,7 @@ func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sd } // calculate the rewards accrued by a delegation between two periods -func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Validator, +func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val expected.ValidatorI, startingPeriod, endingPeriod uint64, stake sdk.Dec) (rewards sdk.DecCoins) { // sanity check if startingPeriod > endingPeriod { @@ -52,7 +53,7 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid } // calculate the total rewards accrued by a delegation -func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation, endingPeriod uint64) (rewards sdk.DecCoins) { +func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val expected.ValidatorI, del expected.DelegationI, endingPeriod uint64) (rewards sdk.DecCoins) { // fetch starting info for delegation startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) @@ -132,7 +133,7 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d return rewards } -func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) (sdk.Coins, sdk.Error) { +func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val expected.ValidatorI, del expected.DelegationI) (sdk.Coins, sdk.Error) { // check existence of delegator starting info if !k.HasDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) { return nil, types.ErrNoDelegationDistInfo(k.codespace) diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index 07d7d23195..01ef5ef118 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // Wrapper struct @@ -9,7 +10,7 @@ type Hooks struct { k Keeper } -var _ sdk.StakingHooks = Hooks{} +var _ types.StakingHooks = Hooks{} // Create new distribution hooks func (k Keeper) Hooks() Hooks { return Hooks{k} } @@ -19,8 +20,7 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { val := h.k.stakingKeeper.Validator(ctx, valAddr) h.k.initializeValidator(ctx, val) } -func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { -} + func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { // fetch outstanding @@ -72,12 +72,14 @@ func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr // clear current rewards h.k.DeleteValidatorCurrentRewards(ctx, valAddr) } + func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { val := h.k.stakingKeeper.Validator(ctx, valAddr) // increment period h.k.incrementValidatorPeriod(ctx, val) } + func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { val := h.k.stakingKeeper.Validator(ctx, valAddr) del := h.k.stakingKeeper.Delegation(ctx, delAddr, valAddr) @@ -87,18 +89,19 @@ func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAd panic(err) } } -func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { - // nothing needed here since BeforeDelegationSharesModified will always also be called -} + func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { // create new delegation period record h.k.initializeDelegation(ctx, valAddr, delAddr) } -func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { -} -func (h Hooks) AfterValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { -} + func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { // record the slash event h.k.updateValidatorSlashFraction(ctx, valAddr, fraction) } + +// nolint - unused hooks +func (h Hooks) BeforeValidatorModified(_ sdk.Context, _ sdk.ValAddress) {} +func (h Hooks) AfterValidatorBonded(_ sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) {} +func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} diff --git a/x/distribution/keeper/invariants.go b/x/distribution/keeper/invariants.go index a224ae1c15..a08673d421 100644 --- a/x/distribution/keeper/invariants.go +++ b/x/distribution/keeper/invariants.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // register all distribution invariants @@ -75,7 +76,7 @@ func CanWithdrawInvariant(k Keeper) sdk.Invariant { } // iterate over all validators - k.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + k.stakingKeeper.IterateValidators(ctx, func(_ int64, val expected.ValidatorI) (stop bool) { _, _ = k.WithdrawValidatorCommission(ctx, val.GetOperator()) delegationAddrs, ok := valDelegationAddrs[val.GetOperator().String()] @@ -108,7 +109,7 @@ func ReferenceCountInvariant(k Keeper) sdk.Invariant { return func(ctx sdk.Context) error { valCount := uint64(0) - k.stakingKeeper.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) { + k.stakingKeeper.IterateValidators(ctx, func(_ int64, val expected.ValidatorI) (stop bool) { valCount++ return false }) diff --git a/x/distribution/keeper/querier.go b/x/distribution/keeper/querier.go index 6c172b8b79..8bc5c703c6 100644 --- a/x/distribution/keeper/querier.go +++ b/x/distribution/keeper/querier.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // nolint @@ -257,7 +258,7 @@ func queryDelegatorTotalRewards(ctx sdk.Context, _ []string, req abci.RequestQue k.stakingKeeper.IterateDelegations( ctx, params.DelegatorAddress, - func(_ int64, del sdk.Delegation) (stop bool) { + func(_ int64, del expected.DelegationI) (stop bool) { valAddr := del.GetValidatorAddr() val := k.stakingKeeper.Validator(ctx, valAddr) endingPeriod := k.incrementValidatorPeriod(ctx, val) @@ -292,7 +293,7 @@ func queryDelegatorValidators(ctx sdk.Context, _ []string, req abci.RequestQuery k.stakingKeeper.IterateDelegations( ctx, params.DelegatorAddress, - func(_ int64, del sdk.Delegation) (stop bool) { + func(_ int64, del expected.DelegationI) (stop bool) { validators = append(validators[:], del.GetValidatorAddr()) return false }, diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index 93c20f4793..1aa14dc227 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -4,10 +4,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // initialize rewards for a new validator -func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) { +func (k Keeper) initializeValidator(ctx sdk.Context, val expected.ValidatorI) { // set initial historical rewards (period 0) with reference count of 1 k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), 0, types.NewValidatorHistoricalRewards(sdk.DecCoins{}, 1)) @@ -22,7 +23,7 @@ func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) { } // increment validator period, returning the period just ended -func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uint64 { +func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val expected.ValidatorI) uint64 { // fetch current rewards rewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator()) diff --git a/x/distribution/types/expected_keepers.go b/x/distribution/types/expected_keepers.go index 6bf8eaa0a8..a4b6fdc47f 100644 --- a/x/distribution/types/expected_keepers.go +++ b/x/distribution/types/expected_keepers.go @@ -1,21 +1,60 @@ package types -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/expected" +) -// expected staking keeper +// StakingKeeper expected staking keeper type StakingKeeper interface { + // iterate through validators by operator address, execute func for each validator + IterateValidators(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + // iterate through bonded validators by operator address, execute func for each validator + IterateBondedValidatorsByPower(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + // iterate through the consensus validator set of the last block by operator address, execute func for each validator + IterateLastValidators(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + Validator(sdk.Context, sdk.ValAddress) expected.ValidatorI // get a particular validator by operator address + ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) expected.ValidatorI // get a particular validator by consensus address + TotalBondedTokens(sdk.Context) sdk.Int // total bonded tokens within the validator set + TotalTokens(sdk.Context) sdk.Int // total token supply + + // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction + Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) + Jail(sdk.Context, sdk.ConsAddress) // jail a validator + Unjail(sdk.Context, sdk.ConsAddress) // unjail a validator + + // Delegation allows for getting a particular delegation for a given validator + // and delegator outside the scope of the staking module. + Delegation(sdk.Context, sdk.AccAddress, sdk.ValAddress) expected.DelegationI + + // MaxValidators returns the maximum amount of bonded validators + MaxValidators(sdk.Context) uint16 + IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, - fn func(index int64, delegation sdk.Delegation) (stop bool)) - Delegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Delegation - Validator(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Validator - ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) sdk.Validator + fn func(index int64, delegation expected.DelegationI) (stop bool)) + GetLastTotalPower(ctx sdk.Context) sdk.Int GetLastValidatorPower(ctx sdk.Context, valAddr sdk.ValAddress) int64 - // used for invariants - IterateValidators(ctx sdk.Context, - fn func(index int64, validator sdk.Validator) (stop bool)) - GetAllSDKDelegations(ctx sdk.Context) []sdk.Delegation + GetAllSDKDelegations(ctx sdk.Context) []staking.Delegation +} + +// StakingHooks event hooks for staking validator object +type StakingHooks interface { + AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator is created + AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is deleted + + BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) // Must be called when a delegation is created + BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) // Must be called when a delegation's shares are modified + AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) + BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) } // expected coin keeper diff --git a/x/gov/expected_keepers.go b/x/gov/expected_keepers.go index bc80bcedcb..25524997d6 100644 --- a/x/gov/expected_keepers.go +++ b/x/gov/expected_keepers.go @@ -1,6 +1,9 @@ package gov -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" +) // expected bank keeper type BankKeeper interface { @@ -10,3 +13,15 @@ type BankKeeper interface { SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) sdk.Error SetSendEnabled(ctx sdk.Context, enabled bool) } + +// StakingKeeper expected staking keeper (Validator and Delegator sets) +type StakingKeeper interface { + // iterate through bonded validators by operator address, execute func for each validator + IterateBondedValidatorsByPower(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + TotalBondedTokens(sdk.Context) sdk.Int // total bonded tokens within the validator set + + IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, + fn func(index int64, delegation expected.DelegationI) (stop bool)) +} diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 0b48d942ed..6d1ef798b1 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -30,11 +30,8 @@ type Keeper struct { // The reference to the CoinKeeper to modify balances ck BankKeeper - // The ValidatorSet to get information about validators - vs sdk.ValidatorSet - - // The reference to the DelegationSet to get information about delegators - ds sdk.DelegationSet + // The reference to the DelegationSet and ValidatorSet to get information about validators and delegators + sk StakingKeeper // The (unexposed) keys used to access the stores from the Context. storeKey sdk.StoreKey @@ -56,7 +53,7 @@ type Keeper struct { // - and tallying the result of the vote. func NewKeeper( cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, paramSpace params.Subspace, - ck BankKeeper, ds sdk.DelegationSet, codespace sdk.CodespaceType, rtr Router, + ck BankKeeper, sk StakingKeeper, codespace sdk.CodespaceType, rtr Router, ) Keeper { // It is vital to seal the governance proposal router here as to not allow @@ -69,8 +66,7 @@ func NewKeeper( paramsKeeper: paramsKeeper, paramSpace: paramSpace.WithKeyTable(ParamKeyTable()), ck: ck, - ds: ds, - vs: ds.GetValidatorSet(), + sk: sk, cdc: cdc, codespace: codespace, router: rtr, diff --git a/x/gov/tally.go b/x/gov/tally.go index a54bc3d0e3..312db667b4 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -2,6 +2,7 @@ package gov import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" "github.com/cosmos/cosmos-sdk/x/gov/types" ) @@ -38,7 +39,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn currValidators := make(map[string]validatorGovInfo) // fetch all the bonded validators, insert them into currValidators - keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) { + keeper.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator expected.ValidatorI) (stop bool) { currValidators[validator.GetOperator().String()] = newValidatorGovInfo( validator.GetOperator(), validator.GetBondedTokens(), @@ -59,7 +60,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn currValidators[valAddrStr] = val } else { // iterate over all delegations from voter, deduct from any delegated-to validators - keeper.ds.IterateDelegations(ctx, vote.Voter, func(index int64, delegation sdk.Delegation) (stop bool) { + keeper.sk.IterateDelegations(ctx, vote.Voter, func(index int64, delegation expected.DelegationI) (stop bool) { valAddrStr := delegation.GetValidatorAddr().String() if val, ok := currValidators[valAddrStr]; ok { @@ -100,12 +101,12 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn // TODO: Upgrade the spec to cover all of these cases & remove pseudocode. // If there is no staked coins, the proposal fails - if keeper.vs.TotalBondedTokens(ctx).IsZero() { + if keeper.sk.TotalBondedTokens(ctx).IsZero() { return false, false, tallyResults } // If there is not enough quorum of votes, the proposal fails - percentVoting := totalVotingPower.Quo(keeper.vs.TotalBondedTokens(ctx).ToDec()) + percentVoting := totalVotingPower.Quo(keeper.sk.TotalBondedTokens(ctx).ToDec()) if percentVoting.LT(tallyParams.Quorum) { return false, true, tallyResults } diff --git a/x/slashing/expected_keepers.go b/x/slashing/expected_keepers.go index c0a637abec..988129b6db 100644 --- a/x/slashing/expected_keepers.go +++ b/x/slashing/expected_keepers.go @@ -3,15 +3,40 @@ package slashing import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) -// expected staking keeper -type StakingKeeper interface { - IterateValidators(ctx sdk.Context, - fn func(index int64, validator sdk.Validator) (stop bool)) -} - -// expected bank keeper +// AccountKeeper expected account keeper type AccountKeeper interface { IterateAccounts(ctx sdk.Context, process func(auth.Account) (stop bool)) } + +// StakingKeeper expected staking keeper +type StakingKeeper interface { + // iterate through validators by operator address, execute func for each validator + IterateValidators(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + Validator(sdk.Context, sdk.ValAddress) expected.ValidatorI // get a particular validator by operator address + ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) expected.ValidatorI // get a particular validator by consensus address + + // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction + Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) + Jail(sdk.Context, sdk.ConsAddress) // jail a validator + Unjail(sdk.Context, sdk.ConsAddress) // unjail a validator + + // Delegation allows for getting a particular delegation for a given validator + // and delegator outside the scope of the staking module. + Delegation(sdk.Context, sdk.AccAddress, sdk.ValAddress) expected.DelegationI + + // MaxValidators returns the maximum amount of bonded validators + MaxValidators(sdk.Context) uint16 +} + +// StakingHooks event hooks for staking validator object +type StakingHooks interface { + AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator is created + AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is deleted + + AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is bonded +} diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 11a9907bc6..3cf27670fc 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -5,6 +5,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // GenesisState - all slashing state that must be provided at genesis @@ -79,7 +80,7 @@ func ValidateGenesis(data GenesisState) error { // and the keeper's address to pubkey map func InitGenesis(ctx sdk.Context, keeper Keeper, stakingKeeper StakingKeeper, data GenesisState) { stakingKeeper.IterateValidators(ctx, - func(index int64, validator sdk.Validator) bool { + func(index int64, validator expected.ValidatorI) bool { keeper.addPubkey(ctx, validator.GetConsPubKey()) return false }, diff --git a/x/slashing/handler.go b/x/slashing/handler.go index d3562fe05d..919279a6f1 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -24,13 +24,13 @@ func NewHandler(k Keeper) sdk.Handler { // Validators must submit a transaction to unjail itself after // having been jailed (and thus unbonded) for downtime func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { - validator := k.validatorSet.Validator(ctx, msg.ValidatorAddr) + validator := k.sk.Validator(ctx, msg.ValidatorAddr) if validator == nil { return ErrNoValidatorForAddress(k.codespace).Result() } // cannot be unjailed if no self-delegation exists - selfDel := k.validatorSet.Delegation(ctx, sdk.AccAddress(msg.ValidatorAddr), msg.ValidatorAddr) + selfDel := k.sk.Delegation(ctx, sdk.AccAddress(msg.ValidatorAddr), msg.ValidatorAddr) if selfDel == nil { return ErrMissingSelfDelegation(k.codespace).Result() } @@ -62,7 +62,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { } // unjail the validator - k.validatorSet.Unjail(ctx, consAddr) + k.sk.Unjail(ctx, consAddr) tags := sdk.NewTags( tags.Category, tags.TxCategory, diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 5083ca273d..78ac989740 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -27,7 +27,7 @@ func (k Keeper) AfterValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ // When a validator is created, add the address-pubkey relation. func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { - validator := k.validatorSet.Validator(ctx, valAddr) + validator := k.sk.Validator(ctx, valAddr) k.addPubkey(ctx, validator.GetConsPubKey()) } @@ -38,12 +38,12 @@ func (k Keeper) AfterValidatorRemoved(ctx sdk.Context, address sdk.ConsAddress) //_________________________________________________________________________________________ -// Wrapper struct +// Hooks wrapper struct for slashing keeper type Hooks struct { k Keeper } -var _ sdk.StakingHooks = Hooks{} +var _ StakingHooks = Hooks{} // Return the wrapper struct func (k Keeper) Hooks() Hooks { diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index dbd26db951..6e29f0ab28 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -14,23 +14,23 @@ import ( // Keeper of the slashing store type Keeper struct { - storeKey sdk.StoreKey - cdc *codec.Codec - validatorSet sdk.ValidatorSet - paramspace params.Subspace + storeKey sdk.StoreKey + cdc *codec.Codec + sk StakingKeeper + paramspace params.Subspace // codespace codespace sdk.CodespaceType } // NewKeeper creates a slashing keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspace params.Subspace, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk StakingKeeper, paramspace params.Subspace, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ - storeKey: key, - cdc: cdc, - validatorSet: vs, - paramspace: paramspace.WithKeyTable(ParamKeyTable()), - codespace: codespace, + storeKey: key, + cdc: cdc, + sk: sk, + paramspace: paramspace.WithKeyTable(ParamKeyTable()), + codespace: codespace, } return keeper } @@ -71,8 +71,8 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio } // Get validator and signing info - validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) - if validator == nil || validator.GetStatus() == sdk.Unbonded { + validator := k.sk.ValidatorByConsAddr(ctx, consAddr) + if validator == nil || validator.IsUnbonded() { // Defensive. // Simulation doesn't take unbonding periods into account, and // Tendermint might break this assumption at some point. @@ -108,12 +108,12 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Tendermint. This value is validator.Tokens as sent to Tendermint via // ABCI, and now received as evidence. // The fraction is passed in to separately to slash unbonding and rebonding delegations. - k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, fraction) + k.sk.Slash(ctx, consAddr, distributionHeight, power, fraction) // Jail validator if not already jailed // begin unbonding validator if not already unbonding (tombstone) if !validator.IsJailed() { - k.validatorSet.Jail(ctx, consAddr) + k.sk.Jail(ctx, consAddr) } // Set tombstoned to be true @@ -175,7 +175,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // if we are past the minimum height and the validator has missed too many blocks, punish them if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { - validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) + validator := k.sk.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.IsJailed() { // Downtime confirmed: slash and jail the validator @@ -188,8 +188,8 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. // That's fine since this is just used to filter unbonding delegations & redelegations. distributionHeight := height - sdk.ValidatorUpdateDelay - 1 - k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) - k.validatorSet.Jail(ctx, consAddr) + k.sk.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) + k.sk.Jail(ctx, consAddr) signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeJailDuration(ctx)) // We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding. diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 7b0bd11745..bfb48b8b85 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -114,7 +114,7 @@ func TestPastMaxEvidenceAge(t *testing.T) { keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), power) // should still be bonded - require.True(t, sk.Validator(ctx, operatorAddr).GetStatus() == sdk.Bonded) + require.True(t, sk.Validator(ctx, operatorAddr).IsBonded()) // should still have same power require.Equal(t, oldPower, sk.Validator(ctx, operatorAddr).GetTendermintPower()) diff --git a/x/slashing/querier.go b/x/slashing/querier.go index df6144b585..e937f88a99 100644 --- a/x/slashing/querier.go +++ b/x/slashing/querier.go @@ -94,7 +94,7 @@ func querySigningInfos(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte if params.Limit == 0 { // set the default limit to max bonded if no limit was provided - params.Limit = int(k.validatorSet.MaxValidators(ctx)) + params.Limit = int(k.sk.MaxValidators(ctx)) } var signingInfos []ValidatorSigningInfo diff --git a/x/staking/expected/expected.go b/x/staking/expected/expected.go new file mode 100644 index 0000000000..1f01405a5d --- /dev/null +++ b/x/staking/expected/expected.go @@ -0,0 +1,37 @@ +package expected + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" +) + +// DelegationI delegation bond for a delegated proof of stake system +type DelegationI interface { + GetDelegatorAddr() sdk.AccAddress // delegator sdk.AccAddress for the bond + GetValidatorAddr() sdk.ValAddress // validator operator address + GetShares() sdk.Dec // amount of validator's shares held in this delegation +} + +// ValidatorI expected validator functions +type ValidatorI interface { + IsJailed() bool // whether the validator is jailed + GetMoniker() string // moniker of the validator + GetStatus() sdk.BondStatus // status of the validator + IsBonded() bool // check if has a bonded status + IsUnbonded() bool // check if has status unbonded + IsUnbonding() bool // check if has status unbonding + GetOperator() sdk.ValAddress // operator address to receive/return validators coins + GetConsPubKey() crypto.PubKey // validation consensus pubkey + GetConsAddr() sdk.ConsAddress // validation consensus address + GetTokens() sdk.Int // validation tokens + GetBondedTokens() sdk.Int // validator bonded tokens + GetTendermintPower() int64 // validation power in tendermint + GetCommission() sdk.Dec // validator commission rate + GetMinSelfDelegation() sdk.Int // validator minimum self delegation + GetDelegatorShares() sdk.Dec // total outstanding delegator shares + TokensFromShares(sdk.Dec) sdk.Dec // token worth of provided delegator shares + TokensFromSharesTruncated(sdk.Dec) sdk.Dec // token worth of provided delegator shares, truncated + TokensFromSharesRoundUp(sdk.Dec) sdk.Dec // token worth of provided delegator shares, rounded up + SharesFromTokens(amt sdk.Int) (sdk.Dec, sdk.Error) // shares worth of delegator's bond + SharesFromTokensTruncated(amt sdk.Int) (sdk.Dec, sdk.Error) // truncated shares worth of delegator's bond +} diff --git a/x/staking/genesis.go b/x/staking/genesis.go index 358f9ebac0..c7cb6fbdfc 100644 --- a/x/staking/genesis.go +++ b/x/staking/genesis.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking/expected" "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -53,7 +54,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeep } // Set timeslice if necessary - if validator.Status == sdk.Unbonding { + if validator.IsUnbonding() { keeper.InsertValidatorQueue(ctx, validator) } } @@ -143,7 +144,7 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { // WriteValidators returns a slice of bonded genesis validators. func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { - keeper.IterateLastValidators(ctx, func(_ int64, validator sdk.Validator) (stop bool) { + keeper.IterateLastValidators(ctx, func(_ int64, validator expected.ValidatorI) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetConsPubKey(), Power: validator.GetTendermintPower(), @@ -179,10 +180,10 @@ func validateGenesisStateValidators(validators []types.Validator) (err error) { if _, ok := addrMap[strKey]; ok { return fmt.Errorf("duplicate validator in genesis state: moniker %v, address %v", val.Description.Moniker, val.ConsAddress()) } - if val.Jailed && val.Status == sdk.Bonded { + if val.Jailed && val.IsBonded() { return fmt.Errorf("validator is bonded and jailed in genesis state: moniker %v, address %v", val.Description.Moniker, val.ConsAddress()) } - if val.DelegatorShares.IsZero() && val.Status != sdk.Unbonding { + if val.DelegatorShares.IsZero() && !val.IsUnbonding() { return fmt.Errorf("bonded/unbonded genesis validator cannot have zero delegator shares, validator: %v", val) } addrMap[strKey] = true diff --git a/x/staking/handler_test.go b/x/staking/handler_test.go index b0cc6d183b..429b6610fa 100644 --- a/x/staking/handler_test.go +++ b/x/staking/handler_test.go @@ -702,7 +702,7 @@ func TestValidatorQueue(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + require.True(t, validator.IsUnbonding(), "%v", validator) // should still be unbonding at time 6 seconds later ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) @@ -710,7 +710,7 @@ func TestValidatorQueue(t *testing.T) { validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + require.True(t, validator.IsUnbonding(), "%v", validator) // should be in unbonded state at time 7 seconds later ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) @@ -718,7 +718,7 @@ func TestValidatorQueue(t *testing.T) { validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.True(t, validator.GetStatus() == sdk.Unbonded, "%v", validator) + require.True(t, validator.IsUnbonded(), "%v", validator) } func TestUnbondingPeriod(t *testing.T) { diff --git a/x/staking/keeper/alias_functions.go b/x/staking/keeper/alias_functions.go index a0cf540f69..396e25c2bd 100644 --- a/x/staking/keeper/alias_functions.go +++ b/x/staking/keeper/alias_functions.go @@ -4,14 +4,15 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// Implements ValidatorSet -var _ sdk.ValidatorSet = Keeper{} +//_______________________________________________________________________ +// Validator Set // iterate through the validator set and perform the provided function -func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { +func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator expected.ValidatorI) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.ValidatorsKey) defer iterator.Close() @@ -27,7 +28,7 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato } // iterate through the bonded validator set and perform the provided function -func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { +func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator expected.ValidatorI) (stop bool)) { store := ctx.KVStore(k.storeKey) maxValidators := k.MaxValidators(ctx) @@ -39,7 +40,7 @@ func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index in address := iterator.Value() validator := k.mustGetValidator(ctx, address) - if validator.Status == sdk.Bonded { + if validator.IsBonded() { stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? if stop { break @@ -50,7 +51,7 @@ func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index in } // iterate through the active validator set and perform the provided function -func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { +func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, validator expected.ValidatorI) (stop bool)) { iterator := k.LastValidatorsIterator(ctx) defer iterator.Close() i := int64(0) @@ -69,8 +70,8 @@ func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, vali } } -// get the sdk.validator for a particular address -func (k Keeper) Validator(ctx sdk.Context, address sdk.ValAddress) sdk.Validator { +// Validator gets the Validator interface for a particular address +func (k Keeper) Validator(ctx sdk.Context, address sdk.ValAddress) expected.ValidatorI { val, found := k.GetValidator(ctx, address) if !found { return nil @@ -78,8 +79,8 @@ func (k Keeper) Validator(ctx sdk.Context, address sdk.ValAddress) sdk.Validator return val } -// get the sdk.validator for a particular pubkey -func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.Validator { +// ValidatorByConsAddr gets the validator interface for a particular pubkey +func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) expected.ValidatorI { val, found := k.GetValidatorByConsAddr(ctx, addr) if !found { return nil @@ -112,17 +113,16 @@ func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Int) { k.SetPool(ctx, pool) } -// Implements DelegationSet - -var _ sdk.DelegationSet = Keeper{} +//_______________________________________________________________________ +// Delegation Set // Returns self as it is both a validatorset and delegationset -func (k Keeper) GetValidatorSet() sdk.ValidatorSet { +func (k Keeper) GetValidatorSet() types.ValidatorSet { return k } -// get the delegation for a particular set of delegator and validator addresses -func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.ValAddress) sdk.Delegation { +// Delegation get the delegation interface for a particular set of delegator and validator addresses +func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.ValAddress) expected.DelegationI { bond, ok := k.GetDelegation(ctx, addrDel, addrVal) if !ok { return nil @@ -133,7 +133,7 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk. // iterate through all of the delegations from a delegator func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, - fn func(index int64, del sdk.Delegation) (stop bool)) { + fn func(index int64, del expected.DelegationI) (stop bool)) { store := ctx.KVStore(k.storeKey) delegatorPrefixKey := types.GetDelegationsKey(delAddr) @@ -151,7 +151,7 @@ func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, // return all delegations used during genesis dump // TODO: remove this func, change all usage for iterate functionality -func (k Keeper) GetAllSDKDelegations(ctx sdk.Context) (delegations []sdk.Delegation) { +func (k Keeper) GetAllSDKDelegations(ctx sdk.Context) (delegations []types.Delegation) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.DelegationKey) defer iterator.Close() diff --git a/x/staking/keeper/delegation.go b/x/staking/keeper/delegation.go index 92cb5a8082..f89e931ce0 100644 --- a/x/staking/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -537,7 +537,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // remove the shares and coins from the validator validator, amount = k.RemoveValidatorTokensAndShares(ctx, validator, shares) - if validator.DelegatorShares.IsZero() && validator.Status == sdk.Unbonded { + if validator.DelegatorShares.IsZero() && validator.IsUnbonded() { // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period k.RemoveValidator(ctx, validator.OperatorAddress) } @@ -556,16 +556,17 @@ func (k Keeper) getBeginInfo( // TODO: When would the validator not be found? switch { - case !found || validator.Status == sdk.Bonded: + case !found || validator.IsBonded(): + // the longest wait - just unbonding period from now completionTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) height = ctx.BlockHeight() return completionTime, height, false - case validator.Status == sdk.Unbonded: + case validator.IsUnbonded(): return completionTime, height, true - case validator.Status == sdk.Unbonding: + case validator.IsUnbonding(): return validator.UnbondingCompletionTime, validator.UnbondingHeight, false default: diff --git a/x/staking/keeper/hooks.go b/x/staking/keeper/hooks.go index 6430e90e6d..b8f25908ba 100644 --- a/x/staking/keeper/hooks.go +++ b/x/staking/keeper/hooks.go @@ -2,8 +2,12 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) +// Implements StakingHooks interface +var _ types.StakingHooks = Keeper{} + // AfterValidatorCreated - call hook if registered func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { if k.hooks != nil { diff --git a/x/staking/keeper/invariants.go b/x/staking/keeper/invariants.go index 8b5a2cfd5b..cab337000f 100644 --- a/x/staking/keeper/invariants.go +++ b/x/staking/keeper/invariants.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking/expected" "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -72,7 +73,7 @@ func SupplyInvariants(k Keeper, f types.FeeCollectionKeeper, } return false }) - k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { + k.IterateValidators(ctx, func(_ int64, validator expected.ValidatorI) bool { switch validator.GetStatus() { case sdk.Bonded: bonded = bonded.Add(validator.GetBondedTokens().ToDec()) diff --git a/x/staking/keeper/keeper.go b/x/staking/keeper/keeper.go index 6392f9514d..c231331466 100644 --- a/x/staking/keeper/keeper.go +++ b/x/staking/keeper/keeper.go @@ -13,13 +13,19 @@ import ( const aminoCacheSize = 500 +// Implements ValidatorSet interface +var _ types.ValidatorSet = Keeper{} + +// Implements DelegationSet interface +var _ types.DelegationSet = Keeper{} + // keeper of the staking store type Keeper struct { storeKey sdk.StoreKey storeTKey sdk.StoreKey cdc *codec.Codec bankKeeper types.BankKeeper - hooks sdk.StakingHooks + hooks types.StakingHooks paramstore params.Subspace validatorCache map[string]cachedValidator validatorCacheList *list.List @@ -49,7 +55,7 @@ func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, bk types.BankKeeper, func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/staking") } // Set the validator hooks -func (k *Keeper) SetHooks(sh sdk.StakingHooks) *Keeper { +func (k *Keeper) SetHooks(sh types.StakingHooks) *Keeper { if k.hooks != nil { panic("cannot set validator hooks twice") } diff --git a/x/staking/keeper/slash.go b/x/staking/keeper/slash.go index fd0df8d596..571f5b8c66 100644 --- a/x/staking/keeper/slash.go +++ b/x/staking/keeper/slash.go @@ -48,7 +48,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } // should not be slashing an unbonded validator - if validator.Status == sdk.Unbonded { + if validator.IsUnbonded() { panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) } diff --git a/x/staking/keeper/val_state_change.go b/x/staking/keeper/val_state_change.go index 13419b502c..354fc1d874 100644 --- a/x/staking/keeper/val_state_change.go +++ b/x/staking/keeper/val_state_change.go @@ -120,21 +120,21 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab // Validator state transitions func (k Keeper) bondedToUnbonding(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Bonded { + if !validator.IsBonded() { panic(fmt.Sprintf("bad state transition bondedToUnbonding, validator: %v\n", validator)) } return k.beginUnbondingValidator(ctx, validator) } func (k Keeper) unbondingToBonded(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Unbonding { + if !validator.IsUnbonding() { panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) } return k.bondValidator(ctx, validator) } func (k Keeper) unbondedToBonded(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Unbonded { + if !validator.IsUnbonded() { panic(fmt.Sprintf("bad state transition unbondedToBonded, validator: %v\n", validator)) } return k.bondValidator(ctx, validator) @@ -142,7 +142,7 @@ func (k Keeper) unbondedToBonded(ctx sdk.Context, validator types.Validator) typ // switches a validator from unbonding state to unbonded state func (k Keeper) unbondingToUnbonded(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Unbonding { + if !validator.IsUnbonding() { panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) } return k.completeUnbondingValidator(ctx, validator) diff --git a/x/staking/keeper/validator.go b/x/staking/keeper/validator.go index 9f75416386..37fd625899 100644 --- a/x/staking/keeper/validator.go +++ b/x/staking/keeper/validator.go @@ -187,7 +187,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { return } - if validator.Status != sdk.Unbonded { + if !validator.IsUnbonded() { panic("cannot call RemoveValidator on bonded or unbonding validators") } if validator.Tokens.IsPositive() { @@ -253,7 +253,7 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { address := iterator.Value() validator := k.mustGetValidator(ctx, address) - if validator.Status == sdk.Bonded { + if validator.IsBonded() { validators[i] = validator i++ } @@ -432,7 +432,7 @@ func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { if !found { panic("validator in the unbonding queue was not found") } - if val.GetStatus() != sdk.Unbonding { + if !val.IsUnbonding() { panic("unexpected validator in unbonding queue, status was not unbonding") } k.unbondingToUnbonded(ctx, val) diff --git a/x/staking/types/delegation.go b/x/staking/types/delegation.go index 66ce5796b5..78a75a0f5c 100644 --- a/x/staking/types/delegation.go +++ b/x/staking/types/delegation.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // DVPair is struct that just has a delegator-validator pair with no other data. @@ -28,6 +29,9 @@ type DVVTriplet struct { ValidatorDstAddress sdk.ValAddress } +// Implements Delegation interface +var _ expected.DelegationI = Delegation{} + // Delegation represents the bond with tokens held by an account. It is // owned by one delegator, and is associated with the voting power of one // validator. @@ -75,10 +79,7 @@ func (d Delegation) Equal(d2 Delegation) bool { d.Shares.Equal(d2.Shares) } -// ensure fulfills the sdk validator types -var _ sdk.Delegation = Delegation{} - -// nolint - for sdk.Delegation +// nolint - for Delegation func (d Delegation) GetDelegatorAddr() sdk.AccAddress { return d.DelegatorAddress } func (d Delegation) GetValidatorAddr() sdk.ValAddress { return d.ValidatorAddress } func (d Delegation) GetShares() sdk.Dec { return d.Shares } diff --git a/x/staking/types/expected_keepers.go b/x/staking/types/expected_keepers.go index 430a369058..e319589a2d 100644 --- a/x/staking/types/expected_keepers.go +++ b/x/staking/types/expected_keepers.go @@ -3,6 +3,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // expected coin keeper @@ -22,7 +23,72 @@ type BankKeeper interface { UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) } -// expected bank keeper +// AccountKeeper expected Account keeper type AccountKeeper interface { IterateAccounts(ctx sdk.Context, process func(auth.Account) (stop bool)) } + +// ValidatorSet expected properties for the set of all validators +type ValidatorSet interface { + // iterate through validators by operator address, execute func for each validator + IterateValidators(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + // iterate through bonded validators by operator address, execute func for each validator + IterateBondedValidatorsByPower(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + // iterate through the consensus validator set of the last block by operator address, execute func for each validator + IterateLastValidators(sdk.Context, + func(index int64, validator expected.ValidatorI) (stop bool)) + + Validator(sdk.Context, sdk.ValAddress) expected.ValidatorI // get a particular validator by operator address + ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) expected.ValidatorI // get a particular validator by consensus address + TotalBondedTokens(sdk.Context) sdk.Int // total bonded tokens within the validator set + TotalTokens(sdk.Context) sdk.Int // total token supply + + // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction + Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) + Jail(sdk.Context, sdk.ConsAddress) // jail a validator + Unjail(sdk.Context, sdk.ConsAddress) // unjail a validator + + // Delegation allows for getting a particular delegation for a given validator + // and delegator outside the scope of the staking module. + Delegation(sdk.Context, sdk.AccAddress, sdk.ValAddress) expected.DelegationI + + // MaxValidators returns the maximum amount of bonded validators + MaxValidators(sdk.Context) uint16 +} + +// DelegationSet expected properties for the set of all delegations for a particular +type DelegationSet interface { + GetValidatorSet() ValidatorSet // validator set for which delegation set is based upon + + // iterate through all delegations from one delegator by validator-AccAddress, + // execute func for each validator + IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress, + fn func(index int64, delegation expected.DelegationI) (stop bool)) +} + +//_______________________________________________________________________________ +// Event Hooks +// These can be utilized to communicate between a staking keeper and another +// keeper which must take particular actions when validators/delegators change +// state. The second keeper must implement this interface, which then the +// staking keeper can call. + +// StakingHooks event hooks for staking validator object +type StakingHooks interface { + AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator is created + BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator's state changes + AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is deleted + + AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is bonded + AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator begins unbonding + + BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) // Must be called when a delegation is created + BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) // Must be called when a delegation's shares are modified + BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) // Must be called when a delegation is removed + AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) + BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) +} diff --git a/x/staking/types/hooks.go b/x/staking/types/hooks.go index 83df7bc5af..de04fae8b2 100644 --- a/x/staking/types/hooks.go +++ b/x/staking/types/hooks.go @@ -5,9 +5,9 @@ import ( ) // combine multiple staking hooks, all hook functions are run in array sequence -type MultiStakingHooks []sdk.StakingHooks +type MultiStakingHooks []StakingHooks -func NewMultiStakingHooks(hooks ...sdk.StakingHooks) MultiStakingHooks { +func NewMultiStakingHooks(hooks ...StakingHooks) MultiStakingHooks { return hooks } diff --git a/x/staking/types/validator.go b/x/staking/types/validator.go index 962345aac5..1c380161d8 100644 --- a/x/staking/types/validator.go +++ b/x/staking/types/validator.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/expected" ) // nolint @@ -23,6 +24,9 @@ const ( MaxDetailsLength = 280 ) +// Implements Validator interface +var _ expected.ValidatorI = Validator{} + // Validator defines the total amount of bond shares and their exchange rate to // coins. Slashing results in a decrease in the exchange rate, allowing correct // calculation of future undelegations without iterating over delegators. @@ -55,7 +59,7 @@ func (v Validators) String() (out string) { } // ToSDKValidators - convenience function convert []Validators to []sdk.Validators -func (v Validators) ToSDKValidators() (validators []sdk.Validator) { +func (v Validators) ToSDKValidators() (validators []expected.ValidatorI) { for _, val := range v { validators = append(validators, val) } @@ -201,6 +205,21 @@ func (v Validator) ConsAddress() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } +// IsBonded checks if the validator status equals Bonded +func (v Validator) IsBonded() bool { + return v.GetStatus().Equal(sdk.Bonded) +} + +// IsUnbonded checks if the validator status equals Unbonded +func (v Validator) IsUnbonded() bool { + return v.GetStatus().Equal(sdk.Unbonded) +} + +// IsUnbonding checks if the validator status equals Unbonding +func (v Validator) IsUnbonding() bool { + return v.GetStatus().Equal(sdk.Unbonding) +} + // constant used in flags to indicate that description field should not be updated const DoNotModifyDesc = "[do-not-modify]" @@ -327,7 +346,7 @@ func (v Validator) RemoveTokens(pool Pool, tokens sdk.Int) (Validator, Pool) { } v.Tokens = v.Tokens.Sub(tokens) // TODO: It is not obvious from the name of the function that this will happen. Either justify or move outside. - if v.Status == sdk.Bonded { + if v.IsBonded() { pool = pool.bondedTokensToNotBonded(tokens) } return v, pool @@ -362,7 +381,7 @@ func (v Validator) AddTokensFromDel(pool Pool, amount sdk.Int) (Validator, Pool, issuedShares = shares } - if v.Status == sdk.Bonded { + if v.IsBonded() { pool = pool.notBondedTokensToBonded(amount) } @@ -397,7 +416,7 @@ func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Dec) (Validator, Poo } v.DelegatorShares = remainingShares - if v.Status == sdk.Bonded { + if v.IsBonded() { pool = pool.bondedTokensToNotBonded(issuedTokens) } @@ -449,7 +468,7 @@ func (v Validator) SharesFromTokensTruncated(amt sdk.Int) (sdk.Dec, sdk.Error) { // get the bonded tokens which the validator holds func (v Validator) BondedTokens() sdk.Int { - if v.Status == sdk.Bonded { + if v.IsBonded() { return v.Tokens } return sdk.ZeroInt() @@ -458,7 +477,7 @@ func (v Validator) BondedTokens() sdk.Int { // get the Tendermint Power // a reduction of 10^6 from validator tokens is applied func (v Validator) TendermintPower() int64 { - if v.Status == sdk.Bonded { + if v.IsBonded() { return v.PotentialTendermintPower() } return 0 @@ -469,10 +488,7 @@ func (v Validator) PotentialTendermintPower() int64 { return sdk.TokensToTendermintPower(v.Tokens) } -// ensure fulfills the sdk validator types -var _ sdk.Validator = Validator{} - -// nolint - for sdk.Validator +// nolint - for ValidatorI func (v Validator) IsJailed() bool { return v.Jailed } func (v Validator) GetMoniker() string { return v.Description.Moniker } func (v Validator) GetStatus() sdk.BondStatus { return v.Status }