transaction stake updates
This commit is contained in:
parent
35956c1c78
commit
b8cf5f347e
|
@ -0,0 +1,88 @@
|
|||
# Validator Set Changes
|
||||
|
||||
## Slashing
|
||||
|
||||
Messges which may compromise the safety of the underlying consensus protocol ("equivocations")
|
||||
result in some amount of the offending validator's shares being removed ("slashed").
|
||||
|
||||
Currently, such messages include only the following:
|
||||
|
||||
- prevotes by the same validator for more than one BlockID at the same
|
||||
Height and Round
|
||||
- precommits by the same validator for more than one BlockID at the same
|
||||
Height and Round
|
||||
|
||||
We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the
|
||||
detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending
|
||||
validators punished.
|
||||
|
||||
For some `evidence` to be valid, it must satisfy:
|
||||
|
||||
`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE`
|
||||
|
||||
where `evidence.Timestamp` is the timestamp in the block at height
|
||||
`evidence.Height` and `block.Timestamp` is the current block timestamp.
|
||||
|
||||
If valid evidence is included in a block, the offending validator loses
|
||||
a constant `SLASH_PROPORTION` of their current stake at the beginning of the block:
|
||||
|
||||
```
|
||||
oldShares = validator.shares
|
||||
validator.shares = oldShares * (1 - SLASH_PROPORTION)
|
||||
```
|
||||
|
||||
This ensures that offending validators are punished the same amount whether they
|
||||
act as a single validator with X stake or as N validators with collectively X
|
||||
stake.
|
||||
|
||||
|
||||
## Automatic Unbonding
|
||||
|
||||
Every block includes a set of precommits by the validators for the previous block,
|
||||
known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power.
|
||||
|
||||
Proposers are incentivized to include precommits from all
|
||||
validators in the LastCommit by receiving additional fees
|
||||
proportional to the difference between the voting power included in the
|
||||
LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)).
|
||||
|
||||
Validators are penalized for failing to be included in the LastCommit for some
|
||||
number of blocks by being automatically unbonded.
|
||||
|
||||
The following information is stored with each validator, and is only non-zero if the validator becomes an active validator:
|
||||
|
||||
```go
|
||||
type ValidatorSigningInfo struct {
|
||||
StartHeight int64
|
||||
SignedBlocksBitArray BitArray
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
* `StartHeight` is set to the height that the validator became an active validator (with non-zero voting power).
|
||||
* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks,
|
||||
whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not.
|
||||
Note it is initialized with all 0s.
|
||||
|
||||
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
||||
|
||||
```
|
||||
h = block.Height
|
||||
index = h % SIGNED_BLOCKS_WINDOW
|
||||
|
||||
for val in block.Validators:
|
||||
signInfo = val.SignInfo
|
||||
if val in block.LastCommit:
|
||||
signInfo.SignedBlocksBitArray.Set(index, 0)
|
||||
else
|
||||
signInfo.SignedBlocksBitArray.Set(index, 1)
|
||||
|
||||
// validator must be active for at least SIGNED_BLOCKS_WINDOW
|
||||
// before they can be automatically unbonded for failing to be
|
||||
// included in 50% of the recent LastCommits
|
||||
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
|
||||
minSigned = SIGNED_BLOCKS_WINDOW / 2
|
||||
blocksSigned = signInfo.SignedBlocksBitArray.Sum()
|
||||
if h > minHeight AND blocksSigned < minSigned:
|
||||
unbond the validator
|
||||
```
|
|
@ -1,16 +1,7 @@
|
|||
|
||||
## State
|
||||
|
||||
The staking module persists the following information to the store:
|
||||
* `Params`, a struct describing the global pools, inflation, and fees
|
||||
* `Pool`, a struct describing the global pools, inflation, and fees
|
||||
* `ValidatorValidators: <pubkey | shares> => <validator>`, a map of all validators (including current validators) in the store,
|
||||
indexed by their public key and shares in the global pool.
|
||||
* `DelegatorBonds: < delegator-address | validator-pubkey > => <delegator-bond>`. a map of all delegations by a delegator to a validator,
|
||||
indexed by delegator address and validator pubkey.
|
||||
public key
|
||||
|
||||
### Pool
|
||||
- index: n/a single-record
|
||||
|
||||
The pool is a space for all dynamic global state of the Cosmos Hub. It tracks
|
||||
information about the total amounts of Atoms in all states, representative
|
||||
|
@ -39,6 +30,7 @@ type PoolShares struct {
|
|||
```
|
||||
|
||||
### Params
|
||||
- index: n/a single-record
|
||||
|
||||
Params is global data structure that stores system parameters and defines
|
||||
overall functioning of the stake module.
|
||||
|
@ -56,6 +48,11 @@ type Params struct {
|
|||
```
|
||||
|
||||
### Validator
|
||||
- index 1: validator owner address
|
||||
- index 2: validator Tendermint PubKey
|
||||
- index 3: bonded validators only
|
||||
- index 4: voting power
|
||||
- index 5: Tendermint updates
|
||||
|
||||
The `Validator` holds the current state and some historical actions of the
|
||||
validator.
|
||||
|
@ -90,10 +87,8 @@ type Description struct {
|
|||
}
|
||||
```
|
||||
|
||||
* RedelegatingShares: The portion of `IssuedDelegatorShares` which are
|
||||
currently re-delegating to a new validator
|
||||
|
||||
### Delegation
|
||||
- index: delegation address
|
||||
|
||||
Atom holders may delegate coins to validators; under this circumstance their
|
||||
funds are held in a `Delegation` data structure. It is owned by one
|
||||
|
@ -110,10 +105,12 @@ type Delegation struct {
|
|||
```
|
||||
|
||||
### UnbondingDelegation
|
||||
- index: delegation address
|
||||
|
||||
A UnbondingDelegation object is created every time an unbonding is initiated.
|
||||
It must be completed with a second transaction provided by the delegation owner
|
||||
The unbond must be completed with a second transaction provided by the delegation owner
|
||||
after the unbonding period has passed.
|
||||
|
||||
|
||||
```golang
|
||||
type UnbondingDelegation struct {
|
||||
|
@ -126,17 +123,17 @@ type UnbondingDelegation struct {
|
|||
```
|
||||
|
||||
### Redelegation
|
||||
|
||||
A redelegation object is created every time a redelegation occurs. It must be
|
||||
completed with a second transaction provided by the delegation owner after the
|
||||
unbonding period has passed. The destination delegation of a redelegation may
|
||||
not itself undergo a new redelegation until the original redelegation has been
|
||||
completed.
|
||||
|
||||
- index: delegation address
|
||||
- index 1: delegation address
|
||||
- index 2: source validator owner address
|
||||
- index 3: destination validator owner address
|
||||
|
||||
A redelegation object is created every time a redelegation occurs. The
|
||||
redelegation must be completed with a second transaction provided by the
|
||||
delegation owner after the unbonding period has passed. The destination
|
||||
delegation of a redelegation may not itself undergo a new redelegation until
|
||||
the original redelegation has been completed.
|
||||
|
||||
|
||||
```golang
|
||||
type Redelegation struct {
|
||||
SourceDelegation []byte // source delegation key
|
||||
|
|
|
@ -1,67 +1,58 @@
|
|||
|
||||
### Transaction Overview
|
||||
|
||||
In this section we describe the processing of the transactions and the
|
||||
corresponding updates to the state.
|
||||
|
||||
Available Transactions:
|
||||
* TxDeclareCandidacy
|
||||
* TxEditCandidacy
|
||||
* TxDelegate
|
||||
* TxUnbond
|
||||
* TxRedelegate
|
||||
* TxProveLive
|
||||
- TxCreateValidator
|
||||
- TxEditValidator
|
||||
- TxDelegation
|
||||
- TxRedelegation
|
||||
- TxUnbond
|
||||
|
||||
## 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
|
||||
`loadValidator(store, PubKey)` to obtain a Validator structure from the store,
|
||||
and `saveValidator(store, validator)` 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.
|
||||
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.Rat` refers to a rational numeric type specified by the sdk.
|
||||
|
||||
### TxDeclareCandidacy
|
||||
### TxCreateValidator
|
||||
|
||||
A validator candidacy is declared using the `TxDeclareCandidacy` transaction.
|
||||
A validator is created using the `TxCreateValidator` transaction.
|
||||
|
||||
```golang
|
||||
type TxDeclareCandidacy struct {
|
||||
type TxCreateValidator struct {
|
||||
OwnerAddr sdk.Address
|
||||
ConsensusPubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
SelfDelegation coin.Coin
|
||||
|
||||
Description Description
|
||||
Commission sdk.Rat
|
||||
CommissionMax sdk.Rat
|
||||
CommissionMaxChange sdk.Rat
|
||||
}
|
||||
|
||||
|
||||
declareCandidacy(tx TxDeclareCandidacy):
|
||||
validator = loadValidator(store, tx.PubKey)
|
||||
if validator != nil return // validator with that public key already exists
|
||||
createValidator(tx TxCreateValidator):
|
||||
validator = getValidator(tx.OwnerAddr)
|
||||
if validator != nil return // only one validator per address
|
||||
|
||||
validator = NewValidator(tx.PubKey)
|
||||
validator.Status = Unbonded
|
||||
validator.Owner = sender
|
||||
init validator VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero
|
||||
init commision related fields based on the values from tx
|
||||
validator.ProposerRewardPool = Coin(0)
|
||||
validator.Description = tx.Description
|
||||
validator = NewValidator(OwnerAddr, ConsensusPubKey, GovernancePubKey, Description)
|
||||
init validator poolShares, delegatorShares set to 0 //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
init validator commision fields from tx
|
||||
validator.PoolShares = 0
|
||||
|
||||
saveValidator(store, validator)
|
||||
setValidator(validator)
|
||||
|
||||
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
|
||||
return delegateWithValidator(txDelegate, validator)
|
||||
|
||||
// see delegateWithValidator function in [TxDelegate](TxDelegate)
|
||||
txDelegate = TxDelegate(tx.OwnerAddr, tx.OwnerAddr, tx.SelfDelegation)
|
||||
delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate)
|
||||
return
|
||||
```
|
||||
|
||||
### TxEditCandidacy
|
||||
### TxEditValidator
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
|
@ -70,87 +61,51 @@ If either the `Description` (excluding `DateBonded` which is constant),
|
|||
```golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Commission sdk.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
editCandidacy(tx TxEditCandidacy):
|
||||
validator = loadValidator(store, tx.PubKey)
|
||||
if validator == nil or validator.Status == Revoked return
|
||||
validator = getValidator(tx.ValidatorAddr)
|
||||
|
||||
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.Commission >= 0 validator.Commission = tx.Commission
|
||||
if tx.Description != nil validator.Description = tx.Description
|
||||
|
||||
saveValidator(store, validator)
|
||||
setValidator(store, validator)
|
||||
return
|
||||
```
|
||||
|
||||
### TxDelegate
|
||||
### TxDelegation
|
||||
|
||||
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 validator's delegator shares that are assigned to
|
||||
`DelegatorBond.Shares`.
|
||||
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`.
|
||||
|
||||
```golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
Amount sdk.Coin
|
||||
}
|
||||
|
||||
delegate(tx TxDelegate):
|
||||
validator = loadValidator(store, tx.PubKey)
|
||||
if validator == nil return
|
||||
return delegateWithValidator(tx, validator)
|
||||
|
||||
delegateWithValidator(tx TxDelegate, validator Validator):
|
||||
pool = getPool()
|
||||
if validator.Status == Revoked return
|
||||
|
||||
if validator.Status == Bonded
|
||||
poolAccount = params.HoldBonded
|
||||
else
|
||||
poolAccount = params.HoldUnbonded
|
||||
delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr)
|
||||
if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr)
|
||||
|
||||
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, validator)
|
||||
bond.Shares += issuedDelegatorShares
|
||||
|
||||
saveValidator(store, validator)
|
||||
saveDelegatorBond(store, sender, bond)
|
||||
saveGlobalState(store, gs)
|
||||
return
|
||||
|
||||
addTokens(amount coin.Coin, validator Validator):
|
||||
if validator.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
|
||||
|
||||
validator.GlobalStakeShares += issuedShares
|
||||
validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool)
|
||||
delegation.Shares += issuedDelegatorShares
|
||||
|
||||
if validator.IssuedDelegatorShares.IsZero()
|
||||
exRate = rational.One
|
||||
else
|
||||
exRate = validator.GlobalStakeShares / validator.IssuedDelegatorShares
|
||||
|
||||
issuedDelegatorShares = issuedShares / exRate
|
||||
validator.IssuedDelegatorShares += issuedDelegatorShares
|
||||
return issuedDelegatorShares
|
||||
|
||||
exchangeRate(shares rational.Rat, tokenAmount int64):
|
||||
if shares.IsZero() then return rational.One
|
||||
return tokenAmount / shares
|
||||
|
||||
setDelegation(delegation)
|
||||
updateValidator(validator)
|
||||
setPool(pool)
|
||||
return
|
||||
```
|
||||
|
||||
### TxUnbond
|
||||
|
@ -159,125 +114,89 @@ Delegator unbonding is defined with the following transaction:
|
|||
|
||||
```golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
Shares string
|
||||
}
|
||||
|
||||
unbond(tx TxUnbond):
|
||||
bond = loadDelegatorBond(store, sender, tx.PubKey)
|
||||
if bond == nil return
|
||||
if bond.Shares < tx.Shares return
|
||||
|
||||
bond.Shares -= tx.Shares
|
||||
|
||||
validator = loadValidator(store, tx.PubKey)
|
||||
|
||||
revokeCandidacy = false
|
||||
if bond.Shares.IsZero()
|
||||
if sender == validator.Owner and validator.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
|
||||
else
|
||||
saveDelegatorBond(store, sender, bond)
|
||||
|
||||
if validator.Status == Bonded
|
||||
poolAccount = params.HoldBonded
|
||||
else
|
||||
poolAccount = params.HoldUnbonded
|
||||
|
||||
returnedCoins = removeShares(validator, shares)
|
||||
|
||||
unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
|
||||
unbondDelegationQueue.add(unbondDelegationElem)
|
||||
|
||||
transfer(poolAccount, unbondingPoolAddress, returnCoins)
|
||||
delegation, found = getDelegatorBond(store, sender, tx.PubKey)
|
||||
if !found == nil return
|
||||
|
||||
if revokeCandidacy
|
||||
if validator.Status == Bonded then bondedToUnbondedPool(validator)
|
||||
validator.Status = Revoked
|
||||
|
||||
if validator.IssuedDelegatorShares.IsZero()
|
||||
removeValidator(store, tx.PubKey)
|
||||
if msg.Shares == "MAX" {
|
||||
if !bond.Shares.GT(sdk.ZeroRat()) {
|
||||
return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result()
|
||||
else
|
||||
saveValidator(store, validator)
|
||||
var err sdk.Error
|
||||
delShares, err = sdk.NewRatFromDecimal(msg.Shares)
|
||||
if err != nil
|
||||
return err
|
||||
if bond.Shares.LT(delShares)
|
||||
return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result()
|
||||
|
||||
saveGlobalState(store, gs)
|
||||
return
|
||||
validator, found := k.GetValidator(ctx, msg.ValidatorAddr)
|
||||
if !found {
|
||||
return err
|
||||
|
||||
removeShares(validator Validator, shares rational.Rat):
|
||||
globalPoolSharesToRemove = delegatorShareExRate(validator) * shares
|
||||
if msg.Shares == "MAX"
|
||||
delShares = bond.Shares
|
||||
|
||||
if validator.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
|
||||
|
||||
validator.GlobalStakeShares -= removedTokens
|
||||
validator.IssuedDelegatorShares -= shares
|
||||
return returnedCoins
|
||||
|
||||
delegatorShareExRate(validator Validator):
|
||||
if validator.IssuedDelegatorShares.IsZero() then return rational.One
|
||||
return validator.GlobalStakeShares / validator.IssuedDelegatorShares
|
||||
|
||||
bondedToUnbondedPool(validator Validator):
|
||||
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * validator.GlobalStakeShares
|
||||
gs.BondedShares -= validator.GlobalStakeShares
|
||||
gs.BondedPool -= removedTokens
|
||||
|
||||
gs.UnbondedPool += removedTokens
|
||||
issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
|
||||
gs.UnbondedShares += issuedShares
|
||||
bond.Shares -= delShares
|
||||
|
||||
validator.GlobalStakeShares = issuedShares
|
||||
validator.Status = Unbonded
|
||||
unbondingDelegation = NewUnbondingDelegation(sender, delShares, currentHeight/Time, startSlashRatio)
|
||||
setUnbondingDelegation(unbondingDelegation)
|
||||
|
||||
return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)
|
||||
revokeCandidacy := false
|
||||
if bond.Shares.IsZero() {
|
||||
|
||||
if bond.DelegatorAddr == validator.Owner && validator.Revoked == false
|
||||
revokeCandidacy = true
|
||||
|
||||
k.removeDelegation(ctx, bond)
|
||||
else
|
||||
bond.Height = currentBlockHeight
|
||||
setDelegation(bond)
|
||||
|
||||
pool := k.GetPool(ctx)
|
||||
validator, pool, returnAmount := validator.removeDelShares(pool, delShares)
|
||||
k.setPool(ctx, pool)
|
||||
AddCoins(ctx, bond.DelegatorAddr, returnAmount)
|
||||
|
||||
if revokeCandidacy
|
||||
validator.Revoked = true
|
||||
|
||||
validator = updateValidator(ctx, validator)
|
||||
|
||||
if validator.DelegatorShares == 0 {
|
||||
removeValidator(ctx, validator.Owner)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### TxRedelegate
|
||||
### TxRedelegation
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if they had never unbonded.
|
||||
The redelegation command allows delegators to instantly switch validators.
|
||||
|
||||
```golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
DelegatorAddr Address
|
||||
ValidatorFrom Validator
|
||||
ValidatorTo Validator
|
||||
Shares sdk.Rat
|
||||
}
|
||||
|
||||
redelegate(tx TxRedelegate):
|
||||
bond = loadDelegatorBond(store, sender, tx.PubKey)
|
||||
if bond == nil then return
|
||||
pool = getPool()
|
||||
delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Owner)
|
||||
if delegation == nil then return
|
||||
|
||||
if bond.Shares < tx.Shares return
|
||||
validator = loadValidator(store, tx.PubKeyFrom)
|
||||
if validator == nil return
|
||||
if delegation.Shares < tx.Shares return
|
||||
delegation.shares -= Tx.Shares
|
||||
validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares)
|
||||
setPool(pool)
|
||||
|
||||
validator.RedelegatingShares += tx.Shares
|
||||
reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo)
|
||||
redelegationQueue.add(reDelegationElem)
|
||||
redelegation = newRedelegation(validatorFrom, validatorTo, Shares, createdCoins)
|
||||
setRedelegation(redelegation)
|
||||
return
|
||||
```
|
||||
|
||||
### TxProveLive
|
||||
|
||||
If a validator was automatically unbonded due to liveness issues and wishes to
|
||||
assert it is still online, it can send `TxProveLive`:
|
||||
|
||||
```golang
|
||||
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
|
||||
```
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
# Validator Set Changes
|
||||
|
||||
The validator set may be updated by state transitions that run at the beginning and
|
||||
end of every block. This can happen one of three ways:
|
||||
|
||||
- voting power of a validator changes due to bonding and unbonding
|
||||
- voting power of validator is "slashed" due to conflicting signed messages
|
||||
- validator is automatically unbonded due to inactivity
|
||||
|
||||
## Voting Power 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:
|
||||
|
||||
|
@ -101,90 +97,3 @@ unbondedToBondedPool(validator Validator):
|
|||
return transfer(address of the unbonded pool, address of the bonded pool, removedTokens)
|
||||
```
|
||||
|
||||
|
||||
## Slashing
|
||||
|
||||
Messges which may compromise the safety of the underlying consensus protocol ("equivocations")
|
||||
result in some amount of the offending validator's shares being removed ("slashed").
|
||||
|
||||
Currently, such messages include only the following:
|
||||
|
||||
- prevotes by the same validator for more than one BlockID at the same
|
||||
Height and Round
|
||||
- precommits by the same validator for more than one BlockID at the same
|
||||
Height and Round
|
||||
|
||||
We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the
|
||||
detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending
|
||||
validators punished.
|
||||
|
||||
For some `evidence` to be valid, it must satisfy:
|
||||
|
||||
`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE`
|
||||
|
||||
where `evidence.Timestamp` is the timestamp in the block at height
|
||||
`evidence.Height` and `block.Timestamp` is the current block timestamp.
|
||||
|
||||
If valid evidence is included in a block, the offending validator loses
|
||||
a constant `SLASH_PROPORTION` of their current stake at the beginning of the block:
|
||||
|
||||
```
|
||||
oldShares = validator.shares
|
||||
validator.shares = oldShares * (1 - SLASH_PROPORTION)
|
||||
```
|
||||
|
||||
This ensures that offending validators are punished the same amount whether they
|
||||
act as a single validator with X stake or as N validators with collectively X
|
||||
stake.
|
||||
|
||||
|
||||
## Automatic Unbonding
|
||||
|
||||
Every block includes a set of precommits by the validators for the previous block,
|
||||
known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power.
|
||||
|
||||
Proposers are incentivized to include precommits from all
|
||||
validators in the LastCommit by receiving additional fees
|
||||
proportional to the difference between the voting power included in the
|
||||
LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)).
|
||||
|
||||
Validators are penalized for failing to be included in the LastCommit for some
|
||||
number of blocks by being automatically unbonded.
|
||||
|
||||
The following information is stored with each validator, and is only non-zero if the validator becomes an active validator:
|
||||
|
||||
```go
|
||||
type ValidatorSigningInfo struct {
|
||||
StartHeight int64
|
||||
SignedBlocksBitArray BitArray
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
* `StartHeight` is set to the height that the validator became an active validator (with non-zero voting power).
|
||||
* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks,
|
||||
whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not.
|
||||
Note it is initialized with all 0s.
|
||||
|
||||
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
||||
|
||||
```
|
||||
h = block.Height
|
||||
index = h % SIGNED_BLOCKS_WINDOW
|
||||
|
||||
for val in block.Validators:
|
||||
signInfo = val.SignInfo
|
||||
if val in block.LastCommit:
|
||||
signInfo.SignedBlocksBitArray.Set(index, 0)
|
||||
else
|
||||
signInfo.SignedBlocksBitArray.Set(index, 1)
|
||||
|
||||
// validator must be active for at least SIGNED_BLOCKS_WINDOW
|
||||
// before they can be automatically unbonded for failing to be
|
||||
// included in 50% of the recent LastCommits
|
||||
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
|
||||
minSigned = SIGNED_BLOCKS_WINDOW / 2
|
||||
blocksSigned = signInfo.SignedBlocksBitArray.Sum()
|
||||
if h > minHeight AND blocksSigned < minSigned:
|
||||
unbond the validator
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue