Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: [Interchain Security] update spec #12848

Merged
merged 4 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions x/staking/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,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 @@ -441,7 +441,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 @@ -361,7 +361,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
44 changes: 39 additions & 5 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(sdk.Int)`

## ValidatorUpdates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this optional? normal staking chains shouldn't need to store this data

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without it the call to GetValidatorUpdates, which is exposed by the staking keeper, will fail.


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

Expand Down Expand Up @@ -83,15 +85,18 @@ 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
- if the delegation is the operator of the validator and no more shares exist then trigger a jail validator
- 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

Expand All @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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

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)`.
2 changes: 2 additions & 0 deletions x/staking/spec/06_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions x/staking/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,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
mpoke marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down