complete staking spec update
This commit is contained in:
parent
b8cf5f347e
commit
e39ba70c08
61
docs/spec/staking/end_block.md
Normal file
61
docs/spec/staking/end_block.md
Normal file
@ -0,0 +1,61 @@
|
||||
# End-Block
|
||||
|
||||
Two staking activities are intended to be processed in the application endblock.
|
||||
- inform tendermint of validator set changes
|
||||
- process and set atom inflation
|
||||
|
||||
# Validator Set Changes
|
||||
|
||||
The Tendermint validator set may be updated by state transitions that run at
|
||||
the end of every block. The Tendermint validator set may be changed by
|
||||
validators either being revoked due to inactivity/unexpected behaviour (covered
|
||||
in slashing) or changed in validator power. Determining which validator set
|
||||
changes must be made occures during staking transactions (and slashing
|
||||
transactions) - during end-block the already accounted changes are applied and
|
||||
the changes cleared
|
||||
|
||||
```golang
|
||||
EndBlock() ValidatorSetChanges
|
||||
vsc = GetTendermintUpdates()
|
||||
ClearTendermintUpdates()
|
||||
return vsc
|
||||
```
|
||||
|
||||
# Inflation
|
||||
|
||||
The atom inflation rate is changed once per hour based on the current and
|
||||
historic bond ratio
|
||||
|
||||
```golang
|
||||
processProvisions():
|
||||
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
|
||||
|
||||
time = BFTTime()
|
||||
if time > pool.InflationLastTime + ProvisionTimeout
|
||||
pool.InflationLastTime = time
|
||||
pool.Inflation = nextInflation(hrsPerYr).Round(1000000000)
|
||||
|
||||
provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr)
|
||||
|
||||
pool.LooseUnbondedTokens += provisions
|
||||
feePool += LooseUnbondedTokens
|
||||
|
||||
setPool(pool)
|
||||
|
||||
nextInflation(hrsPerYr rational.Rat):
|
||||
if pool.TotalSupply > 0
|
||||
bondedRatio = pool.BondedPool / pool.TotalSupply
|
||||
else
|
||||
bondedRation = 0
|
||||
|
||||
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
|
||||
inflationRateChange = inflationRateChangePerYear / hrsPerYr
|
||||
|
||||
inflation = pool.Inflation + inflationRateChange
|
||||
if inflation > params.InflationMax then inflation = params.InflationMax
|
||||
|
||||
if inflation < params.InflationMin then inflation = params.InflationMin
|
||||
|
||||
return inflation
|
||||
```
|
||||
|
||||
@ -52,7 +52,9 @@ type Params struct {
|
||||
- index 2: validator Tendermint PubKey
|
||||
- index 3: bonded validators only
|
||||
- index 4: voting power
|
||||
- index 5: Tendermint updates
|
||||
|
||||
Related Store which holds Validator.ABCIValidator()
|
||||
- index: validator owner address
|
||||
|
||||
The `Validator` holds the current state and some historical actions of the
|
||||
validator.
|
||||
|
||||
@ -2,15 +2,16 @@
|
||||
### Transaction Overview
|
||||
|
||||
In this section we describe the processing of the transactions and the
|
||||
corresponding updates to the state.
|
||||
|
||||
Available Transactions:
|
||||
corresponding updates to the state. Transactions:
|
||||
- TxCreateValidator
|
||||
- TxEditValidator
|
||||
- TxDelegation
|
||||
- TxRedelegation
|
||||
- TxUnbond
|
||||
|
||||
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
|
||||
@ -200,3 +201,93 @@ redelegate(tx TxRedelegate):
|
||||
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.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
# Validator Set Changes
|
||||
|
||||
The Tendermint validator set may be updated by state transitions that run at
|
||||
the beginning and end of every block. The Tendermint validator set may be
|
||||
changed by validators either being revoked due to inactivity/unexpected
|
||||
behaviour (covered in slashing) or changed in validator power.
|
||||
|
||||
At the end of every block, we run the following:
|
||||
|
||||
(TODO remove inflation from here)
|
||||
|
||||
```golang
|
||||
tick(ctx Context):
|
||||
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
|
||||
|
||||
time = ctx.Time()
|
||||
if time > gs.InflationLastTime + ProvisionTimeout
|
||||
gs.InflationLastTime = time
|
||||
gs.Inflation = nextInflation(hrsPerYr).Round(1000000000)
|
||||
|
||||
provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr)
|
||||
|
||||
gs.BondedPool += provisions
|
||||
gs.TotalSupply += provisions
|
||||
|
||||
saveGlobalState(store, gs)
|
||||
|
||||
if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod
|
||||
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
|
||||
transfer(unbondingQueueAddress, elem.Payout, elem.Tokens)
|
||||
unbondDelegationQueue.remove(elem)
|
||||
|
||||
if time > reDelegationQueue.head().InitTime + UnbondingPeriod
|
||||
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
|
||||
validator = getValidator(store, elem.PubKey)
|
||||
returnedCoins = removeShares(validator, elem.Shares)
|
||||
validator.RedelegatingShares -= elem.Shares
|
||||
delegateWithValidator(TxDelegate(elem.NewValidator, returnedCoins), validator)
|
||||
reDelegationQueue.remove(elem)
|
||||
|
||||
return UpdateValidatorSet()
|
||||
|
||||
nextInflation(hrsPerYr rational.Rat):
|
||||
if gs.TotalSupply > 0
|
||||
bondedRatio = gs.BondedPool / gs.TotalSupply
|
||||
else
|
||||
bondedRation = 0
|
||||
|
||||
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
|
||||
inflationRateChange = inflationRateChangePerYear / hrsPerYr
|
||||
|
||||
inflation = gs.Inflation + inflationRateChange
|
||||
if inflation > params.InflationMax then inflation = params.InflationMax
|
||||
|
||||
if inflation < params.InflationMin then inflation = params.InflationMin
|
||||
|
||||
return inflation
|
||||
|
||||
UpdateValidatorSet():
|
||||
validators = loadValidators(store)
|
||||
|
||||
v1 = validators.Validators()
|
||||
v2 = updateVotingPower(validators).Validators()
|
||||
|
||||
change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets
|
||||
return change
|
||||
|
||||
updateVotingPower(validators Validators):
|
||||
foreach validator in validators do
|
||||
validator.VotingPower = (validator.IssuedDelegatorShares - validator.RedelegatingShares) * delegatorShareExRate(validator)
|
||||
|
||||
validators.Sort()
|
||||
|
||||
foreach validator in validators do
|
||||
if validator is not in the first params.MaxVals
|
||||
validator.VotingPower = rational.Zero
|
||||
if validator.Status == Bonded then bondedToUnbondedPool(validator Validator)
|
||||
|
||||
else if validator.Status == UnBonded then unbondedToBondedPool(validator)
|
||||
|
||||
saveValidator(store, c)
|
||||
|
||||
return validators
|
||||
|
||||
unbondedToBondedPool(validator Validator):
|
||||
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * validator.GlobalStakeShares
|
||||
gs.UnbondedShares -= validator.GlobalStakeShares
|
||||
gs.UnbondedPool -= removedTokens
|
||||
|
||||
gs.BondedPool += removedTokens
|
||||
issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool)
|
||||
gs.BondedShares += issuedShares
|
||||
|
||||
validator.GlobalStakeShares = issuedShares
|
||||
validator.Status = Bonded
|
||||
|
||||
return transfer(address of the unbonded pool, address of the bonded pool, removedTokens)
|
||||
```
|
||||
|
||||
@ -282,16 +282,14 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator
|
||||
return validator
|
||||
}
|
||||
|
||||
// XXX TODO build in consideration for revoked
|
||||
//
|
||||
// Update the validator group and kick out any old validators. In addition this
|
||||
// function adds (or doesn't add) a validator which has updated its bonded
|
||||
// tokens to the validator group. -> this validator is specified through the
|
||||
// updatedValidatorAddr term.
|
||||
//
|
||||
// The correct subset is retrieved by iterating through an index of the
|
||||
// validators sorted by power, stored using the ValidatorsByPowerKey. Simultaniously
|
||||
// the current validator records are updated in store with the
|
||||
// validators sorted by power, stored using the ValidatorsByPowerKey.
|
||||
// Simultaneously the current validator records are updated in store with the
|
||||
// ValidatorsBondedKey. This store is used to determine if a validator is a
|
||||
// validator without needing to iterate over the subspace as we do in
|
||||
// GetValidators.
|
||||
@ -319,10 +317,10 @@ func (k Keeper) updateBondedValidators(ctx sdk.Context, store sdk.KVStore,
|
||||
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
|
||||
// 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user