From e39ba70c08d004ca3c2e68159a294a20d7f2bb6a Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 29 May 2018 14:50:35 -0400 Subject: [PATCH] complete staking spec update --- docs/spec/staking/end_block.md | 61 ++++++++++++++++++ docs/spec/staking/state.md | 4 +- docs/spec/staking/transactions.md | 97 +++++++++++++++++++++++++++- docs/spec/staking/valset-changes.md | 99 ----------------------------- x/stake/keeper.go | 14 ++-- 5 files changed, 164 insertions(+), 111 deletions(-) create mode 100644 docs/spec/staking/end_block.md delete mode 100644 docs/spec/staking/valset-changes.md diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md new file mode 100644 index 0000000000..e7c4e1e822 --- /dev/null +++ b/docs/spec/staking/end_block.md @@ -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 +``` + diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index fa235479f6..72cda1bb45 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -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. diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 685883dd11..b590773145 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -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 +``` diff --git a/docs/spec/staking/valset-changes.md b/docs/spec/staking/valset-changes.md deleted file mode 100644 index 92efa293b7..0000000000 --- a/docs/spec/staking/valset-changes.md +++ /dev/null @@ -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) -``` - diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 9f554c764f..dfa2709d1e 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -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