cosmos-sdk/docs/spec/staking/transactions.md

344 lines
9.8 KiB
Markdown
Raw Normal View History

2018-05-08 14:35:24 -07:00
### Transaction Overview
2018-05-25 15:52:34 -07:00
In this section we describe the processing of the transactions and the
2018-05-29 11:50:35 -07:00
corresponding updates to the state. Transactions:
2018-05-25 15:52:34 -07:00
- TxCreateValidator
- TxEditValidator
- TxDelegation
- TxStartUnbonding
- TxCompleteUnbonding
- TxRedelegate
- TxCompleteRedelegation
2018-05-25 15:52:34 -07:00
2018-05-29 11:50:35 -07:00
Other important state changes:
- Update Validators
2018-05-25 15:52:34 -07:00
Other notes:
- `tx` denotes a reference to the transaction being processed
- `sender` denotes the address of the sender of the transaction
- `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and
modify objects from the store
2018-05-30 12:04:14 -07:00
- `sdk.Rat` refers to a rational numeric type specified by the SDK.
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
### TxCreateValidator
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
A validator is created using the `TxCreateValidator` transaction.
2018-05-08 14:35:24 -07:00
```golang
2018-05-25 15:52:34 -07:00
type TxCreateValidator struct {
OwnerAddr sdk.Address
2018-05-08 14:35:24 -07:00
ConsensusPubKey crypto.PubKey
GovernancePubKey crypto.PubKey
2018-05-25 15:52:34 -07:00
SelfDelegation coin.Coin
2018-05-08 14:35:24 -07:00
Description Description
2018-05-25 15:52:34 -07:00
Commission sdk.Rat
CommissionMax sdk.Rat
CommissionMaxChange sdk.Rat
2018-05-08 14:35:24 -07:00
}
2018-05-25 15:52:34 -07:00
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
createValidator(tx TxCreateValidator):
validator = getValidator(tx.OwnerAddr)
if validator != nil return // only one validator per address
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
validator = NewValidator(OwnerAddr, ConsensusPubKey, GovernancePubKey, Description)
init validator poolShares, delegatorShares set to 0 //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
init validator commision fields from tx
validator.PoolShares = 0
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
setValidator(validator)
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
txDelegate = TxDelegate(tx.OwnerAddr, tx.OwnerAddr, tx.SelfDelegation)
delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate)
return
2018-05-08 14:35:24 -07:00
```
2018-05-25 15:52:34 -07:00
### TxEditValidator
2018-05-08 14:35:24 -07:00
If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account:
```golang
type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey
2018-05-25 15:52:34 -07:00
Commission sdk.Rat
2018-05-08 14:35:24 -07:00
Description Description
}
editCandidacy(tx TxEditCandidacy):
2018-05-25 15:52:34 -07:00
validator = getValidator(tx.ValidatorAddr)
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
if tx.Commission > CommissionMax || tx.Commission < 0 return halt tx
if rateChange(tx.Commission) > CommissionMaxChange return halt tx
validator.Commission = tx.Commission
if tx.GovernancePubKey != nil validator.GovernancePubKey = tx.GovernancePubKey
if tx.Description != nil validator.Description = tx.Description
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
setValidator(store, validator)
2018-05-08 14:35:24 -07:00
return
```
2018-05-25 15:52:34 -07:00
### TxDelegation
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
Within this transaction the delegator provides coins, and in return receives
some amount of their validator's delegator-shares that are assigned to
`Delegation.Shares`.
2018-05-08 14:35:24 -07:00
```golang
type TxDelegate struct {
2018-05-25 15:52:34 -07:00
DelegatorAddr sdk.Address
ValidatorAddr sdk.Address
Amount sdk.Coin
2018-05-08 14:35:24 -07:00
}
delegate(tx TxDelegate):
2018-05-25 15:52:34 -07:00
pool = getPool()
if validator.Status == Revoked return
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr)
if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr)
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool)
delegation.Shares += issuedDelegatorShares
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
setDelegation(delegation)
updateValidator(validator)
setPool(pool)
return
2018-05-08 14:35:24 -07:00
```
### TxStartUnbonding
2018-05-08 14:35:24 -07:00
Delegator unbonding is defined with the following transaction:
```golang
type TxStartUnbonding struct {
2018-05-25 15:52:34 -07:00
DelegatorAddr sdk.Address
ValidatorAddr sdk.Address
Shares string
2018-05-08 14:35:24 -07:00
}
startUnbonding(tx TxStartUnbonding):
2018-05-25 15:52:34 -07:00
delegation, found = getDelegatorBond(store, sender, tx.PubKey)
if !found == nil return
if tx.Shares == "MAX" {
2018-05-25 15:52:34 -07:00
if !bond.Shares.GT(sdk.ZeroRat()) {
return ErrNotEnoughBondShares
2018-05-08 14:35:24 -07:00
else
2018-05-25 15:52:34 -07:00
var err sdk.Error
delShares, err = sdk.NewRatFromDecimal(tx.Shares)
2018-05-25 15:52:34 -07:00
if err != nil
return err
if bond.Shares.LT(delShares)
return ErrNotEnoughBondShares
2018-05-08 14:35:24 -07:00
validator, found := GetValidator(tx.ValidatorAddr)
2018-05-25 15:52:34 -07:00
if !found {
return err
2018-05-08 14:35:24 -07:00
if tx.Shares == "MAX"
2018-05-25 15:52:34 -07:00
delShares = bond.Shares
bond.Shares -= delShares
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
revokeCandidacy := false
if bond.Shares.IsZero() {
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
if bond.DelegatorAddr == validator.Owner && validator.Revoked == false
revokeCandidacy = true
2018-05-08 14:35:24 -07:00
removeDelegation( bond)
2018-05-25 15:52:34 -07:00
else
bond.Height = currentBlockHeight
setDelegation(bond)
2018-05-08 14:35:24 -07:00
pool := GetPool()
2018-05-25 15:52:34 -07:00
validator, pool, returnAmount := validator.removeDelShares(pool, delShares)
setPool( pool)
unbondingDelegation = NewUnbondingDelegation(sender, returnAmount, currentHeight/Time, startSlashRatio)
setUnbondingDelegation(unbondingDelegation)
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
if revokeCandidacy
validator.Revoked = true
validator = updateValidator(validator)
2018-05-08 14:35:24 -07:00
2018-05-25 15:52:34 -07:00
if validator.DelegatorShares == 0 {
removeValidator(validator.Owner)
2018-05-25 15:52:34 -07:00
return
2018-05-08 14:35:24 -07:00
```
### TxCompleteUnbonding
Complete the unbonding and transfer the coins to the delegate. Perform any
slashing that occured during the unbonding period.
```golang
type TxUnbondingComplete struct {
DelegatorAddr sdk.Address
ValidatorAddr sdk.Address
}
redelegationComplete(tx TxRedelegate):
unbonding = getUnbondingDelegation(tx.DelegatorAddr, tx.Validator)
if unbonding.CompleteTime >= CurrentBlockTime && unbonding.CompleteHeight >= CurrentBlockHeight
validator = GetValidator(tx.ValidatorAddr)
returnTokens = ExpectedTokens * tx.startSlashRatio/validator.SlashRatio
AddCoins(unbonding.DelegatorAddr, returnTokens)
removeUnbondingDelegation(unbonding)
return
```
2018-05-25 15:52:34 -07:00
### TxRedelegation
2018-05-08 14:35:24 -07:00
The redelegation command allows delegators to instantly switch validators. Once
the unbonding period has passed, the redelegation must be completed with
txRedelegationComplete.
2018-05-08 14:35:24 -07:00
```golang
type TxRedelegate struct {
2018-05-25 15:52:34 -07:00
DelegatorAddr Address
ValidatorFrom Validator
ValidatorTo Validator
Shares sdk.Rat
CompletedTime int64
2018-05-08 14:35:24 -07:00
}
redelegate(tx TxRedelegate):
2018-05-25 15:52:34 -07:00
pool = getPool()
delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Owner)
if delegation == nil
return
2018-05-08 14:35:24 -07:00
if delegation.Shares < tx.Shares
return
2018-05-25 15:52:34 -07:00
delegation.shares -= Tx.Shares
validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares)
setPool(pool)
2018-05-08 14:35:24 -07:00
redelegation = newRedelegation(tx.DelegatorAddr, tx.validatorFrom,
tx.validatorTo, tx.Shares, createdCoins, tx.CompletedTime)
2018-05-25 15:52:34 -07:00
setRedelegation(redelegation)
2018-05-08 14:35:24 -07:00
return
```
### TxCompleteRedelegation
Note that unlike TxCompleteUnbonding slashing of redelegating shares does not
take place during completion. Slashing on redelegated shares takes place
actively as a slashing occurs.
```golang
type TxRedelegationComplete struct {
DelegatorAddr Address
ValidatorFrom Validator
ValidatorTo Validator
}
redelegationComplete(tx TxRedelegate):
redelegation = getRedelegation(tx.DelegatorAddr, tx.validatorFrom, tx.validatorTo)
if redelegation.CompleteTime >= CurrentBlockTime && redelegation.CompleteHeight >= CurrentBlockHeight
removeRedelegation(redelegation)
return
```
2018-05-29 11:50:35 -07:00
### Update Validators
Within many transactions the validator set must be updated based on changes in
power to a single validator. This process also updates the Tendermint-Updates
store for use in end-block when validators are either added or kicked from the
Tendermint.
```golang
updateBondedValidators(newValidator Validator) (updatedVal Validator)
kickCliffValidator := false
oldCliffValidatorAddr := getCliffValidator(ctx)
// add the actual validator power sorted store
maxValidators := GetParams(ctx).MaxValidators
iterator := ReverseSubspaceIterator(ValidatorsByPowerKey) // largest to smallest
bondedValidatorsCount := 0
var validator Validator
for {
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
setCliffValidator(ctx, validator, GetPool(ctx))
iterator.Close()
break
// either retrieve the original validator from the store,
// or under the situation that this is the "new validator" just
// use the validator provided because it has not yet been updated
// in the main validator store
ownerAddr := iterator.Value()
if bytes.Equal(ownerAddr, newValidator.Owner) {
validator = newValidator
else
validator = getValidator(ownerAddr)
// if not previously a validator (and unrevoked),
// kick the cliff validator / bond this new validator
if validator.Status() != sdk.Bonded && !validator.Revoked {
kickCliffValidator = true
validator = bondValidator(ctx, store, validator)
if bytes.Equal(ownerAddr, newValidator.Owner) {
updatedVal = validator
bondedValidatorsCount++
iterator.Next()
// perform the actual kicks
if oldCliffValidatorAddr != nil && kickCliffValidator {
validator := getValidator(store, oldCliffValidatorAddr)
unbondValidator(ctx, store, validator)
return
// perform all the store operations for when a validator status becomes unbonded
unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator)
pool := GetPool(ctx)
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
setPool(ctx, pool)
// save the now unbonded validator record
setValidator(validator)
// add to accumulated changes for tendermint
setTendermintUpdates(validator.abciValidatorZero)
// also remove from the bonded validators index
removeValidatorsBonded(validator)
}
// perform all the store operations for when a validator status becomes bonded
bondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator
pool := GetPool(ctx)
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
setPool(ctx, pool)
// save the now bonded validator record to the three referenced stores
setValidator(validator)
setValidatorsBonded(validator)
// add to accumulated changes for tendermint
setTendermintUpdates(validator.abciValidator)
return validator
```