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

8.9 KiB

Transaction Overview

Available Transactions:

  • TxDeclareCandidacy
  • TxEditCandidacy
  • TxDelegate
  • TxUnbond
  • TxRedelegate
  • TxProveLive

Transaction processing

In this section we describe the processing of the transactions and the corresponding updates to the global state. In the following text we will use gs to refer to the GlobalState data structure, unbondDelegationQueue is a reference to the queue of unbond delegations, reDelegationQueue is the reference for the queue of redelegations. We use tx to denote a reference to a transaction that is being processed, and sender to denote the address of the sender of the transaction. We use function loadCandidate(store, PubKey) to obtain a Candidate structure from the store, and saveCandidate(store, candidate) to save it. Similarly, we use loadDelegatorBond(store, sender, PubKey) to load a delegator bond with the key (sender and PubKey) from the store, and saveDelegatorBond(store, sender, bond) to save it. removeDelegatorBond(store, sender, bond) is used to remove the bond from the store.

TxDeclareCandidacy

A validator candidacy is declared using the TxDeclareCandidacy transaction.

type TxDeclareCandidacy struct {
    ConsensusPubKey     crypto.PubKey
    Amount              coin.Coin       
    GovernancePubKey    crypto.PubKey
    Commission          rational.Rat
    CommissionMax       int64 
    CommissionMaxChange int64 
    Description         Description
}

declareCandidacy(tx TxDeclareCandidacy):
    candidate = loadCandidate(store, tx.PubKey)
    if candidate != nil return // candidate with that public key already exists 
   	
    candidate = NewCandidate(tx.PubKey)
    candidate.Status = Unbonded
    candidate.Owner = sender
    init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero
    init commision related fields based on the values from tx
    candidate.ProposerRewardPool = Coin(0)  
    candidate.Description = tx.Description
   	
    saveCandidate(store, candidate)
   
    txDelegate = TxDelegate(tx.PubKey, tx.Amount)
    return delegateWithCandidate(txDelegate, candidate) 

// see delegateWithCandidate function in [TxDelegate](TxDelegate)

TxEditCandidacy

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:

type TxEditCandidacy struct {
    GovernancePubKey    crypto.PubKey
    Commission          int64  
    Description         Description
}
 
editCandidacy(tx TxEditCandidacy):
    candidate = loadCandidate(store, tx.PubKey)
    if candidate == nil or candidate.Status == Revoked return 
    
    if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
    if tx.Commission >= 0 candidate.Commission = tx.Commission
    if tx.Description != nil candidate.Description = tx.Description
    
    saveCandidate(store, candidate)
    return

TxDelegate

Delegator bonds are created using the TxDelegate transaction. Within this transaction the delegator provides an amount of coins, and in return receives some amount of candidate's delegator shares that are assigned to DelegatorBond.Shares.

type TxDelegate struct {
    PubKey crypto.PubKey
    Amount coin.Coin       
}

delegate(tx TxDelegate):
    candidate = loadCandidate(store, tx.PubKey)
    if candidate == nil return
	return delegateWithCandidate(tx, candidate)

delegateWithCandidate(tx TxDelegate, candidate Candidate):
    if candidate.Status == Revoked return

    if candidate.Status == Bonded 
	    poolAccount = params.HoldBonded
    else 
	    poolAccount = params.HoldUnbonded
	
    err = transfer(sender, poolAccount, tx.Amount)
    if err != nil return 

    bond = loadDelegatorBond(store, sender, tx.PubKey)
    if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0))
	
    issuedDelegatorShares = addTokens(tx.Amount, candidate)
    bond.Shares += issuedDelegatorShares
	
    saveCandidate(store, candidate)
    saveDelegatorBond(store, sender, bond)
    saveGlobalState(store, gs)
    return 

addTokens(amount coin.Coin, candidate Candidate):
    if candidate.Status == Bonded 
	    gs.BondedPool += amount
	    issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool)
	    gs.BondedShares += issuedShares
    else 
	    gs.UnbondedPool += amount
	    issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
	    gs.UnbondedShares += issuedShares
	
    candidate.GlobalStakeShares += issuedShares
    
    if candidate.IssuedDelegatorShares.IsZero() 
        exRate = rational.One
    else
        exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
	
    issuedDelegatorShares = issuedShares / exRate
    candidate.IssuedDelegatorShares += issuedDelegatorShares
    return issuedDelegatorShares
	
exchangeRate(shares rational.Rat, tokenAmount int64):
    if shares.IsZero() then return rational.One
    return tokenAmount / shares
    	

TxUnbond

Delegator unbonding is defined with the following transaction:

type TxUnbond struct {
    PubKey crypto.PubKey
    Shares rational.Rat 
}

unbond(tx TxUnbond):    
    bond = loadDelegatorBond(store, sender, tx.PubKey)
    if bond == nil return 
    if bond.Shares < tx.Shares return 
	
    bond.Shares -= tx.Shares

    candidate = loadCandidate(store, tx.PubKey)
	
    revokeCandidacy = false
    if bond.Shares.IsZero() 
	    if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
    else 
	    saveDelegatorBond(store, sender, bond)

    if candidate.Status == Bonded 
        poolAccount = params.HoldBonded
    else 
        poolAccount = params.HoldUnbonded

    returnedCoins = removeShares(candidate, shares)
	
    unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
    unbondDelegationQueue.add(unbondDelegationElem)
	
    transfer(poolAccount, unbondingPoolAddress, returnCoins)  
    
    if revokeCandidacy 
	    if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
	    candidate.Status = Revoked

    if candidate.IssuedDelegatorShares.IsZero() 
	    removeCandidate(store, tx.PubKey)
    else 
	    saveCandidate(store, candidate)

    saveGlobalState(store, gs)
    return 

removeShares(candidate Candidate, shares rational.Rat):
    globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares

    if candidate.Status == Bonded 
	    gs.BondedShares -= globalPoolSharesToRemove
	    removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove
	    gs.BondedPool -= removedTokens
    else 
	    gs.UnbondedShares -= globalPoolSharesToRemove
	    removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove
	    gs.UnbondedPool -= removedTokens
	
    candidate.GlobalStakeShares -= removedTokens
    candidate.IssuedDelegatorShares -= shares
    return returnedCoins

delegatorShareExRate(candidate Candidate):
    if candidate.IssuedDelegatorShares.IsZero() then return rational.One
    return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
	
bondedToUnbondedPool(candidate Candidate):
    removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares 
    gs.BondedShares -= candidate.GlobalStakeShares
    gs.BondedPool -= removedTokens
	
    gs.UnbondedPool += removedTokens
    issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
    gs.UnbondedShares += issuedShares
    
    candidate.GlobalStakeShares = issuedShares
    candidate.Status = Unbonded

    return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)

TxRedelegate

The re-delegation command allows delegators to switch validators while still receiving equal reward to as if they had never unbonded.

type TxRedelegate struct {
    PubKeyFrom crypto.PubKey
    PubKeyTo   crypto.PubKey
    Shares     rational.Rat 
}

redelegate(tx TxRedelegate):
    bond = loadDelegatorBond(store, sender, tx.PubKey)
    if bond == nil then return 
    
    if bond.Shares < tx.Shares return 
    candidate = loadCandidate(store, tx.PubKeyFrom)
    if candidate == nil return
    
    candidate.RedelegatingShares += tx.Shares
    reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo)
    redelegationQueue.add(reDelegationElem)
    return     

TxProveLive

If a validator was automatically unbonded due to liveness issues and wishes to assert it is still online, it can send TxProveLive:

type TxProveLive struct {
    PubKey crypto.PubKey
}

All delegators in the temporary unbonding pool which have not transacted to move will be bonded back to the now-live validator and begin to once again collect provisions and rewards.

TODO: pseudo-code