Skip to content

Commit

Permalink
docs: [Interchain Security] update spec (#12848)
Browse files Browse the repository at this point in the history
* updating staking spec

* clarify code

* fix typo
  • Loading branch information
mpoke authored and sainoe committed Nov 3, 2022
1 parent b4349f2 commit d5ccf0f
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 83 deletions.
4 changes: 2 additions & 2 deletions x/staking/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,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)
Expand Down Expand Up @@ -507,7 +507,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)
Expand Down
55 changes: 20 additions & 35 deletions x/staking/keeper/unbonding.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,46 @@ 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
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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -159,25 +148,21 @@ 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)
if err != nil {
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
Expand Down
2 changes: 1 addition & 1 deletion x/staking/keeper/val_state_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
62 changes: 48 additions & 14 deletions x/staking/spec/01_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ Store entries prefixed with "Last" must remain unchanged until EndBlock.

* LastTotalPower: `0x12 -> ProtocolBuffer(math.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
Expand Down Expand Up @@ -47,10 +62,11 @@ required lookups for slashing and validator-set updates. A third special index
throughout each block, unlike the first two indices which mirror the validator
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`
* LastValidatorsPower: `0x11 | OperatorAddrLen (1 byte) | OperatorAddr -> ProtocolBuffer(ConsensusPower)`
- 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
associated validator, where the public key of that validator can change in the
Expand All @@ -67,6 +83,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).
Expand Down Expand Up @@ -118,14 +137,21 @@ detected.

`UnbondingDelegation` are indexed in the store as:

* UnbondingDelegation: `0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr -> ProtocolBuffer(unbondingDelegation)`
* UnbondingDelegationsFromValidator: `0x33 | ValidatorAddrLen (1 byte) | ValidatorAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil`
- 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.46.0-rc1/proto/cosmos/staking/v1beta1/staking.proto#L207-L220
Expand All @@ -140,13 +166,21 @@ committed by the source validator.

`Redelegation` are indexed in the store as:

* 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`
- 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`.

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`.
`RedelegationsByDst` is used 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:
Expand Down
25 changes: 17 additions & 8 deletions x/staking/spec/02_state_transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ that of the `LastValidator`.

When a validator begins the unbonding process the following operations occur:

* send the `validator.Tokens` from the `BondedPool` to the `NotBondedTokens` `ModuleAccount`
* set `validator.Status` to `Unbonding`
* delete the existing record from `ValidatorByPowerIndex`
* 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
- send the `validator.Tokens` from the `BondedPool` to the `NotBondedTokens` `ModuleAccount`
- set `validator.Status` to `Unbonding`
- delete the existing record from `ValidatorByPowerIndex`
- 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

Expand Down Expand Up @@ -90,6 +92,9 @@ Delegation may be called.
* 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.
* 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`

### Cancel an `UnbondingDelegation` Entry
When a `cancel unbond delegation` occurs both the `validator`, the `delegation` and an `UnbondingDelegationQueue` state will be updated.
Expand All @@ -116,6 +121,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.
Expand Down Expand Up @@ -150,7 +158,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

Expand All @@ -159,7 +168,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

Expand Down
17 changes: 13 additions & 4 deletions x/staking/spec/05_end_block.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)`.
Loading

0 comments on commit d5ccf0f

Please sign in to comment.