diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 36b9f97f2..3a3d666e0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,8 @@ v Before smashing the submit button please review the checkboxes. v If a checkbox is n/a - please still include it but + a little note why ☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > --> +- Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/cosmos-sdk/blob/develop/CONTRIBUTING.md#pr-targeting)) + - [ ] Linked to github-issue with discussion and accepted design OR link to spec that describes this work. - [ ] Wrote tests - [ ] Updated relevant documentation (`docs/`) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b9d5348..479f5913a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.24.2 + +*August 22nd, 2018* + +BUG FIXES + +* Tendermint + - Fix unbounded consensus WAL growth + ## 0.24.1 *August 21st, 2018* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b7ad81fd..3a7dca210 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,6 +133,17 @@ Libraries need not follow the model strictly, but would be wise to. The SDK utilizes [semantic versioning](https://semver.org/). +### PR Targeting + +Ensure that you base and target your PR on the correct branch: + - `release/vxx.yy.zz` for a merge into a release candidate + - `master` for a merge of a release + - `develop` in the usual case + +All feature additions should be targeted against `develop`. Bug fixes for an outstanding release candidate +should be targeted against the release candidate branch. Release candidate branches themselves should be the +only pull requests targeted directly against master. + ### Development Procedure: - the latest state of development is on `develop` - `develop` must never fail `make test` or `make test_cli` diff --git a/Gopkg.lock b/Gopkg.lock index 73a2ff60f..81591a0ae 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -165,13 +165,12 @@ [[projects]] branch = "master" - digest = "1:a361611b8c8c75a1091f00027767f7779b29cb37c456a71b8f2604c88057ab40" + digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240" name = "github.com/hashicorp/hcl" packages = [ ".", "hcl/ast", "hcl/parser", - "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -424,7 +423,7 @@ version = "v0.9.2" [[projects]] - digest = "1:26146cdb2811ce481e72138439b9b1aa17a64d54364f96bb92f97a9ef8ba4f01" + digest = "1:4f15e95fe3888cc75dd34f407d6394cbc7fd3ff24920851b92b295f6a8b556e6" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -487,8 +486,8 @@ "version", ] pruneopts = "UT" - revision = "013b9cef642f875634c614019ab13b17570778ad" - version = "v0.23.0" + revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e" + version = "v0.23.1-rc0" [[projects]] digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e" diff --git a/Gopkg.toml b/Gopkg.toml index acc3e282a..4368699b6 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -57,7 +57,7 @@ [[override]] name = "github.com/tendermint/tendermint" - version = "=v0.23.0" + version = "=v0.23.1-rc0" [[constraint]] name = "github.com/bartekn/go-bip39" diff --git a/Makefile b/Makefile index de5f6956f..66d566309 100644 --- a/Makefile +++ b/Makefile @@ -152,7 +152,7 @@ test_sim_gaia_nondeterminism: test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=200 -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=50 -v -timeout 24h test_sim_gaia_slow: @echo "Running full Gaia simulation. This may take awhile!" diff --git a/PENDING.md b/PENDING.md index 213453e4a..6d70d9f8a 100644 --- a/PENDING.md +++ b/PENDING.md @@ -16,8 +16,9 @@ BREAKING CHANGES * Gaia * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) * [x/stake] \#1901 Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface. + * [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period * [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed" - + * SDK * [core] \#1807 Switch from use of rational to decimal * [types] \#1901 Validator interface's GetOwner() renamed to GetOperator() diff --git a/docs/spec/slashing/README.md b/docs/spec/slashing/README.md new file mode 100644 index 000000000..dee91eb33 --- /dev/null +++ b/docs/spec/slashing/README.md @@ -0,0 +1,33 @@ +# Slashing module specification + +## Abstract + +This section specifies the slashing module of the Cosmos SDK, which implements functionality +first outlined in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016. + +The slashing module enables Cosmos SDK-based blockchains to disincentivize any attributable action +by a protocol-recognized actor with value at stake by penalizing them ("slashing"). + +Penalties may include, but are not limited to: +- Burning some amount of their stake +- Removing their ability to vote on future blocks for a period of time. + +This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosystem. + +## Contents + +1. **[Overview](overview.md)** +1. **[State](state.md)** + 1. [SigningInfo](state.md#signing-info) + 1. [SlashingPeriod](state.md#slashing-period) +1. **[Transactions](transactions.md)** + 1. [Unjail](transactions.md#unjail) +1. **[Hooks](hooks.md)** + 1. [Validator Bonded](hooks.md#validator-bonded) + 1. [Validator Unbonded](hooks.md#validator-unbonded) + 1. [Validator Slashed](hooks.md#validator-slashed) +1. **[Begin Block](begin-block.md)** + 1. [Evidence handling](begin-block.md#evidence-handling) + 1. [Uptime tracking](begin-block.md#uptime-tracking) +1. **[Future Improvements](future-improvements.md)** + 1. [State cleanup](future-improvements.md#state-cleanup) diff --git a/docs/spec/slashing/end_block.md b/docs/spec/slashing/begin-block.md similarity index 86% rename from docs/spec/slashing/end_block.md rename to docs/spec/slashing/begin-block.md index 3eec27372..375e19185 100644 --- a/docs/spec/slashing/end_block.md +++ b/docs/spec/slashing/begin-block.md @@ -1,12 +1,13 @@ -# End-Block +# Begin-Block -## Slashing +## Evidence handling 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 +committed malicious behavior. The relevant information is forwarded to the application as [ABCI -Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259), so the validator an be accordingly punished. +Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259) in `abci.RequestBeginBlock` +so that the validator an be accordingly punished. For some `evidence` to be valid, it must satisfy: @@ -75,7 +76,9 @@ 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 +The amount slashed for all double signature infractions committed within a single slashing period is capped as described in [state-machine.md](state-machine.md). + +## Uptime tracking At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded: @@ -113,3 +116,5 @@ for val in block.Validators: SigningInfo.Set(val.Address, signInfo) ``` + +The amount slashed for downtime slashes is *not* capped by the slashing period in which they are committed, although they do reset it (since the validator is unbonded). diff --git a/docs/spec/slashing/future-improvements.md b/docs/spec/slashing/future-improvements.md new file mode 100644 index 000000000..84be139e8 --- /dev/null +++ b/docs/spec/slashing/future-improvements.md @@ -0,0 +1,4 @@ +## State Cleanup + +Once no evidence for a given slashing period can possibly be valid (the end time plus the unbonding period is less than the current time), +old slashing periods should be cleaned up. This will be implemented post-launch. diff --git a/docs/spec/slashing/hooks.md b/docs/spec/slashing/hooks.md new file mode 100644 index 000000000..36dde61f9 --- /dev/null +++ b/docs/spec/slashing/hooks.md @@ -0,0 +1,58 @@ +## Hooks + +In this section we describe the "hooks" - slashing module code that runs when other events happen. + +### Validator Bonded + +Upon successful bonding of a validator (a given validator entering the "bonded" state, +which may happen on delegation, on unjailing, etc), we create a new `SlashingPeriod` structure for the +now-bonded validator, which `StartHeight` of the current block, `EndHeight` of `0` (sentinel value for not-yet-ended), +and `SlashedSoFar` of `0`: + +``` +onValidatorBonded(address sdk.ValAddress) + + slashingPeriod = SlashingPeriod{ + ValidatorAddr : address, + StartHeight : CurrentHeight, + EndHeight : 0, + SlashedSoFar : 0, + } + setSlashingPeriod(slashingPeriod) + + return +``` + +### Validator Unbonded + +When a validator is unbonded, we update the in-progress `SlashingPeriod` with the current block as the `EndHeight`: + +``` +onValidatorUnbonded(address sdk.ValAddress) + + slashingPeriod = getSlashingPeriod(address, CurrentHeight) + slashingPeriod.EndHeight = CurrentHeight + setSlashingPeriod(slashingPeriod) + + return +``` + +### Validator Slashed + +When a validator is slashed, we look up the appropriate `SlashingPeriod` based on the validator +address and the time of infraction, cap the fraction slashed as `max(SlashFraction, SlashedSoFar)` +(which may be `0`), and update the `SlashingPeriod` with the increased `SlashedSoFar`: + +``` +beforeValidatorSlashed(address sdk.ValAddress, fraction sdk.Rat, infractionHeight int64) + + slashingPeriod = getSlashingPeriod(address, infractionHeight) + totalToSlash = max(slashingPeriod.SlashedSoFar, fraction) + slashingPeriod.SlashedSoFar = totalToSlash + setSlashingPeriod(slashingPeriod) + + remainderToSlash = slashingPeriod.SlashedSoFar - totalToSlash + fraction = remainderToSlash + + continue with slashing +``` diff --git a/docs/spec/slashing/overview.md b/docs/spec/slashing/overview.md new file mode 100644 index 000000000..aa42f6193 --- /dev/null +++ b/docs/spec/slashing/overview.md @@ -0,0 +1,68 @@ +## Conceptual overview + +### States + +At any given time, there are any number of validators registered in the state machine. +Each block, the top `n = MaximumBondedValidators` validators who are not jailed become *bonded*, meaning that they may propose and vote on blocks. +Validators who are *bonded* are *at stake*, meaning that part or all of their stake and their delegators' stake is at risk if they commit a protocol fault. + +### Slashing period + +In order to mitigate the impact of initially likely categories of non-malicious protocol faults, the Cosmos Hub implements for each validator +a *slashing period*, in which the amount by which a validator can be slashed is capped at the punishment for the worst violation. For example, +if you misconfigure your HSM and double-sign a bunch of old blocks, you'll only be punished for the first double-sign (and then immediately jailed, +so that you have a chance to reconfigure your setup). This will still be quite expensive and desirable to avoid, but slashing periods somewhat blunt +the economic impact of unintentional misconfiguration. + +Unlike the unbonding period, the slashing period doesn't have a fixed length. A new slashing period starts whenever a validator is bonded and ends +whenever the validator is unbonded (which will happen if the validator is jailed). The amount of tokens slashed relative to validator power for infractions +committed within the slashing period, whenever they are discovered, is capped at the punishment for the worst infraction +(which for the Cosmos Hub at launch will be double-signing a block). + +#### ASCII timelines + +*Code* + +*[* : timeline start +*]* : timeline end +*<* : slashing period start +*>* : slashing period end +*Cn* : infraction `n` committed +*Dn* : infraction `n` discovered +*Vb* : validator bonded +*Vu* : validator unbonded + +*Single infraction* + +<-----------------> +[----------C1----D1,Vu-----] + +A single infraction is committed then later discovered, at which point the validator is unbonded and slashed at the full amount for the infraction. + +*Multiple infractions* + +<---------------------------> +[----------C1--C2---C3---D1,D2,D3Vu-----] + +Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction. + +*Multiple infractions after rebonding* + + +<--------------------------->                        <-------------> +[----------C1--C2---C3---D1,D2,D3Vu---Vb---C4----D4,Vu--] + +Multiple infractions are committed within a single slashing period then later discovered, at which point the validator is unbonded and slashed for only the worst infraction. +The validator then unjails themself and rebonds, then commits a fourth infraction - which is discovered and punished at the full amount, since a new slashing period started +when they unjailed and rebonded. + +### Safety note + +Slashing is capped fractionally per period, but the amount of total bonded stake associated with any given validator can change (by an unbounded amount) over that period. + +For example, with MaxFractionSlashedPerPeriod = `0.5`, if a validator is initially slashed at `0.4` near the start of a period when they have 100 stake bonded, +then later slashed at `0.4` when they have `1000` stake bonded, the total amount slashed is just `40 + 100 = 140` (since the latter slash is capped at `0.1`) - +whereas if they had `1000` stake bonded initially, the first offense would have been slashed for `400` stake and the total amount slashed would have been `400 + 100 = 500`. + +This means that any slashing events which utilize the slashing period (are capped-per-period) **must also** jail the validator when the infraction is discovered. +Otherwise it would be possible for a validator to slash themselves intentionally at a low bond, then increase their bond but no longer be at stake since they would have already hit the `SlashedSoFar` cap. diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index 8bbb22c76..ae426db7b 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -1,6 +1,6 @@ -## State +# State -### Signing Info +## 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. @@ -36,10 +36,11 @@ The information stored for tracking validator liveness is as follows: ```go type ValidatorSigningInfo struct { - StartHeight int64 - IndexOffset int64 - JailedUntil int64 - SignedBlocksCounter int64 + StartHeight int64 // Height at which the validator became able to sign blocks + IndexOffset int64 // Offset into the signed block bit array + JailedUntilHeight int64 // Block height until which the validator is jailed, + // or sentinel value of 0 for not jailed + SignedBlocksCounter int64 // Running counter of signed blocks } ``` @@ -49,3 +50,31 @@ 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 jailed due to downtime * `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. + +## Slashing Period + +A slashing period is a start and end block height associated with a particular validator, +within which only the "worst infraction counts" (see the [Overview](overview.md)): the total +amount of slashing for infractions committed within the period (and discovered whenever) is +capped at the penalty for the worst offense. + +This period starts when a validator is first bonded and ends when a validator is slashed & jailed +for any reason. When the validator rejoins the validator set (perhaps through unjailing themselves, +and perhaps also changing signing keys), they enter into a new period. + +Slashing periods are indexed in the store as follows: + +- SlashingPeriod: ` 0x03 | ValTendermintAddr | StartHeight -> amino(slashingPeriod) ` + +This allows us to look up slashing period by a validator's address, the only lookup necessary, +and iterate over start height to efficiently retrieve the most recent slashing period(s) +or those beginning after a given height. + +```go +type SlashingPeriod struct { + ValidatorAddr sdk.ValAddress // Tendermint address of the validator + StartHeight int64 // Block height at which slashing period begin + EndHeight int64 // Block height at which slashing period ended + SlashedSoFar sdk.Rat // Fraction slashed so far, cumulative +} +``` diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index cdf495e4d..be33ee096 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -1,19 +1,40 @@ +## Transactions -### TxProveLive +In this section we describe the processing of transactions for the `slashing` module. -If a validator was automatically unbonded due to liveness issues and wishes to -assert it is still online, it can send `TxProveLive`: +### Unjail -```golang -type TxProveLive struct { - PubKey crypto.PubKey +If a validator was automatically unbonded due to downtime and wishes to come back online & +possibly rejoin the bonded set, it must send `TxUnjail`: + +``` +type TxUnjail struct { + ValidatorAddr sdk.AccAddress } + +handleMsgUnjail(tx TxUnjail) + + validator = getValidator(tx.ValidatorAddr) + if validator == nil + fail with "No validator found" + + if !validator.Jailed + fail with "Validator not jailed, cannot unjail" + + info = getValidatorSigningInfo(operator) + if block time < info.JailedUntil + fail with "Validator still jailed, cannot unjail until period has expired" + + // Update the start height so the validator won't be immediately unbonded again + info.StartHeight = BlockHeight + setValidatorSigningInfo(info) + + validator.Jailed = false + setValidator(validator) + + return ``` -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 -``` +If the validator has enough stake to be in the top `n = MaximumBondedValidators`, they will be automatically rebonded, +and all delegators still delegated to the validator will be rebonded and begin to again collect +provisions and rewards. diff --git a/version/version.go b/version/version.go index 707868ae0..8bfea9577 100644 --- a/version/version.go +++ b/version/version.go @@ -3,9 +3,9 @@ package version const Maj = "0" const Min = "24" -const Fix = "1" +const Fix = "2" -const Version = "0.24.1" +const Version = "0.24.2" // GitCommit set by build flags var GitCommit = ""