From a374f74f7c914a3c7640694ae80c33bf74e05688 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Fri, 20 Aug 2021 13:26:04 -0400 Subject: [PATCH 1/2] e2e: cleanup node start function (#6842) I realized after my last commit that my change made a following line of code a bit redundant. (alternatively my last change was redunadnt to the existing code.) I took this oppertunity to make some minor cleanups and logging changes to the node changes which I hope will make tests a bit more clear. --- test/e2e/runner/start.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index ca3ebfcbd..c8d6163ed 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -9,6 +9,9 @@ import ( ) func Start(testnet *e2e.Testnet) error { + if len(testnet.Nodes) == 0 { + return fmt.Errorf("no nodes in testnet") + } // Nodes are already sorted by name. Sort them by name then startAt, // which gives the overall order startAt, mode, name. @@ -29,9 +32,7 @@ func Start(testnet *e2e.Testnet) error { sort.SliceStable(nodeQueue, func(i, j int) bool { return nodeQueue[i].StartAt < nodeQueue[j].StartAt }) - if len(nodeQueue) == 0 { - return fmt.Errorf("no nodes in testnet") - } + if nodeQueue[0].StartAt > 0 { return fmt.Errorf("no initial nodes in testnet") } @@ -93,10 +94,8 @@ func Start(testnet *e2e.Testnet) error { } } - logger.Info(fmt.Sprintf("Starting node %v at height %v...", node.Name, node.StartAt)) - if _, _, err := waitForHeight(testnet, node.StartAt); err != nil { - return err - } + logger.Info("Starting catch up node", "node", node.Name, "height", node.StartAt) + if err := execCompose(testnet.Dir, "up", "-d", node.Name); err != nil { return err } From 8700ca9d1a0c74b10386657591720953f8f9dfd9 Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Fri, 20 Aug 2021 16:07:20 -0400 Subject: [PATCH 2/2] ADR 071: Proposer-based Timestamps (#6799) Architectural decision record for Proposer-based timestamps. --- .../adr-071-proposer-based-timestamps.md | 445 ++++++++++++++++++ docs/architecture/img/pbts-message.png | Bin 0 -> 32028 bytes 2 files changed, 445 insertions(+) create mode 100644 docs/architecture/adr-071-proposer-based-timestamps.md create mode 100644 docs/architecture/img/pbts-message.png diff --git a/docs/architecture/adr-071-proposer-based-timestamps.md b/docs/architecture/adr-071-proposer-based-timestamps.md new file mode 100644 index 000000000..c23488005 --- /dev/null +++ b/docs/architecture/adr-071-proposer-based-timestamps.md @@ -0,0 +1,445 @@ +# ADR 71: Proposer-Based Timestamps + +* [Changelog](#changelog) +* [Status](#status) +* [Context](#context) +* [Alternative Approaches](#alternative-approaches) + * [Remove timestamps altogether](#remove-timestamps-altogether) +* [Decision](#decision) +* [Detailed Design](#detailed-design) + * [Overview](#overview) + * [Proposal Timestamp and Block Timestamp](#proposal-timestamp-and-block-timestamp) + * [Saving the timestamp across heights](#saving-the-timestamp-across-heights) + * [Changes to `CommitSig`](#changes-to-commitsig) + * [Changes to `Commit`](#changes-to-commit) + * [Changes to `Vote` messages](#changes-to-vote-messages) + * [New consensus parameters](#new-consensus-parameters) + * [Changes to `Header`](#changes-to-header) + * [Changes to the block proposal step](#changes-to-the-block-proposal-step) + * [Proposer selects proposal timestamp](#proposer-selects-proposal-timestamp) + * [Proposer selects block timestamp](#proposer-selects-block-timestamp) + * [Proposer waits](#proposer-waits) + * [Changes to the propose step timeout](#changes-to-the-propose-step-timeout) + * [Changes to validation rules](#changes-to-validation-rules) + * [Proposal timestamp validation](#proposal-timestamp-validation) + * [Block timestamp validation](#block-timestamp-validation) + * [Changes to the prevote step](#changes-to-the-prevote-step) + * [Changes to the precommit step](#changes-to-the-precommit-step) + * [Changes to locking a block](#changes-to-locking-a-block) + * [Remove voteTime Completely](#remove-votetime-completely) +* [Future Improvements](#future-improvements) +* [Consequences](#consequences) + * [Positive](#positive) + * [Neutral](#neutral) + * [Negative](#negative) +* [References](#references) + +## Changelog + + - July 15 2021: Created by @williambanfield + - Aug 4 2021: Draft completed by @williambanfield + - Aug 5 2021: Draft updated to include data structure changes by @williambanfield + - Aug 20 2021: Language edits completed by @williambanfield + +## Status + + **Accepted** + +## Context + +Tendermint currently provides a monotonically increasing source of time known as [BFTTime](https://github.com/tendermint/spec/blob/master/spec/consensus/bft-time.md). +This mechanism for producing a source of time is reasonably simple. +Each correct validator adds a timestamp to each `Precommit` message it sends. +The timestamp it sends is either the validator's current known Unix time or one millisecond greater than the previous block time, depending on which value is greater. +When a block is produced, the proposer chooses the block timestamp as the weighted median of the times in all of the `Precommit` messages the proposer received. +The weighting is proportional to the amount of voting power, or stake, a validator has on the network. +This mechanism for producing timestamps is both deterministic and byzantine fault tolerant. + +This current mechanism for producing timestamps has a few drawbacks. +Validators do not have to agree at all on how close the selected block timestamp is to their own currently known Unix time. +Additionally, any amount of voting power `>1/3` may directly control the block timestamp. +As a result, it is quite possible that the timestamp is not particularly meaningful. + +These drawbacks present issues in the Tendermint protocol. +Timestamps are used by light clients to verify blocks. +Light clients rely on correspondence between their own currently known Unix time and the block timestamp to verify blocks they see; +However, their currently known Unix time may be greatly divergent from the block timestamp as a result of the limitations of `BFTTime`. + +The proposer-based timestamps specification suggests an alternative approach for producing block timestamps that remedies these issues. +Proposer-based timestamps alter the current mechanism for producing block timestamps in two main ways: + +1. The block proposer is amended to offer up its currently known Unix time as the timestamp for the next block. +1. Correct validators only approve the proposed block timestamp if it is close enough to their own currently known Unix time. + +The result of these changes is a more meaningful timestamp that cannot be controlled by `<= 2/3` of the validator voting power. +This document outlines the necessary code changes in Tendermint to implement the corresponding [proposer-based timestamps specification](https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp). + +## Alternative Approaches + +### Remove timestamps altogether + +Computer clocks are bound to skew for a variety of reasons. +Using timestamps in our protocol means either accepting the timestamps as not reliable or impacting the protocol’s liveness guarantees. +This design requires impacting the protocol’s liveness in order to make the timestamps more reliable. +An alternate approach is to remove timestamps altogether from the block protocol. +`BFTTime` is deterministic but may be arbitrarily inaccurate. +However, having a reliable source of time is quite useful for applications and protocols built on top of a blockchain. + +We therefore decided not to remove the timestamp. +Applications often wish for some transactions to occur on a certain day, on a regular period, or after some time following a different event. +All of these require some meaningful representation of agreed upon time. +The following protocols and application features require a reliable source of time: +* Tendermint Light Clients [rely on correspondence between their known time](https://github.com/tendermint/spec/blob/master/spec/light-client/verification/README.md#definitions-1) and the block time for block verification. +* Tendermint Evidence validity is determined [either in terms of heights or in terms of time](https://github.com/tendermint/spec/blob/8029cf7a0fcc89a5004e173ec065aa48ad5ba3c8/spec/consensus/evidence.md#verification). +* Unbonding of staked assets in the Cosmos Hub [occurs after a period of 21 days](https://github.com/cosmos/governance/blob/ce75de4019b0129f6efcbb0e752cd2cc9e6136d3/params-change/Staking.md#unbondingtime). +* IBC packets can use either a [timestamp or a height to timeout packet delivery](https://docs.cosmos.network/v0.43/ibc/overview.html#acknowledgements). + +Finally, inflation distribution in the Cosmos Hub uses an approximation of time to calculate an annual percentage rate. +This approximation of time is calculated using [block heights with an estimated number of blocks produced in a year](https://github.com/cosmos/governance/blob/master/params-change/Mint.md#blocksperyear). +Proposer-based timestamps will allow this inflation calculation to use a more meaningful and accurate source of time. + + +## Decision + +Implement proposer-based timestamps and remove `BFTTime`. + +## Detailed Design + +### Overview + +Implementing proposer-based timestamps will require a few changes to Tendermint’s code. +These changes will be to the following components: +* The `internal/consensus/` package. +* The `state/` package. +* The `Vote`, `CommitSig`, `Commit` and `Header` types. +* The consensus parameters. + +### Proposal Timestamp and Block Timestamp + +This design discusses two timestamps: (1) The timestamp in the block and (2) the timestamp in the proposal message. +The existence and use of both of these timestamps can get a bit confusing, so some background is given here to clarify their uses. + +The [proposal message currently has a timestamp](https://github.com/tendermint/tendermint/blob/e5312942e30331e7c42b75426da2c6c9c00ae476/types/proposal.go#L31). +This timestamp is the current Unix time known to the proposer when sending the `Proposal` message. +This timestamp is not currently used as part of consensus. +The changes in this ADR will begin using the proposal message timestamp as part of consensus. +We will refer to this as the **proposal timestamp** throughout this design. + +The block has a timestamp field [in the header](https://github.com/tendermint/tendermint/blob/dc7c212c41a360bfe6eb38a6dd8c709bbc39aae7/types/block.go#L338). +This timestamp is set currently as part of Tendermint’s `BFTtime` algorithm. +It is set when a block is proposed and it is checked by the validators when they are deciding to prevote the block. +This field will continue to be used but the logic for creating and validating this timestamp will change. +We will refer to this as the **block timestamp** throughout this design. + +At a high level, the proposal timestamp from height `H` is used as the block timestamp at height `H+1`. +The following image shows this relationship. +The rest of this document describes the code changes that will make this possible. + +![](./img/pbts-message.png) + +### Saving the timestamp across heights + +Currently, `BFTtime` uses `LastCommit` to construct the block timestamp. +The `LastCommit` is created at height `H-1` and is saved in the state store to be included in the block at height `H`. +`BFTtime` takes the weighted median of the timestamps in `LastCommit.CommitSig` to build the timestamp for height `H`. + +For proposer-based timestamps, the `LastCommit.CommitSig` timestamps will no longer be used to build the timestamps for height `H`. +Instead, the proposal timestamp from height `H-1` will become the block timestamp for height `H`. +To enable this, we will add a `Timestamp` field to the `Commit` struct. +This field will be populated at each height with the proposal timestamp decided on at the previous height. +This timestamp will also be saved with the rest of the commit in the state store [when the commit is finalized](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/state.go#L1611) so that it can be recovered if Tendermint crashes. +Changes to the `CommitSig` and `Commit` struct are detailed below. + +### Changes to `CommitSig` + +The [CommitSig](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/block.go#L604) struct currently contains a timestamp. +This timestamp is the current Unix time known to the validator when it issued a `Precommit` for the block. +This timestamp is no longer used and will be removed in this change. + +`CommitSig` will be updated as follows: + +```diff +type CommitSig struct { + BlockIDFlag BlockIDFlag `json:"block_id_flag"` + ValidatorAddress Address `json:"validator_address"` +-- Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` +} +``` + +### Changes to `Commit` + +The [Commit](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/block.go#L746) struct does not currently contain a timestamp. +The timestamps in the `Commit.CommitSig` entries are currently used to build the block timestamp. +With these timestamps removed, the commit time will instead be stored in the `Commit` struct. + +`Commit` will be updated as follows. + +```diff +type Commit struct { + Height int64 `json:"height"` + Round int32 `json:"round"` +++ Timestamp time.Time `json:"timestamp"` + BlockID BlockID `json:"block_id"` + Signatures []CommitSig `json:"signatures"` +} +``` + +### Changes to `Vote` messages + +`Precommit` and `Prevote` messages use a common [Vote struct](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/vote.go#L50). +This struct currently contains a timestamp. +This timestamp is set using the [voteTime](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/state.go#L2241) function and therefore vote times correspond to the current Unix time known to the validator. +For precommits, this timestamp is used to construct the [CommitSig that is included in the block in the LastCommit](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/types/block.go#L754) field. +For prevotes, this field is unused. +Proposer-based timestamps will use the [RoundState.Proposal](https://github.com/tendermint/tendermint/blob/c3ae6f5b58e07b29c62bfdc5715b6bf8ae5ee951/internal/consensus/types/round_state.go#L76) timestamp to construct the `signedBytes` `CommitSig`. +This timestamp is therefore no longer useful and will be dropped. + +`Vote` will be updated as follows: + +```diff +type Vote struct { + Type tmproto.SignedMsgType `json:"type"` + Height int64 `json:"height"` + Round int32 `json:"round"` + BlockID BlockID `json:"block_id"` // zero if vote is nil. +-- Timestamp time.Time `json:"timestamp"` + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int32 `json:"validator_index"` + Signature []byte `json:"signature"` +} +``` + +### New consensus parameters + +The proposer-based timestamp specification includes multiple new parameters that must be the same among all validators. +These parameters are `PRECISION`, `MSGDELAY`, and `ACCURACY`. + +The `PRECISION` and `MSGDELAY` parameters are used to determine if the proposed timestamp is acceptable. +A validator will only Prevote a proposal if the proposal timestamp is considered `timely`. +A proposal timestamp is considered `timely` if it is within `PRECISION` and `MSGDELAY` of the Unix time known to the validator. +More specifically, a proposal timestamp is `timely` if `validatorLocalTime - PRECISION < proposalTime < validatorLocalTime + PRECISION + MSGDELAY`. + +Because the `PRECISION` and `MSGDELAY` parameters must be the same across all validators, they will be added to the [consensus parameters](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/types/params.proto#L13) as [durations](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration). + +The proposer-based timestamp specification also includes a [new ACCURACY parameter](https://github.com/tendermint/spec/blob/master/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md#pbts-clocksync-external0). +Intuitively, `ACCURACY` represents the difference between the ‘real’ time and the currently known time of correct validators. +The currently known Unix time of any validator is always somewhat different from real time. +`ACCURACY` is the largest such difference between each validator's time and real time taken as an absolute value. +This is not something a computer can determine on its own and must be specified as an estimate by community running a Tendermint-based chain. +It is used in the new algorithm to [calculate a timeout for the propose step](https://github.com/tendermint/spec/blob/master/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md#pbts-alg-startround0). +`ACCURACY` is assumed to be the same across all validators and therefore should be included as a consensus parameter. + +The consensus will be updated to include this `Timestamp` field as follows: + +```diff +type ConsensusParams struct { + Block BlockParams `json:"block"` + Evidence EvidenceParams `json:"evidence"` + Validator ValidatorParams `json:"validator"` + Version VersionParams `json:"version"` +++ Timestamp TimestampParams `json:"timestamp"` +} +``` + +```go +type TimestampParams struct { + Accuracy time.Duration `json:"accuracy"` + Precision time.Duration `json:"precision"` + MsgDelay time.Duration `json:"msg_delay"` +} +``` + +### Changes to `Header` + +The [Header](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/block.go#L338) struct currently contains a timestamp. +This timestamp is set as the `BFTtime` derived from the block's `LastCommit.CommitSig` timestamps. +This timestamp will no longer be derived from the `LastCommit.CommitSig` timestamps and will instead be included directly into the block's `LastCommit`. +This timestamp will therfore be identical in both the `Header` and the `LastCommit`. +To clarify that the timestamp in the header corresponds to the `LastCommit`'s time, we will rename this timestamp field to `last_timestamp`. + +`Header` will be updated as follows: + +```diff +type Header struct { + // basic block info + Version version.Consensus `json:"version"` + ChainID string `json:"chain_id"` + Height int64 `json:"height"` +-- Time time.Time `json:"time"` +++ LastTimestamp time.Time `json:"last_timestamp"` + + // prev block info + LastBlockID BlockID `json:"last_block_id"` + + // hashes of block data + LastCommitHash tmbytes.HexBytes `json:"last_commit_hash"` + DataHash tmbytes.HexBytes `json:"data_hash"` + + // hashes from the app output from the prev block + ValidatorsHash tmbytes.HexBytes `json:"validators_hash"` + NextValidatorsHash tmbytes.HexBytes `json:"next_validators_hash"` + ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` + AppHash tmbytes.HexBytes `json:"app_hash"` + + // root hash of all results from the txs from the previous block + LastResultsHash tmbytes.HexBytes `json:"last_results_hash"` + + // consensus info + EvidenceHash tmbytes.HexBytes `json:"evidence_hash"` + ProposerAddress Address `json:"proposer_address"` +} +``` + +### Changes to the block proposal step + +#### Proposer selects proposal timestamp + +The proposal logic already [sets the Unix time known to the validator](https://github.com/tendermint/tendermint/blob/2abfe20114ee3bb3adfee817589033529a804e4d/types/proposal.go#L44) into the `Proposal` message. +This satisfies the proposer-based timestamp specification and does not need to change. + +#### Proposer selects block timestamp + +The proposal timestamp that was decided in height `H-1` will be stored in the `State` struct's in the `RoundState.LastCommit` field. +The proposer will select this timestamp to use as the block timestamp at height `H`. + +#### Proposer waits + +Block timestamps must be monotonically increasing. +In `BFTTime`, if a validator’s clock was behind, the [validator added 1 millisecond to the previous block’s time and used that in its vote messages](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/state.go#L2246). +A goal of adding proposer-based timestamps is to enforce some degree of clock synchronization, so having a mechanism that completely ignores the Unix time of the validator time no longer works. + +Validator clocks will not be perfectly in sync. +Therefore, the proposer’s current known Unix time may be less than the `LastCommit.Timestamp`. +If the proposer’s current known Unix time is less than the `LastCommit.Timestamp`, the proposer will sleep until its known Unix time exceeds `LastCommit.Timestamp`. + +This change will require amending the [defaultDecideProposal](https://github.com/tendermint/tendermint/blob/822893615564cb20b002dd5cf3b42b8d364cb7d9/internal/consensus/state.go#L1180) method. +This method should now block until the proposer’s time is greater than `LastCommit.Timestamp`. + +#### Changes to the propose step timeout + +Currently, a validator waiting for a proposal will proceed past the propose step if the configured propose timeout is reached and no proposal is seen. +Proposer-based timestamps requires changing this timeout logic. + +The proposer will now wait until its current known Unix time exceeds the `LastCommit.Timestamp` to propose a block. +The validators must now take this and some other factors into account when deciding when to timeout the propose step. +Specifically, the propose step timeout must also take into account potential inaccuracy in the validator’s clock and in the clock of the proposer. +Additionally, there may be a delay communicating the proposal message from the proposer to the other validators. + +Therefore, validators waiting for a proposal must wait until after the `LastCommit.Timestamp` before timing out. +To account for possible inaccuracy in its own clock, inaccuracy in the proposer’s clock, and message delay, validators waiting for a proposal will wait until `LastCommit.Timesatmp + 2*ACCURACY + MSGDELAY`. + The spec defines this as `waitingTime`. + +The [propose step’s timeout is set in enterPropose](https://github.com/tendermint/tendermint/blob/822893615564cb20b002dd5cf3b42b8d364cb7d9/internal/consensus/state.go#L1108) in `state.go`. +`enterPropose` will be changed to calculate waiting time using the new consensus parameters. +The timeout in `enterPropose` will then be set as the maximum of `waitingTime` and the [configured proposal step timeout](https://github.com/tendermint/tendermint/blob/dc7c212c41a360bfe6eb38a6dd8c709bbc39aae7/config/config.go#L1013). + +### Changes to validation rules + +The rules for validating that a proposal is valid will need slight modification to implement proposer-based timestamps. +Specifically, we will change the validation logic to ensure that the proposal timestamp is `timely` and we will modify the way the block timestamp is validated as well. + +#### Proposal timestamp validation + +Adding proposal timestamp validation is a reasonably straightforward change. +The current Unix time known to the proposer is already included in the [Proposal message](https://github.com/tendermint/tendermint/blob/dc7c212c41a360bfe6eb38a6dd8c709bbc39aae7/types/proposal.go#L31). +Once the proposal is received, the complete message is stored in the `RoundState.Proposal` field. +The precommit and prevote validation logic does not currently use this timestamp. +This validation logic will be updated to check that the proposal timestamp is within `PRECISION` of the current Unix time known to the validators. +If the timestamp is not within `PRECISION` of the current Unix time known to the validator, the proposal will not be considered it valid. +The validator will also check that the proposal time is greater than the block timestamp from the previous height. + +If no valid proposal is received by the proposal timeout, the validator will prevote nil. +This is identical to the current logic. + +#### Block timestamp validation + +The [validBlock function](https://github.com/tendermint/tendermint/blob/c3ae6f5b58e07b29c62bfdc5715b6bf8ae5ee951/state/validation.go#L14) currently [validates the proposed block timestamp in three ways](https://github.com/tendermint/tendermint/blob/c3ae6f5b58e07b29c62bfdc5715b6bf8ae5ee951/state/validation.go#L118). +First, the validation logic checks that this timestamp is greater than the previous block’s timestamp. +Additionally, it validates that the block timestamp is correctly calculated as the weighted median of the timestamps in the [block’s LastCommit](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/types/block.go#L48). +Finally, the logic also authenticates the timestamps in the `LastCommit`. +The cryptographic signature in each `CommitSig` is created by signing a hash of fields in the block with the validator’s private key. +One of the items in this `signedBytes` hash is derived from the timestamp in the `CommitSig`. +To authenticate the `CommitSig` timestamp, the validator builds a hash of fields that includes the timestamp and checks this hash against the provided signature. +This takes place in the [VerifyCommit function](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/types/validation.go#L25). + +The logic to validate that the block timestamp is greater than the previous block’s timestamp also works for proposer-based timestamps and will not change. + +`BFTTime` validation is no longer applicable and will be removed. +Validators will no longer check that the block timestamp is a weighted median of `LastCommit` timestamps. +This will mean removing the call to [MedianTime in the validateBlock function](https://github.com/tendermint/tendermint/blob/4db71da68e82d5cb732b235eeb2fd69d62114b45/state/validation.go#L117). +The `MedianTime` function can be completely removed. +The `LastCommit` timestamps may also be removed. + +The `signedBytes` validation logic in `VerifyCommit` will be slightly altered. +The `CommitSig`s in the block’s `LastCommit` will no longer each contain a timestamp. +The validation logic will instead include the `LastCommit.Timestamp` in the hash of fields for generating the `signedBytes`. +The cryptographic signatures included in the `CommitSig`s will then be checked against this `signedBytes` hash to authenticate the timestamp. +Specifically, the `VerifyCommit` function will be updated to use this new timestamp. + +### Changes to the prevote step + +Currently, a validator will prevote a proposal in one of three cases: + +* Case 1: Validator has no locked block and receives a valid proposal. +* Case 2: Validator has a locked block and receives a valid proposal matching its locked block. +* Case 3: Validator has a locked block, sees a valid proposal not matching its locked block but sees +⅔ prevotes for the new proposal’s block. + +The only change we will make to the prevote step is to what a validator considers a valid proposal as detailed above. + +### Changes to the precommit step + +The precommit step will not require much modification. +Its proposal validation rules will change in the same ways that validation will change in the prevote step. + +### Changes to locking a block +When a validator receives a valid proposed block and +2/3 prevotes for that block, it stores the block as its ‘locked block’ in the [RoundState.ValidBlock](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/types/round_state.go#L85) field. +In each subsequent round it will prevote that block. +A validator will only change which block it has locked if it sees +2/3 prevotes for a different block. + +This mechanism will remain largely unchanged. +The only difference is the addition of proposal timestamp validation. +A validator will prevote nil in a round if the proposal message it received is not `timely`. +Prevoting nil in this case will not cause a validator to ‘unlock’ its locked block. +This difference is an incidental result of the changes to prevote validation. +It is included in this design for completeness and to clarify that no additional changes will be made to block locking. + +### Remove voteTime Completely + +[voteTime](https://github.com/tendermint/tendermint/blob/822893615564cb20b002dd5cf3b42b8d364cb7d9/internal/consensus/state.go#L2229) is a mechanism for calculating the next `BFTTime` given both the validator's current known Unix time and the previous block timestamp. +If the previous block timestamp is greater than the validator's current known Unix time, then voteTime returns a value one millisecond greater than the previous block timestamp. +This logic is used in multiple places and is no longer needed for proposer-based timestamps. +It should therefore be removed completely. + +## Future Improvements + +* Implement BLS signature aggregation. +By removing fields from the `Precommit` messages, we are able to aggregate signatures. + +## Consequences + +### Positive + +* `<2/3` of validators can no longer influence block timestamps. +* Block timestamp will have stronger correspondence to real time. +* Improves the reliability of light client block verification. +* Enables BLS signature aggregation. +* Enables evidence handling to use time instead of height for evidence validity. + +### Neutral + +* Alters Tendermint’s liveness properties. +Liveness now requires that all correct validators have synchronized clocks within a bound. +Liveness will now also require that validators’ clocks move forward, which was not required under `BFTTime`. + +### Negative + +* May increase the length of the propose step if there is a large skew between the previous proposer and the current proposer’s local Unix time. +This skew will be bound by the `PRECISION` value, so it is unlikely to be too large. + +* Current chains with block timestamps far in the future will either need to pause consensus until after the erroneous block timestamp or must maintain synchronized but very inaccurate clocks. + +## References + +* [PBTS Spec](https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp) +* [BFTTime spec](https://github.com/tendermint/spec/blob/master/spec/consensus/bft-time.md) diff --git a/docs/architecture/img/pbts-message.png b/docs/architecture/img/pbts-message.png new file mode 100644 index 0000000000000000000000000000000000000000..400f3569021ca719fe06f20e0db17940091751a1 GIT binary patch literal 32028 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 literal 0 HcmV?d00001