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

8.7 KiB

Transaction Overview

In this section we describe the processing of the transactions and the corresponding updates to the state. Transactions:

  • TxCreateValidator
  • TxEditValidator
  • TxDelegation
  • TxStartUnbonding
  • TxRedelegate

Other important state changes:

  • Update Validators

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
  • sdk.Dec refers to a decimal type specified by the SDK.

TxCreateValidator

  • triggers: distribution.CreateValidatorDistribution

A validator is created using the TxCreateValidator transaction.

type TxCreateValidator struct {
    Description    Description
    Commission     Commission

    DelegatorAddr  sdk.AccAddress
    ValidatorAddr  sdk.ValAddress
    PubKey         crypto.PubKey
    Delegation     sdk.Coin
}

createValidator(tx TxCreateValidator):
    ok := validatorExists(tx.ValidatorAddr)
    if ok return err // only one validator per address

    ok := validatorByPubKeyExists(tx.PubKey)
    if ok return err // only one validator per public key

    err := validateDenom(tx.Delegation.Denom)
    if err != nil return err // denomination must be valid

    validator := NewValidator(tx.ValidatorAddr, tx.PubKey, tx.Description)

    err := setInitialCommission(validator, tx.Commission, blockTime)
    if err != nil return err // must be able to set initial commission correctly

    // set the validator and public key
    setValidator(validator)
    setValidatorByPubKeyIndex(validator)

    // delegate coins from tx.DelegatorAddr to the validator
    err := delegate(tx.DelegatorAddr, tx.Delegation, validator)
    if err != nil return err // must be able to set delegation correctly

    tags := createTags(tx)
    return tags

TxEditValidator

If either the Description, Commission, or the ValidatorAddr need to be updated, the TxEditCandidacy transaction should be sent from the operator account:

type TxEditCandidacy struct {
    Description     Description
    ValidatorAddr   sdk.ValAddress
    CommissionRate  sdk.Dec
}

editCandidacy(tx TxEditCandidacy):
    validator, ok := getValidator(tx.ValidatorAddr)
    if !ok return err // validator must exist

    // Attempt to update the validator's description. The description provided
    // must be valid.
    description, err := updateDescription(validator, tx.Description)
    if err != nil return err

    // a validator is not required to update it's commission rate
    if tx.CommissionRate != nil {
        // Attempt to update a validator's commission rate. The rate provided
        // must be valid. It's rate can only be updated once a day.
        err := updateValidatorCommission(validator, tx.CommissionRate)
        if err != nil return err
    }

    // set the validator and public key
    setValidator(validator)

    tags := createTags(tx)
    return tags

TxDelegate

  • triggers: distribution.CreateOrModDelegationDistribution

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.

type TxDelegate struct {
	DelegatorAddr sdk.Address
	ValidatorAddr sdk.Address
	Amount        sdk.Coin
}

delegate(tx TxDelegate):
    pool = getPool()
    if validator.Status == Jailed return

    delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr)
    if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr)

    validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool)
    delegation.Shares += issuedDelegatorShares

    setDelegation(delegation)
    updateValidator(validator)
    setPool(pool)
    return

TxStartUnbonding

Delegator unbonding is defined with the following transaction:

type TxStartUnbonding struct {
	DelegatorAddr sdk.Address
	ValidatorAddr sdk.Address
	Shares        string
}

startUnbonding(tx TxStartUnbonding):
    delegation, found = getDelegatorBond(store, sender, tx.PubKey)
    if !found == nil return

		if bond.Shares < tx.Shares
			return ErrNotEnoughBondShares

	validator, found = GetValidator(tx.ValidatorAddr)
	if !found {
		return err

	bond.Shares -= tx.Shares

	revokeCandidacy = false
	if bond.Shares.IsZero() {

		if bond.DelegatorAddr == validator.Operator && validator.Jailed == false
			revokeCandidacy = true

		removeDelegation( bond)
	else
		bond.Height = currentBlockHeight
		setDelegation(bond)

	pool = GetPool()
	validator, pool, returnAmount = validator.removeDelShares(pool, tx.Shares)
	setPool( pool)

    unbondingDelegation = NewUnbondingDelegation(sender, returnAmount, currentHeight/Time, startSlashRatio)
    setUnbondingDelegation(unbondingDelegation)

	if revokeCandidacy
		validator.Jailed = true

	validator = updateValidator(validator)

	if validator.Status == Unbonded && validator.DelegatorShares == 0 {
		removeValidator(validator.Operator)

    return

TxRedelegation

The redelegation command allows delegators to instantly switch validators. Once the unbonding period has passed, the redelegation is automatically completed in the EndBlocker.

type TxRedelegate struct {
    DelegatorAddr Address
    ValidatorFrom Validator
    ValidatorTo   Validator
    Shares        sdk.Dec 
    CompletedTime int64 
}

redelegate(tx TxRedelegate):

    pool = getPool()
    delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Operator)
    if delegation == nil
        return

    if delegation.Shares < tx.Shares
        return
    delegation.shares -= Tx.Shares
    validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares)
    setPool(pool)

    redelegation = newRedelegation(tx.DelegatorAddr, tx.validatorFrom,
        tx.validatorTo, tx.Shares, createdCoins, tx.CompletedTime)
    setRedelegation(redelegation)
    return

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.

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

		operatorAddr = iterator.Value()
		if bytes.Equal(operatorAddr, newValidator.Operator) {
			validator = newValidator
        else
			validator = getValidator(operatorAddr)

		// if not previously a validator (and unjailed),
		// kick the cliff validator / bond this new validator
		if validator.Status() != Bonded && !validator.Jailed {
			kickCliffValidator = true

			validator = bondValidator(ctx, store, validator)
			if bytes.Equal(operatorAddr, newValidator.Operator) {
				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 Context, store KVStore, validator Validator)
	pool = GetPool(ctx)

	// set the status
	validator, pool = validator.UpdateStatus(pool, 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 Context, store KVStore, validator Validator) Validator
	pool = GetPool(ctx)

	// set the status
	validator, pool = validator.UpdateStatus(pool, 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