diff --git a/x/staking/keeper/delegation.go b/x/staking/keeper/delegation.go index 45214aad4c2a..9cb1409b30c8 100644 --- a/x/staking/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -318,7 +318,7 @@ func (k Keeper) SetUnbondingDelegationEntry( k.SetUnbondingDelegation(ctx, ubd) // Add to the UBDByUnbondingOp index to look up the UBD by the UBDE ID - k.SetUnbondingDelegationByUnbondingIndex(ctx, ubd, id) + k.SetUnbondingDelegationByUnbondingId(ctx, ubd, id) // Call hook k.AfterUnbondingInitiated(ctx, id) @@ -520,7 +520,7 @@ func (k Keeper) SetRedelegationEntry(ctx sdk.Context, k.SetRedelegation(ctx, red) // Add to the UBDByEntry index to look up the UBD by the UBDE ID - k.SetRedelegationByUnbondingIndex(ctx, red, id) + k.SetRedelegationByUnbondingId(ctx, red, id) // Call hook k.AfterUnbondingInitiated(ctx, id) diff --git a/x/staking/keeper/unbonding.go b/x/staking/keeper/unbonding.go index 310008c75c0c..099950e6030d 100644 --- a/x/staking/keeper/unbonding.go +++ b/x/staking/keeper/unbonding.go @@ -8,35 +8,32 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// Increments and returns a unique ID for an UnbondingDelegationEntry -func (k Keeper) IncrementUnbondingId(ctx sdk.Context) (unbondingDelegationEntryId uint64) { +// Increments and returns a unique ID for an unbonding operation +func (k Keeper) IncrementUnbondingId(ctx sdk.Context) (unbondingId uint64) { store := ctx.KVStore(k.storeKey) - bz := store.Get(types.UnbondingDelegationEntryIdKey) + bz := store.Get(types.UnbondingIdKey) if bz == nil { - unbondingDelegationEntryId = 0 + unbondingId = 0 } else { - unbondingDelegationEntryId = binary.BigEndian.Uint64(bz) + unbondingId = binary.BigEndian.Uint64(bz) } - unbondingDelegationEntryId = unbondingDelegationEntryId + 1 + unbondingId = unbondingId + 1 // Convert back into bytes for storage bz = make([]byte, 8) - binary.BigEndian.PutUint64(bz, unbondingDelegationEntryId) + binary.BigEndian.PutUint64(bz, unbondingId) - store.Set(types.UnbondingDelegationEntryIdKey, bz) + store.Set(types.UnbondingIdKey, bz) - return unbondingDelegationEntryId + return unbondingId } -// Remove a ValidatorByUnbondingIndex +// Remove a mapping from UnbondingId to unbonding operation func (k Keeper) DeleteUnbondingIndex(ctx sdk.Context, id uint64) { store := ctx.KVStore(k.storeKey) - - indexKey := types.GetUnbondingIndexKey(id) - - store.Delete(indexKey) + store.Delete(types.GetUnbondingIndexKey(id)) } // return a unbonding delegation that has an unbonding delegation entry with a certain ID @@ -44,15 +41,13 @@ func (k Keeper) GetUnbondingDelegationByUnbondingId( ctx sdk.Context, id uint64, ) (ubd types.UnbondingDelegation, found bool) { store := ctx.KVStore(k.storeKey) - indexKey := types.GetUnbondingIndexKey(id) - ubdeKey := store.Get(indexKey) + ubdeKey := store.Get(types.GetUnbondingIndexKey(id)) if ubdeKey == nil { return types.UnbondingDelegation{}, false } value := store.Get(ubdeKey) - if value == nil { return types.UnbondingDelegation{}, false } @@ -71,15 +66,13 @@ func (k Keeper) GetRedelegationByUnbondingId( ctx sdk.Context, id uint64, ) (red types.Redelegation, found bool) { store := ctx.KVStore(k.storeKey) - indexKey := types.GetUnbondingIndexKey(id) - redKey := store.Get(indexKey) + redKey := store.Get(types.GetUnbondingIndexKey(id)) if redKey == nil { return types.Redelegation{}, false } value := store.Get(redKey) - if value == nil { return types.Redelegation{}, false } @@ -98,15 +91,13 @@ func (k Keeper) GetValidatorByUnbondingId( ctx sdk.Context, id uint64, ) (val types.Validator, found bool) { store := ctx.KVStore(k.storeKey) - indexKey := types.GetUnbondingIndexKey(id) - valKey := store.Get(indexKey) + valKey := store.Get(types.GetUnbondingIndexKey(id)) if valKey == nil { return types.Validator{}, false } value := store.Get(valKey) - if value == nil { return types.Validator{}, false } @@ -121,7 +112,7 @@ func (k Keeper) GetValidatorByUnbondingId( } // Set an index to look up an UnbondingDelegation by the unbondingId of an UnbondingDelegationEntry that it contains -func (k Keeper) SetUnbondingDelegationByUnbondingIndex(ctx sdk.Context, ubd types.UnbondingDelegation, id uint64) { +func (k Keeper) SetUnbondingDelegationByUnbondingId(ctx sdk.Context, ubd types.UnbondingDelegation, id uint64) { store := ctx.KVStore(k.storeKey) delAddr, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress) @@ -134,14 +125,12 @@ func (k Keeper) SetUnbondingDelegationByUnbondingIndex(ctx sdk.Context, ubd type panic(err) } - indexKey := types.GetUnbondingIndexKey(id) ubdKey := types.GetUBDKey(delAddr, valAddr) - - store.Set(indexKey, ubdKey) + store.Set(types.GetUnbondingIndexKey(id), ubdKey) } // Set an index to look up an Redelegation by the unbondingId of an RedelegationEntry that it contains -func (k Keeper) SetRedelegationByUnbondingIndex(ctx sdk.Context, red types.Redelegation, id uint64) { +func (k Keeper) SetRedelegationByUnbondingId(ctx sdk.Context, red types.Redelegation, id uint64) { store := ctx.KVStore(k.storeKey) delAddr, err := sdk.AccAddressFromBech32(red.DelegatorAddress) @@ -159,14 +148,12 @@ func (k Keeper) SetRedelegationByUnbondingIndex(ctx sdk.Context, red types.Redel panic(err) } - indexKey := types.GetUnbondingIndexKey(id) redKey := types.GetREDKey(delAddr, valSrcAddr, valDstAddr) - - store.Set(indexKey, redKey) + store.Set(types.GetUnbondingIndexKey(id), redKey) } // Set an index to look up a Validator by the unbondingId corresponding to its current unbonding -func (k Keeper) SetValidatorByUnbondingIndex(ctx sdk.Context, val types.Validator, id uint64) { +func (k Keeper) SetValidatorByUnbondingId(ctx sdk.Context, val types.Validator, id uint64) { store := ctx.KVStore(k.storeKey) valAddr, err := sdk.ValAddressFromBech32(val.OperatorAddress) @@ -174,10 +161,8 @@ func (k Keeper) SetValidatorByUnbondingIndex(ctx sdk.Context, val types.Validato panic(err) } - indexKey := types.GetUnbondingIndexKey(id) valKey := types.GetValidatorKey(valAddr) - - store.Set(indexKey, valKey) + store.Set(types.GetUnbondingIndexKey(id), valKey) } // unbondingDelegationEntryArrayIndex and redelegationEntryArrayIndex are utilities to find diff --git a/x/staking/keeper/val_state_change.go b/x/staking/keeper/val_state_change.go index 01f7f0be03f5..289a4f92af52 100644 --- a/x/staking/keeper/val_state_change.go +++ b/x/staking/keeper/val_state_change.go @@ -357,7 +357,7 @@ func (k Keeper) BeginUnbondingValidator(ctx sdk.Context, validator types.Validat } k.AfterValidatorBeginUnbonding(ctx, consAddr, validator.GetOperator()) - k.SetValidatorByUnbondingIndex(ctx, validator, id) + k.SetValidatorByUnbondingId(ctx, validator, id) k.AfterUnbondingInitiated(ctx, id) diff --git a/x/staking/spec/01_state.md b/x/staking/spec/01_state.md index 656862798b4d..b31c732e51c2 100644 --- a/x/staking/spec/01_state.md +++ b/x/staking/spec/01_state.md @@ -15,6 +15,21 @@ Store entries prefixed with "Last" must remain unchanged until EndBlock. - LastTotalPower: `0x12 -> ProtocolBuffer(sdk.Int)` +## ValidatorUpdates + +ValidatorUpdates contains the validator updates returned to ABCI at the end of every block. +The values are overwritten in every block. + +- ValidatorUpdates `0x51 -> []abci.ValidatorUpdate` + +## UnbondingId + +UnbondingId stores the ID of the latest unbonding operation. It enables to create unique IDs for +unbonding operation, i.e., UnbondingId is incremented every time a new unbonding operation +(validator unbonding, unbonding delegation, redelegation) is initiated. + +- UnbondingId: `0x37 -> uint64` + ## Params Params is a module-wide configuration structure that stores system parameters @@ -52,6 +67,7 @@ records within a block. - Validators: `0x21 | OperatorAddrLen (1 byte) | OperatorAddr -> ProtocolBuffer(validator)` - ValidatorsByConsAddr: `0x22 | ConsAddrLen (1 byte) | ConsAddr -> OperatorAddr` - ValidatorsByPower: `0x23 | BigEndian(ConsensusPower) | OperatorAddrLen (1 byte) | OperatorAddr -> OperatorAddr` +- ValidatorsByUnbondingId: `0x38 | UnbondingId -> 0x21 | OperatorAddrLen (1 byte) | OperatorAddr` - LastValidatorsPower: `0x11 | OperatorAddrLen (1 byte) | OperatorAddr -> ProtocolBuffer(ConsensusPower)` `Validators` is the primary index - it ensures that each operator can have only one @@ -69,6 +85,9 @@ potential validators to quickly determine the current active set. Here ConsensusPower is validator.Tokens/10^6 by default. Note that all validators where `Jailed` is true are not stored within this index. +`ValidatorsByUnbondingId` is an additional index that enables lookups for +validators by the unbonding IDs corresponding to their current unbonding. + `LastValidatorsPower` is a special index that provides a historical list of the last-block's bonded validators. This index remains constant during a block but is updated during the validator set update process which takes place in [`EndBlock`](./05_end_block.md). @@ -122,12 +141,19 @@ detected. - UnbondingDelegation: `0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr -> ProtocolBuffer(unbondingDelegation)` - UnbondingDelegationsFromValidator: `0x33 | ValidatorAddrLen (1 byte) | ValidatorAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil` +- UnbondingDelegationByUnbondingId: `0x38 | UnbondingId -> 0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr` + +`UnbondingDelegation` is used in queries, to lookup all unbonding delegations for +a given delegator. -The first map here is used in queries, to lookup all unbonding delegations for -a given delegator, while the second map is used in slashing, to lookup all +`UnbondingDelegationsFromValidator` is used in slashing, to lookup all unbonding delegations associated with a given validator that need to be slashed. +`UnbondingDelegationByUnbondingId` is an additional index that enables +lookups for unbonding delegations by the unbonding IDs of the containing +unbonding delegation entries. + A UnbondingDelegation object is created every time an unbonding is initiated. +++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L172-L198 @@ -145,10 +171,18 @@ committed by the source validator. - Redelegations: `0x34 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddr -> ProtocolBuffer(redelegation)` - RedelegationsBySrc: `0x35 | ValidatorSrcAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddrLen (1 byte) | ValidatorDstAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil` - RedelegationsByDst: `0x36 | ValidatorDstAddrLen (1 byte) | ValidatorDstAddr | ValidatorSrcAddrLen (1 byte) | ValidatorSrcAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil` +- RedelegationByUnbondingId: `0x38 | UnbondingId -> 0x34 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddr` + +`Redelegations` is used for queries, to lookup all redelegations for a given +delegator. + +`RedelegationsBySrc` is used for slashing based on the `ValidatorSrcAddr`. + +`RedelegationsByDst` is used for slashing based on the `ValidatorDstAddr` -The first map here is used for queries, to lookup all redelegations for a given -delegator. The second map is used for slashing based on the `ValidatorSrcAddr`, -while the third map is for slashing based on the `ValidatorDstAddr`. +`RedelegationByUnbondingId` is an additional index that enables +lookups for redelegations by the unbonding IDs of the containing +redelegation entries. A redelegation object is created every time a redelegation occurs. To prevent "redelegation hopping" redelegations may not occur under the situation that: diff --git a/x/staking/spec/02_state_transitions.md b/x/staking/spec/02_state_transitions.md index d62761d16a5a..5766f9cb24b9 100644 --- a/x/staking/spec/02_state_transitions.md +++ b/x/staking/spec/02_state_transitions.md @@ -41,6 +41,8 @@ When a validator begins the unbonding process the following operations occur: - add a new updated record to the `ValidatorByPowerIndex` - update the `Validator` object for this validator - insert a new record into the `ValidatorQueue` for this validator +- get a unique `unbondingId` and map it to the validator in `ValidatorsByUnbondingId` +- call the `AfterUnbondingInitiated(unbondingId)` hook ### Unbonding to Unbonded @@ -83,7 +85,7 @@ As a part of the Undelegate and Complete Unbonding state transitions Unbond Delegation may be called. - subtract the unbonded shares from delegator -- if the validator is `Unbonding` or `Bonded` add the tokens to an `UnbondingDelegation` Entry +- if the validator is `Unbonding` or `Bonded` add the tokens to an `UnbondingDelegationEntry` - if the validator is `Unbonded` send the tokens directly to the withdraw account - update the delegation or remove the delegation if there are no more shares @@ -91,7 +93,10 @@ Delegation may be called. - update the validator with removed the delegator shares and associated coins - if the validator state is `Bonded`, transfer the `Coins` worth of the unbonded shares from the `BondedPool` to the `NotBondedPool` `ModuleAccount` -- remove the validator if it is unbonded and there are no more delegation shares. +- remove the validator if it is unbonded and there are no more delegation shares +- get a unique `unbondingId` and map it to the `UnbondingDelegationEntry` in `UnbondingDelegationByUnbondingId` +- call the `AfterUnbondingInitiated(unbondingId)` hook +- add the unbonding delegation to `UnbondingDelegationQueue` with the completion time set to `UnbondingTime` ### Complete Unbonding @@ -112,6 +117,9 @@ Redelegations affect the delegation, source and destination validators. - otherwise, if the `sourceValidator.Status` is not `Bonded`, and the `destinationValidator` is `Bonded`, transfer the newly delegated tokens from the `NotBondedPool` to the `BondedPool` `ModuleAccount` - record the token amount in an new entry in the relevant `Redelegation` +- get a unique `unbondingId` and map it to the `RedelegationEntry` in `RedelegationByUnbondingId` +- call the `AfterUnbondingInitiated(unbondingId)` hook +- add the redelegation to `RedelegationQueue` with the completion time set to `UnbondingTime` From when a redelegation begins until it completes, the delegator is in a state of "pseudo-unbonding", and can still be slashed for infractions that occured before the redelegation began. @@ -146,7 +154,8 @@ Put otherwise, validators are not slashed retroactively, only when they are caug When a validator is slashed, so are those unbonding delegations from the validator that began unbonding after the time of the infraction. Every entry in every unbonding delegation from the validator is slashed by `slashFactor`. The amount slashed is calculated from the `InitialBalance` of the -delegation and is capped to prevent a resulting negative balance. Completed (or mature) unbondings are not slashed. +delegation and is capped to prevent a resulting negative balance. Completed (i.e., mature and not on hold) +unbondings are not slashed. ### Slash Redelegation @@ -155,7 +164,7 @@ infraction. Redelegations are slashed by `slashFactor`. Redelegations that began before the infraction are not slashed. The amount slashed is calculated from the `InitialBalance` of the delegation and is capped to prevent a resulting negative balance. -Mature redelegations (that have completed pseudo-unbonding) are not slashed. +Completed (i.e., mature and not on hold) redelegations are not slashed. ## How Shares are calculated diff --git a/x/staking/spec/05_end_block.md b/x/staking/spec/05_end_block.md index c097579e092a..70be7763b756 100644 --- a/x/staking/spec/05_end_block.md +++ b/x/staking/spec/05_end_block.md @@ -35,10 +35,11 @@ is calculated during `EndBlock`. ## Queues Within staking, certain state-transitions are not instantaneous but take place -over a duration of time (typically the unbonding period). When these -transitions are mature certain operations must take place in order to complete -the state operation. This is achieved through the use of queues which are -checked/processed at the end of each block. +over a duration of time (typically the unbonding period). We refer to these +transitions as unbonding operations. When these transitions are mature certain +operations must take place in order to complete the unbonding operation. +This is achieved through the use of queues which are checked/processed at the +end of each block. ### Unbonding Validators @@ -75,3 +76,11 @@ Complete the unbonding of all mature `Redelegation.Entries` within the - remove the mature entry from `Redelegation.Entries` - remove the `Redelegation` object from the store if there are no remaining entries. + +## Putting unbonding operations on hold + +Unbonding operations can be put on hold by external modules via the `PutUnbondingOnHold(unbondingId)` method. +As a result, an unbonding operation (e.g., an unbonding delegation) that is on hold, cannot complete +even if it reaches maturity. For an unbonding operation with `unbondingId` to eventually complete +(after it reaches maturity), every call to `PutUnbondingOnHold(unbondingId)` must be matched +by a call to `UnbondingCanComplete(unbondingId)`. \ No newline at end of file diff --git a/x/staking/spec/06_hooks.md b/x/staking/spec/06_hooks.md index d4c3228eca7a..3c1d2cc2bb20 100644 --- a/x/staking/spec/06_hooks.md +++ b/x/staking/spec/06_hooks.md @@ -25,3 +25,5 @@ following hooks can registered with staking: - called when a delegation's shares are modified - `BeforeDelegationRemoved(Context, AccAddress, ValAddress)` - called when a delegation is removed +- `AfterUnbondingInitiated(Context, UnbondingID)` + - called when an unbonding operation (validator unbonding, unbonding delegation, redelegation) was initiated diff --git a/x/staking/types/keys.go b/x/staking/types/keys.go index 2047aeda7a50..187e98bee3de 100644 --- a/x/staking/types/keys.go +++ b/x/staking/types/keys.go @@ -46,15 +46,15 @@ var ( RedelegationByValSrcIndexKey = []byte{0x35} // prefix for each key for an redelegation, by source validator operator RedelegationByValDstIndexKey = []byte{0x36} // prefix for each key for an redelegation, by destination validator operator - UnbondingDelegationEntryIdKey = []byte{0x37} // key for the counter for the incrementing id for UnbondingDelegationEntries - UnbondingIndexKey = []byte{0x38} // prefix for an index for looking up UnbondingDelegations by UnbondingDelegationEntry ID + UnbondingIdKey = []byte{0x37} // key for the counter for the incrementing id for UnbondingOperations + UnbondingIndexKey = []byte{0x38} // prefix for an index for looking up unbonding operations by their IDs UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue HistoricalInfoKey = []byte{0x50} // prefix for the historical info - ValidatorUpdatesKey = []byte{0x51} // prefix for the end block validator updates transient key + ValidatorUpdatesKey = []byte{0x51} // prefix for the end block validator updates key ) // Returns a key for the index for looking up UnbondingDelegations by the UnbondingDelegationEntries they contain