From 85389f0db9d60f938555634c90cb9f0cbb47939d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 14 Jun 2018 17:48:33 -0700 Subject: [PATCH 1/7] docs/spec/staking: update state.md --- docs/spec/staking/state.md | 87 +++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 1cfe4b26e..064d4879a 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -52,13 +52,21 @@ 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 +val owner address: SDK account addresss of the owner of the validator :) +tm val pubkey: Public Key of the Tendermint Validator + + - map1: -> + - map2: -> + - map3: -> + + map1 is the main lookup. each owner can have only one validator. + delegators point to an immutable owner + owners can change their TM val pubkey + need map2 so we can do lookups for slashing ! + need map3 so we have sorted vals to know the top 100 + +----------- The `Validator` holds the current state and some historical actions of the validator. @@ -67,7 +75,6 @@ validator. ```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? @@ -75,17 +82,26 @@ type Validator struct { 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 + Description Description // description terms for the validator - 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) + // 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 - PrevPoolShares PoolShares // total shares of a global hold pools + 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 +} + +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,7 +113,8 @@ type Description struct { ``` ### Delegation - - index: delegation address + + - map1: < delegator address | val owner address > -> < 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 @@ -108,15 +125,18 @@ the transaction is the owner of the bond. ```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 + + - map1: < prefix-unbonding | delegator address | val owner address > -> < unbonding delegation > + - map2: < prefix-unbonding | val owner address | delegator address > -> nil + + map1 for queries. + map2 for eager slashing A UnbondingDelegation object is created every time an unbonding is initiated. The unbond must be completed with a second transaction provided by the @@ -126,18 +146,23 @@ delegation owner after the unbonding period has passed. ```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 + + - map1: < prefix-redelegation | delegator address | from val owner address | to + val owner address > -> < redelegation > + - map2: < prefix-redelegation | from val owner address | to + val owner address | delegator > -> nil + - map2: < prefix-redelegation | to val owner address | from + val owner address | delegator > -> nil + + map1: queries + map2: slash for from validator + map3: slash for to validator A redelegation object is created every time a redelegation occurs. The redelegation must be completed with a second transaction provided by the @@ -149,12 +174,8 @@ the original redelegation has been completed. ```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 } ``` From cab0a9bbae502a2a212a13adb52f65883733532f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 14 Jun 2018 20:26:21 -0700 Subject: [PATCH 2/7] docs/spec/slashing --- .../end-block.md} | 73 ++++++++++++++++--- docs/spec/{WIP_slashing => slashing}/state.md | 0 .../transactions.md | 0 3 files changed, 62 insertions(+), 11 deletions(-) rename docs/spec/{WIP_slashing/valset-changes.md => slashing/end-block.md} (63%) rename docs/spec/{WIP_slashing => slashing}/state.md (100%) rename docs/spec/{WIP_slashing => slashing}/transactions.md (100%) diff --git a/docs/spec/WIP_slashing/valset-changes.md b/docs/spec/slashing/end-block.md similarity index 63% rename from docs/spec/WIP_slashing/valset-changes.md rename to docs/spec/slashing/end-block.md index 8aad213b8..840269d3e 100644 --- a/docs/spec/WIP_slashing/valset-changes.md +++ b/docs/spec/slashing/end-block.md @@ -23,18 +23,52 @@ For some `evidence` to be valid, it must satisfy: 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: +If valid evidence is included in a block, the validator's stake is reduced by `SLASH_PROPORTION` of +what there stake was at the eqiuvocation occurred (rather than when it was found): ``` -oldShares = validator.shares -validator.shares = oldShares * (1 - SLASH_PROPORTION) +curVal := validator +oldVal := loadValidator(evidence.Height, evidence.Address) + +slashAmount := SLASH_PROPORTION * oldVal.Shares + +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 -= 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 @@ -49,6 +83,11 @@ 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. +Maps: + +- map1: < prefix-info | tm val addr > -> +- map2: < prefix-bit-array | tm val addr | LE uint64 index in sign bit array > -> < signed bool > + The following information is stored with each validator candidate, and is only non-zero if the candidate becomes an active validator: ```go @@ -57,8 +96,8 @@ type ValidatorSigningInfo struct { IndexOffset int64 JailedUntil int64 SignedBlocksCounter int64 - SignedBlocksBitArray BitArray } + ``` Where: @@ -66,8 +105,13 @@ Where: * `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. + + +Map2 simulates a bit array - better to do the lookup rather than read/write the +bitarray every time. Size of bit-array is `SIGNED_BLOCKS_WINDOW`. It records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks, +whether or not this validator was included in the LastCommit. +It sets the value to true if the validator was included and false if not. +Note it is not explicilty initialized (the keys wont exist). At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: @@ -75,17 +119,21 @@ At the beginning of each block, we update the signing info for each validator an height := block.Height for val in block.Validators: - signInfo = val.SignInfo + signInfo = getSignInfo(val.Address) + if signInfo == nil{ + signInfo.StartHeight = height + } + index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW signInfo.IndexOffset++ - previous = signInfo.SignedBlocksBitArray.Get(index) + previous = getDidSign(val.Address, index) // update counter if array has changed if previous and val in block.AbsentValidators: - signInfo.SignedBlocksBitArray.Set(index, 0) + setDidSign(val.Address, index, false) signInfo.SignedBlocksCounter-- else if !previous and val not in block.AbsentValidators: - signInfo.SignedBlocksBitArray.Set(index, 1) + setDidSign(val.Address, index, true) signInfo.SignedBlocksCounter++ // else previous == val not in block.AbsentValidators, no change @@ -96,5 +144,8 @@ for val in block.Validators: minSigned = SIGNED_BLOCKS_WINDOW / 2 if height > minHeight AND signInfo.SignedBlocksCounter < minSigned: signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION + slash & unbond the validator + + setSignInfo(val.Address, signInfo) ``` diff --git a/docs/spec/WIP_slashing/state.md b/docs/spec/slashing/state.md similarity index 100% rename from docs/spec/WIP_slashing/state.md rename to docs/spec/slashing/state.md diff --git a/docs/spec/WIP_slashing/transactions.md b/docs/spec/slashing/transactions.md similarity index 100% rename from docs/spec/WIP_slashing/transactions.md rename to docs/spec/slashing/transactions.md From 1b93f468bc32ef33df76b3c790214fba47f2fef2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 Jun 2018 02:53:00 -0700 Subject: [PATCH 3/7] docs/spec/staking: clean up state.md --- docs/spec/staking/state.md | 162 +++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 71 deletions(-) diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 064d4879a..2593f4754 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -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 @@ -53,54 +52,62 @@ type Params struct { ### Validator -val owner address: SDK account addresss of the owner of the validator :) -tm val pubkey: Public Key of the Tendermint Validator +Validators are identified according to the `ValOwnerAddr`, +an SDK account address for the owner of the validator. - - map1: -> - - map2: -> - - map3: -> +Validators also have a `ValTendermintAddr`, the address +of the public key of the validator. - map1 is the main lookup. each owner can have only one validator. - delegators point to an immutable owner - owners can change their TM val pubkey - need map2 so we can do lookups for slashing ! - need map3 so we have sorted vals to know the top 100 +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 { - 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 - + 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 - + 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 - + + 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 + 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) + 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 } @@ -114,15 +121,16 @@ type Description struct { ### Delegation - - map1: < delegator address | val owner address > -> < delegation > +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 { Shares sdk.Rat // delegation shares recieved @@ -132,18 +140,25 @@ type Delegation struct { ### UnbondingDelegation - - map1: < prefix-unbonding | delegator address | val owner address > -> < unbonding delegation > - - map2: < prefix-unbonding | val owner address | delegator address > -> nil +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. - map1 for queries. - map2 for eager slashing +`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 { Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding @@ -153,16 +168,23 @@ type UnbondingDelegation struct { ### Redelegation - - map1: < prefix-redelegation | delegator address | from val owner address | to - val owner address > -> < redelegation > - - map2: < prefix-redelegation | from val owner address | to - val owner address | delegator > -> nil - - map2: < prefix-redelegation | to val owner address | from - val owner address | delegator > -> nil +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`. - map1: queries - map2: slash for from validator - map3: slash for to validator +`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 @@ -170,8 +192,6 @@ 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 { SourceShares sdk.Rat // amount of source shares redelegating From 902d066f52338d97488da304a1641e4d4b313e01 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 Jun 2018 03:22:06 -0700 Subject: [PATCH 4/7] docs/spec/slashing: separate out a proper state.md --- docs/spec/WIP_provisioning/state.md | 13 +++++ .../slashing/{end-block.md => end_block.md} | 53 +++--------------- docs/spec/slashing/state.md | 54 ++++++++++++++++--- 3 files changed, 65 insertions(+), 55 deletions(-) create mode 100644 docs/spec/WIP_provisioning/state.md rename docs/spec/slashing/{end-block.md => end_block.md} (60%) diff --git a/docs/spec/WIP_provisioning/state.md b/docs/spec/WIP_provisioning/state.md new file mode 100644 index 000000000..0711b01aa --- /dev/null +++ b/docs/spec/WIP_provisioning/state.md @@ -0,0 +1,13 @@ + + +Validator + +* Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` + +Delegation Shares + +* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` +* AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Validator.ProposerRewardPool` diff --git a/docs/spec/slashing/end-block.md b/docs/spec/slashing/end_block.md similarity index 60% rename from docs/spec/slashing/end-block.md rename to docs/spec/slashing/end_block.md index 840269d3e..d86b7110b 100644 --- a/docs/spec/slashing/end-block.md +++ b/docs/spec/slashing/end_block.md @@ -1,4 +1,4 @@ -# Validator Set Changes +# End-Block ## Slashing @@ -72,68 +72,27 @@ for redel in redels { ## 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. - -Maps: - -- map1: < prefix-info | tm val addr > -> -- map2: < prefix-bit-array | tm val addr | LE uint64 index in sign bit array > -> < signed bool > - -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 -} - -``` - -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. - - -Map2 simulates a bit array - better to do the lookup rather than read/write the -bitarray every time. Size of bit-array is `SIGNED_BLOCKS_WINDOW`. It records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks, -whether or not this validator was included in the LastCommit. -It sets the value to true if the validator was included and false if not. -Note it is not explicilty initialized (the keys wont exist). - 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 = getSignInfo(val.Address) + signInfo = SigningInfo.Get(val.Address) if signInfo == nil{ signInfo.StartHeight = height } index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW signInfo.IndexOffset++ - previous = getDidSign(val.Address, index) + previous = SigningBitArray.Get(val.Address, index) // update counter if array has changed if previous and val in block.AbsentValidators: - setDidSign(val.Address, index, false) + SigningBitArray.Set(val.Address, index, false) signInfo.SignedBlocksCounter-- else if !previous and val not in block.AbsentValidators: - setDidSign(val.Address, index, true) + SigningBitArray.Set(val.Address, index, true) signInfo.SignedBlocksCounter++ // else previous == val not in block.AbsentValidators, no change @@ -147,5 +106,5 @@ for val in block.Validators: slash & unbond the validator - setSignInfo(val.Address, signInfo) + SigningInfo.Set(val.Address, signInfo) ``` diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index 0711b01aa..1df9d5022 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -1,13 +1,51 @@ +## State +### Signing Info -Validator +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. -* Adjustment factor used to passively calculate each validators entitled fees - from `GlobalState.FeePool` +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)). -Delegation Shares +Validators are penalized for failing to be included in the LastCommit for some +number of blocks by being automatically unbonded. -* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds - entitled fees from `GlobalState.FeePool` -* AdjustmentRewardPool: Adjustment factor used to passively calculate each - bonds entitled fees from `Validator.ProposerRewardPool` +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. From 0a6d09ebb32809b12e2caa7f3c61f6bde31bacc0 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 15 Jun 2018 23:26:14 +0200 Subject: [PATCH 5/7] Fix two typos, ensure nonnegative tokens --- docs/spec/slashing/end_block.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spec/slashing/end_block.md b/docs/spec/slashing/end_block.md index d86b7110b..ef36250eb 100644 --- a/docs/spec/slashing/end_block.md +++ b/docs/spec/slashing/end_block.md @@ -24,7 +24,7 @@ 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 there stake was at the eqiuvocation occurred (rather than when it was found): +what their stake was when the equivocation occurred (rather than when the evidence was discovered): ``` curVal := validator @@ -32,7 +32,7 @@ oldVal := loadValidator(evidence.Height, evidence.Address) slashAmount := SLASH_PROPORTION * oldVal.Shares -curVal.Shares -= slashAmount +curVal.Shares = max(0, curVal.Shares - slashAmount) ``` This ensures that offending validators are punished the same amount whether they @@ -50,7 +50,7 @@ for unbond in unbondings { } unbond.InitialTokens burn := unbond.InitialTokens * SLASH_PROPORTION - unbond.Tokens -= burn + unbond.Tokens = max(0, unbond.Tokens - burn) } // only care if source gets slashed because we're already bonded to destination From 2219c3b1c784b5f67656adbe888bad2c4f59005c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 15 Jun 2018 23:26:09 -0700 Subject: [PATCH 6/7] docs/spec/slashing: point to Tendermint evidence --- docs/spec/slashing/end_block.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/spec/slashing/end_block.md b/docs/spec/slashing/end_block.md index ef36250eb..5958d0a21 100644 --- a/docs/spec/slashing/end_block.md +++ b/docs/spec/slashing/end_block.md @@ -2,19 +2,11 @@ ## 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. +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: From 190d87d750d6695b47be240fd821bc3f1ea63e8d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 16 Jun 2018 00:39:15 -0700 Subject: [PATCH 7/7] docs/spec: flesh out dir structure and update index --- docs/spec/README.md | 17 ++++++++--------- docs/spec/auth/state.md | 0 docs/spec/auth/transactions.md | 0 .../{basecoin/basecoin.md => bank/state.md} | 0 docs/spec/bank/transactions.md | 0 .../fee_distribution_model.xlsx | Bin .../overview.md | 0 .../state.md | 0 docs/spec/store/README.md | 0 9 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 docs/spec/auth/state.md create mode 100644 docs/spec/auth/transactions.md rename docs/spec/{basecoin/basecoin.md => bank/state.md} (100%) create mode 100644 docs/spec/bank/transactions.md rename docs/spec/{WIP_provisioning => provisioning}/fee_distribution_model.xlsx (100%) rename docs/spec/{WIP_provisioning => provisioning}/overview.md (100%) rename docs/spec/{WIP_provisioning => provisioning}/state.md (100%) create mode 100644 docs/spec/store/README.md diff --git a/docs/spec/README.md b/docs/spec/README.md index b115e0d45..4b58baf9e 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -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. diff --git a/docs/spec/auth/state.md b/docs/spec/auth/state.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/spec/auth/transactions.md b/docs/spec/auth/transactions.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/spec/basecoin/basecoin.md b/docs/spec/bank/state.md similarity index 100% rename from docs/spec/basecoin/basecoin.md rename to docs/spec/bank/state.md diff --git a/docs/spec/bank/transactions.md b/docs/spec/bank/transactions.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/spec/WIP_provisioning/fee_distribution_model.xlsx b/docs/spec/provisioning/fee_distribution_model.xlsx similarity index 100% rename from docs/spec/WIP_provisioning/fee_distribution_model.xlsx rename to docs/spec/provisioning/fee_distribution_model.xlsx diff --git a/docs/spec/WIP_provisioning/overview.md b/docs/spec/provisioning/overview.md similarity index 100% rename from docs/spec/WIP_provisioning/overview.md rename to docs/spec/provisioning/overview.md diff --git a/docs/spec/WIP_provisioning/state.md b/docs/spec/provisioning/state.md similarity index 100% rename from docs/spec/WIP_provisioning/state.md rename to docs/spec/provisioning/state.md diff --git a/docs/spec/store/README.md b/docs/spec/store/README.md new file mode 100644 index 000000000..e69de29bb