Merge pull request #1263 from cosmos/bucky/staking-spec-updates

spec updates
This commit is contained in:
Ethan Buchman 2018-06-16 16:55:08 -07:00 committed by GitHub
commit d08328761f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 269 additions and 176 deletions

View File

@ -5,15 +5,14 @@ the Cosmos Hub.
NOTE: the specifications are not yet complete and very much a work in progress.
- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for
sending tokens.
- [Governance](governance) - Governance related specifications including
proposals and voting.
- [IBC](ibc) - Specification of the Cosmos inter-blockchain communication (IBC) protocol.
- [Staking](staking) - Proof-of-stake related specifications including bonding
and delegation transactions, inflation, etc.
- [Slashing](slashing) - Specifications of validator punishment mechanisms
- [Provisioning](provisioning) - Fee distribution, and atom provision distribution specification
- [Store](store) - The core Merkle store that holds the state.
- [Auth](auth) - The structure and authnentication of accounts and transactions.
- [Bank](bank) - Sending tokens.
- [Governance](governance) - Proposals and voting.
- [IBC](ibc) - Inter-Blockchain Communication (IBC) protocol.
- [Staking](staking) - Proof-of-stake bonding, delegation, etc.
- [Slashing](slashing) - Validator punishment mechanisms.
- [Provisioning](provisioning) - Fee distribution, and atom provision distribution
- [Other](other) - Other components of the Cosmos Hub, including the reserve
pool, All in Bits vesting, etc.

View File

@ -1,100 +0,0 @@
# 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 candidate, and is only non-zero if the candidate becomes an active validator:
```go
type ValidatorSigningInfo struct {
StartHeight int64
IndexOffset int64
JailedUntil int64
SignedBlocksCounter int64
SignedBlocksBitArray BitArray
}
```
Where:
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not).
* `JailedUntil` is set whenever the candidate is revoked due to downtime
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.
* `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 `1` if the validator was included, and a `0` 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:
```
height := block.Height
for val in block.Validators:
signInfo = val.SignInfo
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
signInfo.IndexOffset++
previous = signInfo.SignedBlocksBitArray.Get(index)
// update counter if array has changed
if previous and val in block.AbsentValidators:
signInfo.SignedBlocksBitArray.Set(index, 0)
signInfo.SignedBlocksCounter--
else if !previous and val not in block.AbsentValidators:
signInfo.SignedBlocksBitArray.Set(index, 1)
signInfo.SignedBlocksCounter++
// else previous == val not in block.AbsentValidators, no change
// 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
if height > minHeight AND signInfo.SignedBlocksCounter < minSigned:
signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION
slash & unbond the validator
```

0
docs/spec/auth/state.md Normal file
View File

View File

View File

View File

@ -0,0 +1,102 @@
# End-Block
## Slashing
Tendermint blocks can include
[Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator
committed malicious behaviour. The relevant information is forwarded to the
application as [ABCI
Evidence](https://github.com/tendermint/abci/blob/develop/types/types.proto#L259), so the validator an be accordingly 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 validator's stake is reduced by `SLASH_PROPORTION` of
what their stake was when the equivocation occurred (rather than when the evidence was discovered):
```
curVal := validator
oldVal := loadValidator(evidence.Height, evidence.Address)
slashAmount := SLASH_PROPORTION * oldVal.Shares
curVal.Shares = max(0, curVal.Shares - slashAmount)
```
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.
We also need to loop through the unbondings and redelegations to slash them as
well:
```
unbondings := getUnbondings(validator.Address)
for unbond in unbondings {
if was not bonded before evidence.Height {
continue
}
unbond.InitialTokens
burn := unbond.InitialTokens * SLASH_PROPORTION
unbond.Tokens = max(0, unbond.Tokens - burn)
}
// only care if source gets slashed because we're already bonded to destination
// so if destination validator gets slashed our delegation just has same shares
// of smaller pool.
redels := getRedelegationsBySource(validator.Address)
for redel in redels {
if was not bonded before evidence.Height {
continue
}
burn := redel.InitialTokens * SLASH_PROPORTION
amount := unbondFromValidator(redel.Destination, burn)
destroy(amount)
}
```
## Automatic Unbonding
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
```
height := block.Height
for val in block.Validators:
signInfo = SigningInfo.Get(val.Address)
if signInfo == nil{
signInfo.StartHeight = height
}
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
signInfo.IndexOffset++
previous = SigningBitArray.Get(val.Address, index)
// update counter if array has changed
if previous and val in block.AbsentValidators:
SigningBitArray.Set(val.Address, index, false)
signInfo.SignedBlocksCounter--
else if !previous and val not in block.AbsentValidators:
SigningBitArray.Set(val.Address, index, true)
signInfo.SignedBlocksCounter++
// else previous == val not in block.AbsentValidators, no change
// 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
if height > minHeight AND signInfo.SignedBlocksCounter < minSigned:
signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION
slash & unbond the validator
SigningInfo.Set(val.Address, signInfo)
```

View File

@ -0,0 +1,51 @@
## State
### Signing Info
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.
Information about validator activity is tracked in a `ValidatorSigningInfo`.
It is indexed in the store as follows:
- SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)`
- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)`
The first map allows us to easily lookup the recent signing info for a
validator, according to the Tendermint validator address. The second map acts as
a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array.
The index in the bit-array is given as little endian uint64.
The result is a `varint` that takes on `0` or `1`, where `0` indicates the
validator did not sign the corresponding block, and `1` indicates they did.
Note that the SigningBitArray is not explicitly initialized up-front. Keys are
added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly
bonded validator.
The information stored for tracking validator liveness is as follows:
```go
type ValidatorSigningInfo struct {
StartHeight int64
IndexOffset int64
JailedUntil int64
SignedBlocksCounter int64
}
```
Where:
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not).
* `JailedUntil` is set whenever the candidate is revoked due to downtime
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.

View File

@ -1,44 +1,43 @@
## State
### Pool
- index: n/a single-record
- key: `01`
- value: `amino(pool)`
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
validator shares for stake in the global pools, moving Atom inflation
information, etc.
- stored object:
```golang
type Pool struct {
LooseUnbondedTokens int64 // tokens not associated with any validator
UnbondedTokens int64 // reserve of unbonded tokens held with validators
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
BondedTokens int64 // reserve of bonded tokens
UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool
InflationLastTime int64 // block which the last inflation was processed // TODO make time
Inflation sdk.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily)
UnbondedTokens int64 // reserve of unbonded tokens held with validators
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
BondedTokens int64 // reserve of bonded tokens
UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool
InflationLastTime int64 // block which the last inflation was processed // TODO make time
Inflation sdk.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily)
}
type PoolShares struct {
Status sdk.BondStatus // either: unbonded, unbonding, or bonded
Amount sdk.Rat // total shares of type ShareKind
Status sdk.BondStatus // either: unbonded, unbonding, or bonded
Amount sdk.Rat // total shares of type ShareKind
}
```
### Params
- index: n/a single-record
- key: `00`
- value: `amino(params)`
Params is global data structure that stores system parameters and defines
overall functioning of the stake module.
- stored object:
```golang
type Params struct {
InflationRateChange sdk.Rat // maximum annual change in inflation rate
@ -52,40 +51,64 @@ type Params struct {
```
### Validator
- index 1: validator owner address
- index 2: validator Tendermint PubKey
- index 3: bonded validators only
- index 4: voting power
Related Store which holds Validator.ABCIValidator()
- index: validator owner address
Validators are identified according to the `ValOwnerAddr`,
an SDK account address for the owner of the validator.
Validators also have a `ValTendermintAddr`, the address
of the public key of the validator.
Validators are indexed in the store using the following maps:
- Validators: `0x02 | ValOwnerAddr -> amino(validator)`
- ValidatorsByPubKey: `0x03 | ValTendermintAddr -> ValOwnerAddr`
- ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> ValOwnerAddr`
`Validators` is the primary index - it ensures that each owner can have only one
associated validator, where the public key of that validator can change in the
future. Delegators can refer to the immutable owner of the validator, without
concern for the changing public key.
`ValidatorsByPubKey` is a secondary index that enables lookups for slashing.
When Tendermint reports evidence, it provides the validator address, so this
map is needed to find the owner.
`ValidatorsByPower` is a secondary index that provides a sorted list of
potential validators to quickly determine the current active set. For instance,
the first 100 validators in this list can be returned with every EndBlock.
The `Validator` holds the current state and some historical actions of the
validator.
- stored object:
```golang
type Validator struct {
Owner sdk.Address // sender of BondTx - UnbondTx returns here
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
Revoked bool // has the validator been revoked?
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
Revoked bool // has the validator been revoked?
PoolShares PoolShares // total shares for tokens held in the pool
DelegatorShares sdk.Rat // total shares issued to a validator's delegators
SlashRatio sdk.Rat // increases each time the validator is slashed
Description Description // description terms for the validator
// Needed for ordering vals in the bypower key
BondHeight int64 // earliest height as a bonded validator
BondIntraTxCounter int16 // block-local tx index of validator change
CommissionInfo CommissionInfo // info about the validator's commission
ProposerRewardPool sdk.Coins // reward pool collected from being the proposer
// TODO: maybe this belongs in distribution module ?
PrevPoolShares PoolShares // total shares of a global hold pools
}
PoolShares PoolShares // total shares for tokens held in the pool
DelegatorShares sdk.Rat // total shares issued to a validator's delegators
SlashRatio sdk.Rat // increases each time the validator is slashed
Description Description // description terms for the validator
BondHeight int64 // earliest height as a bonded validator
BondIntraTxCounter int16 // block-local tx index of validator change
ProposerRewardPool sdk.Coins // reward pool collected from being the proposer
Commission sdk.Rat // the commission rate of fees charged to any delegators
CommissionMax sdk.Rat // maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Rat // maximum daily increase of the validator commission
CommissionChangeToday sdk.Rat // commission rate change today, reset each day (UTC time)
PrevPoolShares PoolShares // total shares of a global hold pools
type CommissionInfo struct {
Rate sdk.Rat // the commission rate of fees charged to any delegators
Max sdk.Rat // maximum commission rate which this validator can ever charge
ChangeRate sdk.Rat // maximum daily increase of the validator commission
ChangeToday sdk.Rat // commission rate change today, reset each day (UTC time)
LastChange int64 // unix timestamp of last commission change
}
type Description struct {
@ -97,47 +120,71 @@ type Description struct {
```
### Delegation
- index: delegation address
Delegations are identified by combining `DelegatorAddr` (the address of the delegator) with the ValOwnerAddr
Delegators are indexed in the store as follows:
- Delegation: ` 0x0A | DelegatorAddr | ValOwnerAddr -> amino(delegation)`
Atom holders may delegate coins to validators; under this circumstance their
funds are held in a `Delegation` data structure. It is owned by one
delegator, and is associated with the shares for one validator. The sender of
the transaction is the owner of the bond.
- stored object:
```golang
type Delegation struct {
DelegatorAddr sdk.Address // delegation owner address
ValidatorAddr sdk.Address // validator owner address
Shares sdk.Rat // delegation shares recieved
Height int64 // last height bond updated
Shares sdk.Rat // delegation shares recieved
Height int64 // last height bond updated
}
```
### UnbondingDelegation
- index: delegation address
Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`,
where shares can be reduced if Byzantine behaviour is detected.
`UnbondingDelegation` are indexed in the store as:
- UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | ValOwnerAddr ->
amino(unbondingDelegation)`
- UnbondingDelegationByValOwner: ` 0x0C | ValOwnerAddr | DelegatorAddr | ValOwnerAddr ->
nil`
The first map here is used in queries, to lookup all unbonding delegations for
a given delegator, while the second map is used in slashing, to lookup all
unbonding delegations associated with a given validator that need to be
slashed.
A UnbondingDelegation object is created every time an unbonding is initiated.
The unbond must be completed with a second transaction provided by the
delegation owner after the unbonding period has passed.
- stored object:
```golang
type UnbondingDelegation struct {
DelegationKey sdk.Address // key of the delegation
ExpectedTokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding
StartSlashRatio sdk.Rat // validator slash ratio at unbonding initiation
Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding
CompleteTime int64 // unix time to complete redelegation
CompleteHeight int64 // block height to complete redelegation
}
```
### Redelegation
- index 1: delegation address
- index 2: source validator owner address
- index 3: destination validator owner address
Shares in a `Delegation` can be rebonded to a different validator, but they must for some time exist as a `Redelegation`,
where shares can be reduced if Byzantine behaviour is detected. This is tracked
as moving a delegation from a `FromValOwnerAddr` to a `ToValOwnerAddr`.
`Redelegation` are indexed in the store as:
- Redelegations: `0x0D | DelegatorAddr | FromValOwnerAddr | ToValOwnerAddr ->
amino(redelegation)`
- RedelegationsBySrc: `0x0E | FromValOwnerAddr | ToValOwnerAddr |
DelegatorAddr -> nil`
- RedelegationsByDst: `0x0F | ToValOwnerAddr | FromValOwnerAddr | DelegatorAddr
-> nil`
The first map here is used for queries, to lookup all redelegations for a given
delegator. The second map is used for slashing based on the FromValOwnerAddr,
while the third map is for slashing based on the ToValOwnerAddr.
A redelegation object is created every time a redelegation occurs. The
redelegation must be completed with a second transaction provided by the
@ -145,16 +192,10 @@ 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.
- stored object:
```golang
type Redelegation struct {
SourceDelegation sdk.Address // source delegation key
DestinationDelegation sdk.Address // destination delegation key
SourceShares sdk.Rat // amount of source shares redelegating
DestinationShares sdk.Rat // amount of destination shares created at redelegation
SourceStartSlashRatio sdk.Rat // source validator slash ratio at unbonding initiation
CompleteTime int64 // unix time to complete redelegation
CompleteHeight int64 // block height to complete redelegation
}
```

View File