From ff4360bc3c53e16cf42f4a3ce69e27528144c706 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:36:47 +0100 Subject: [PATCH] Rename Tendermint to CometBFT in /spec (backport #143) (#185) * Rename Tendermint to CometBFT in /spec (#143) * spec: renaming Tendermint to CometBFT * CometBFT's consensus protocol is called Tendermint, so this use has been preserved. * spec: initial renaming Tendermint to CometBFT * spec: renaming Tendermint to CometBFT in /spec/p2p * spec/rename: links to CometBFT repo in /spec/ * spec/rename: repository links in /spec/light-client * spec/rename: Tendermint to CometBFT in /spec/abci * spec/rename: further renaming of links * spec/rename: Tendermint consensus protocol of CometBFT * spec/rename: Tendermint -> CometBFT Socket Protocol * spec/rename: Tendermint as a consensus algorithm * spec/rename: replacing some Go documentation links * spec/rename: TMHOME -> CMTHOME, go-amino removed * spec/rename: Tendermint -> Cosmos blockchain * Also applies for full node, light node, security and failure model * spec/rename: tags renamed TMBC -> CMBC * spec: renaming Tendermint to CometBFT * CometBFT's consensus protocol is called Tendermint, so this use has been preserved. * spec: initial renaming Tendermint to CometBFT * spec: renaming Tendermint to CometBFT in /spec/p2p * spec/rename: links to CometBFT repo in /spec/ * spec/rename: repository links in /spec/light-client * spec/rename: Tendermint to CometBFT in /spec/abci * spec/rename: further renaming of links * spec/rename: Tendermint consensus protocol of CometBFT * spec/rename: Tendermint -> CometBFT Socket Protocol * spec/rename: Tendermint as a consensus algorithm * spec/rename: replacing some Go documentation links * spec/rename: TMHOME -> CMTHOME, go-amino removed * spec/rename: Tendermint -> Cosmos blockchain * Also applies for full node, light node, security and failure model * spec/rename: tags renamed TMBC -> CMBC * spec/rename: Tendermint -> Cosmos security model * spec/rename: fixing mismatch CometBFT/Tendermint/consensus * spec/rename: Apply suggestions from code review Co-authored-by: Sergio Mena Co-authored-by: Sergio Mena (cherry picked from commit a36697d2276f10a6138215512fedbb0b9204527b) * spec/p2p/v0.34: synchronizing main and v0.34.x branches * spec/rename: aligning branches main and v0.34.x * spec/rename: update branch links main -> v0.34.x * Apply suggestions from code review Co-authored-by: Sergio Mena Co-authored-by: Daniel Co-authored-by: Sergio Mena --- spec/README.md | 20 +- spec/abci/README.md | 2 +- spec/blockchain/readme.md | 2 +- spec/consensus/bft-time.md | 13 +- spec/consensus/consensus-paper/README.md | 4 +- spec/consensus/consensus.md | 14 +- spec/consensus/evidence.md | 6 +- spec/consensus/light-client/README.md | 2 +- spec/consensus/light-client/accountability.md | 2 +- spec/consensus/light-client/detection.md | 2 +- spec/consensus/light-client/verification.md | 2 +- .../proposer-based-timestamp/README.md | 2 +- .../pbts-algorithm_001_draft.md | 3 - .../pbts-sysmodel_001_draft.md | 27 +- .../pbts_001_draft.md | 9 +- .../tla/TendermintPBT_001_draft.tla | 4 +- spec/consensus/proposer-selection.md | 8 +- spec/consensus/readme.md | 9 +- spec/consensus/signing.md | 6 +- spec/consensus/wal.md | 4 +- spec/core/data_structures.md | 130 +++---- spec/core/encoding.md | 20 +- spec/core/genesis.md | 8 +- spec/core/readme.md | 2 +- spec/ivy-proofs/domain_model.ivy | 5 +- spec/light-client/README.md | 20 +- spec/light-client/accountability/README.md | 18 +- spec/light-client/accountability/Synopsis.md | 2 +- .../TendermintAccInv_004_draft.tla | 2 +- .../TendermintAcc_004_draft.tla | 2 +- .../attacks/Blockchain_003_draft.tla | 2 +- .../attacks/Isolation_001_draft.tla | 6 +- .../attacks/LCVerificationApi_003_draft.tla | 2 +- .../attacks/isolate-attackers_001_draft.md | 40 +- .../attacks/isolate-attackers_002_reviewed.md | 48 +-- .../attacks/notes-on-evidence-handling.md | 6 +- .../detection/Blockchain_003_draft.tla | 2 +- .../detection/LCDetector_003_draft.tla | 4 +- .../detection/LCVerificationApi_003_draft.tla | 2 +- spec/light-client/detection/README.md | 8 +- .../detection/detection_001_reviewed.md | 99 +++-- .../detection/detection_003_reviewed.md | 114 +++--- spec/light-client/detection/discussions.md | 2 +- .../light-client/detection/draft-functions.md | 10 +- .../detection/req-ibc-detection.md | 32 +- .../supervisor/supervisor_001_draft.md | 22 +- .../supervisor/supervisor_002_draft.md | 12 +- .../verification/Blockchain_002_draft.tla | 6 +- .../verification/Blockchain_003_draft.tla | 2 +- .../verification/Blockchain_A_1.tla | 6 +- .../LCVerificationApi_003_draft.tla | 2 +- .../verification/Lightclient_003_draft.tla | 2 +- .../verification/Lightclient_A_1.tla | 1 + spec/light-client/verification/README.md | 20 +- .../verification_001_published.md | 170 ++++---- .../verification/verification_002_draft.md | 161 ++++---- spec/p2p/config.md | 2 +- spec/p2p/connection.md | 2 +- spec/p2p/messages/README.md | 2 +- spec/p2p/messages/consensus.md | 6 +- spec/p2p/messages/pex.md | 14 +- spec/p2p/messages/state-sync.md | 5 +- spec/p2p/node.md | 14 +- spec/p2p/peer.md | 14 +- spec/p2p/readme.md | 7 + spec/p2p/v0.34/README.md | 70 ++++ spec/p2p/v0.34/addressbook.md | 368 ++++++++++++++++++ spec/p2p/v0.34/configuration.md | 51 +++ spec/p2p/v0.34/img/p2p_state.png | Bin 0 -> 132059 bytes spec/p2p/v0.34/peer_manager.md | 147 +++++++ spec/p2p/v0.34/pex-protocol.md | 240 ++++++++++++ spec/p2p/v0.34/pex.md | 111 ++++++ spec/p2p/v0.34/switch.md | 238 +++++++++++ spec/p2p/v0.34/transport.md | 222 +++++++++++ spec/p2p/v0.34/types.md | 239 ++++++++++++ spec/rpc/README.md | 10 +- 76 files changed, 2279 insertions(+), 614 deletions(-) create mode 100644 spec/p2p/v0.34/README.md create mode 100644 spec/p2p/v0.34/addressbook.md create mode 100644 spec/p2p/v0.34/configuration.md create mode 100644 spec/p2p/v0.34/img/p2p_state.png create mode 100644 spec/p2p/v0.34/peer_manager.md create mode 100644 spec/p2p/v0.34/pex-protocol.md create mode 100644 spec/p2p/v0.34/pex.md create mode 100644 spec/p2p/v0.34/switch.md create mode 100644 spec/p2p/v0.34/transport.md create mode 100644 spec/p2p/v0.34/types.md diff --git a/spec/README.md b/spec/README.md index e7563175ce..ebb7f210d5 100644 --- a/spec/README.md +++ b/spec/README.md @@ -6,15 +6,15 @@ parent: order: 7 --- -# Tendermint Spec +# CometBFT Spec -This is a markdown specification of the Tendermint blockchain. +This is a markdown specification of CometBFT. It defines the base data structures, how they are validated, and how they are communicated over the network. If you find discrepancies between the spec and the code that do not have an associated issue or pull request on github, -please submit them to our [bug bounty](https://tendermint.com/security)! +please submit them to our [bug bounty](https://cometbft.com/security)! ## Contents @@ -44,7 +44,7 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ### RPC -- [RPC SPEC](./rpc/README.md): Specification of the Tendermint remote procedure call interface. +- [RPC SPEC](./rpc/README.md): Specification of the CometBFT remote procedure call interface. ### Software @@ -55,19 +55,19 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ## Overview -Tendermint provides Byzantine Fault Tolerant State Machine Replication using +CometBFT provides Byzantine Fault Tolerant State Machine Replication using hash-linked batches of transactions. Such transaction batches are called "blocks". -Hence, Tendermint defines a "blockchain". +Hence, CometBFT defines a "blockchain". -Each block in Tendermint has a unique index - its Height. +Each block in CometBFT has a unique index - its Height. Height's in the blockchain are monotonic. Each block is committed by a known set of weighted Validators. Membership and weighting within this validator set may change over time. -Tendermint guarantees the safety and liveness of the blockchain +CometBFT guarantees the safety and liveness of the blockchain so long as less than 1/3 of the total weight of the Validator set is malicious or faulty. -A commit in Tendermint is a set of signed messages from more than 2/3 of +A commit in CometBFT is a set of signed messages from more than 2/3 of the total weight of the current Validator set. Validators take turns proposing blocks and voting on them. Once enough votes are received, the block is considered committed. These votes are included in the _next_ block as proof that the previous block @@ -79,7 +79,7 @@ The application returns results for each of the transactions in the block. The application can also return changes to be made to the validator set, as well as a cryptographic digest of its latest state. -Tendermint is designed to enable efficient verification and authentication +CometBFT is designed to enable efficient verification and authentication of the latest state of the blockchain. To achieve this, it embeds cryptographic commitments to certain information in the block "header". This information includes the contents of the block (eg. the transactions), diff --git a/spec/abci/README.md b/spec/abci/README.md index 6c71393ce0..17a895aaa0 100644 --- a/spec/abci/README.md +++ b/spec/abci/README.md @@ -14,7 +14,7 @@ _methods_, each with a corresponding `Request` and `Response`message type. To perform state-machine replication, Tendermint calls the ABCI methods on the ABCI application by sending the `Request*` messages and receiving the `Response*` messages in return. -All ABCI messages and methods are defined in [protocol buffers](https://github.com/tendermint/tendermint/blob/v0.34.x/proto/abci/types.proto). +All ABCI messages and methods are defined in [protocol buffers](https://github.com/cometbft/cometbft/blob/v0.34.x/proto/abci/types.proto). This allows Tendermint to run with applications written in many programming languages. This specification is split as follows: diff --git a/spec/blockchain/readme.md b/spec/blockchain/readme.md index 10ad466907..3f5f61a4b4 100644 --- a/spec/blockchain/readme.md +++ b/spec/blockchain/readme.md @@ -7,7 +7,7 @@ parent: # Blockchain -This section describes the core types and functionality of the Tendermint protocol implementation. +This section describes the core types and functionality of the CometBFT protocol implementation. [Core Data Structures](../core/data_structures.md) [Encoding](../core/encoding.md) diff --git a/spec/consensus/bft-time.md b/spec/consensus/bft-time.md index cec3b91ab9..9312d9b390 100644 --- a/spec/consensus/bft-time.md +++ b/spec/consensus/bft-time.md @@ -3,8 +3,8 @@ order: 2 --- # BFT Time -Tendermint provides a deterministic, Byzantine fault-tolerant, source of time. -Time in Tendermint is defined with the Time field of the block header. +CometBFT provides a deterministic, Byzantine fault-tolerant, source of time. +Time in CometBFT is defined with the Time field of the block header. It satisfies the following properties: @@ -15,12 +15,13 @@ valid values for the Time field of the block header is defined only by Precommit messages (from the LastCommit field) sent by correct processes, i.e., a faulty process cannot arbitrarily increase the Time value. -In the context of Tendermint, time is of type int64 and denotes UNIX time in milliseconds, i.e., -corresponds to the number of milliseconds since January 1, 1970. Before defining rules that need to be enforced by the -Tendermint consensus protocol, so the properties above holds, we introduce the following definition: +In the context of CometBFT, time is of type int64 and denotes UNIX time in milliseconds, i.e., +corresponds to the number of milliseconds since January 1, 1970. +Before defining rules that need to be enforced by Tendermint, the consensus algorithm adopted in CometBFT, +so the properties above holds, we introduce the following definition: - median of a Commit is equal to the median of `Vote.Time` fields of the `Vote` messages, -where the value of `Vote.Time` is counted number of times proportional to the process voting power. As in Tendermint +where the value of `Vote.Time` is counted number of times proportional to the process voting power. As the voting power is not uniform (one process one vote), a vote message is actually an aggregator of the same votes whose number is equal to the voting power of the process that has casted the corresponding votes message. diff --git a/spec/consensus/consensus-paper/README.md b/spec/consensus/consensus-paper/README.md index 33e3958061..3c328ddd06 100644 --- a/spec/consensus/consensus-paper/README.md +++ b/spec/consensus/consensus-paper/README.md @@ -1,7 +1,7 @@ -# Tendermint-spec +# Consensus Paper The repository contains the specification (and the proofs) of the Tendermint -consensus protocol. +consensus protocol, adopted in CometBFT. ## How to install Latex on Mac OS diff --git a/spec/consensus/consensus.md b/spec/consensus/consensus.md index e4b7bdecc5..20f2254a48 100644 --- a/spec/consensus/consensus.md +++ b/spec/consensus/consensus.md @@ -14,10 +14,8 @@ order: 1 just "step"). - A node is said to be _at_ a given height, round, and step, or at `(H,R,S)`, or at `(H,R)` in short to omit the step. -- To _prevote_ or _precommit_ something means to broadcast a [prevote - vote](https://godoc.org/github.com/tendermint/tendermint/types#Vote) - or [first precommit - vote](https://godoc.org/github.com/tendermint/tendermint/types#FirstPrecommit) +- To _prevote_ or _precommit_ something means to broadcast a prevote + or precommit [vote](https://github.com/cometbft/cometbft/blob/af3bc47df982e271d4d340a3c5e0d773e440466d/types/vote.go#L50) for something. - A vote _at_ `(H,R)` is a vote signed with the bytes for `H` and `R` included in its [sign-bytes](../core/data_structures.md#vote). @@ -108,7 +106,7 @@ example, - Nodes gossip prevotes for the proposed PoLC (proof-of-lock-change) round if one is proposed. - Nodes gossip to nodes lagging in blockchain height with block - [commits](https://godoc.org/github.com/tendermint/tendermint/types#Commit) + [commits](https://github.com/cometbft/cometbft/blob/af3bc47df982e271d4d340a3c5e0d773e440466d/types/block.go#L738) for older blocks. - Nodes opportunistically gossip `ReceivedVote` messages to hint peers what votes it already has. @@ -123,7 +121,7 @@ A proposal is signed and published by the designated proposer at each round. The proposer is chosen by a deterministic and non-choking round robin selection algorithm that selects proposers in proportion to their voting power (see -[implementation](https://github.com/tendermint/tendermint/blob/v0.34.x/types/validator_set.go)). +[implementation](https://github.com/cometbft/cometbft/blob/v0.34.x/types/validator_set.go#L51)). A proposal at `(H,R)` is composed of a block and an optional latest `PoLC-Round < R` which is included iff the proposer knows of one. This @@ -285,7 +283,7 @@ tells peers that this node has (or does not have) a +2/3 majority for B majority. Peers can react by responding with appropriate votes. We will implement such an algorithm for the next iteration of the -Tendermint consensus protocol. +consensus protocol. Other potential improvements include adding more data in votes such as the last known PoLC round that caused a lock change, and the last voted @@ -295,7 +293,7 @@ may make JSet verification/gossip logic easier to implement. ### Censorship Attacks Due to the definition of a block -[commit](https://github.com/tendermint/tendermint/blob/v0.34.x/docs/tendermint-core/validators.md), any 1/3+ coalition of +[commit](https://github.com/cometbft/cometbft/blob/v0.34.x/docs/tendermint-core/validators.md), any 1/3+ coalition of validators can halt the blockchain by not broadcasting their votes. Such a coalition can also censor particular transactions by rejecting blocks that include these transactions, though this would result in a diff --git a/spec/consensus/evidence.md b/spec/consensus/evidence.md index 84d51a01b5..a61d7f50c2 100644 --- a/spec/consensus/evidence.md +++ b/spec/consensus/evidence.md @@ -1,11 +1,11 @@ # Evidence -Evidence is an important component of Tendermint's security model. Whilst the core +Evidence is an important component of CometBFT's security model. Whilst the core consensus protocol provides correctness gaurantees for state machine replication that can tolerate less than 1/3 failures, the evidence system looks to detect and gossip byzantine faults whose combined power is greater than or equal to 1/3. It is worth noting that the evidence system is designed purely to detect possible attacks, gossip them, -commit them on chain and inform the application running on top of Tendermint. +commit them on chain and inform the application running on top of CometBFT. Evidence in itself does not punish "bad actors", this is left to the discretion of the application. A common form of punishment is slashing where the validators that were caught violating the protocol have all or a portion of their voting @@ -16,7 +16,7 @@ security on a "best effort" basis. This document walks through the various forms of evidence, how they are detected, gossiped, verified and committed. -> NOTE: Evidence here is internal to tendermint and should not be confused with +> NOTE: Evidence here is internal to CometBFT and should not be confused with > application evidence ## Detection diff --git a/spec/consensus/light-client/README.md b/spec/consensus/light-client/README.md index 44b9e0c762..7d342a776d 100644 --- a/spec/consensus/light-client/README.md +++ b/spec/consensus/light-client/README.md @@ -4,6 +4,6 @@ parent: title: Light Client order: false --- -# Tendermint Light Client Protocol +# Light Client Protocol Deprecated, please see [light-client](../../light-client/README.md). diff --git a/spec/consensus/light-client/accountability.md b/spec/consensus/light-client/accountability.md index 5cf46b0b43..3d7778bc29 100644 --- a/spec/consensus/light-client/accountability.md +++ b/spec/consensus/light-client/accountability.md @@ -1,3 +1,3 @@ # Fork accountability -Deprecated, please see [light-client/accountability](../../light-client/accountability.md). +Deprecated, please see [light-client/accountability](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/accountability). diff --git a/spec/consensus/light-client/detection.md b/spec/consensus/light-client/detection.md index 5c87562ba5..ce391697eb 100644 --- a/spec/consensus/light-client/detection.md +++ b/spec/consensus/light-client/detection.md @@ -1,3 +1,3 @@ # Detection -Deprecated, please see [light-client/detection](../../light-client/detection.md). +Deprecated, please see [light-client/detection](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection). diff --git a/spec/consensus/light-client/verification.md b/spec/consensus/light-client/verification.md index 1f0104a402..9c5aa59017 100644 --- a/spec/consensus/light-client/verification.md +++ b/spec/consensus/light-client/verification.md @@ -1,3 +1,3 @@ # Core Verification -Deprecated, please see [light-client/accountability](../../light-client/verification.md). +Deprecated, please see [light-client/verification](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification). diff --git a/spec/consensus/proposer-based-timestamp/README.md b/spec/consensus/proposer-based-timestamp/README.md index 82421d99da..2972d8765b 100644 --- a/spec/consensus/proposer-based-timestamp/README.md +++ b/spec/consensus/proposer-based-timestamp/README.md @@ -1,6 +1,6 @@ # Proposer-Based Timestamps -This section describes a version of the Tendermint consensus protocol, +This section describes a version of the Tendermint consensus algorithm, adopted in CometBFT, which uses proposer-based timestamps. ## Contents diff --git a/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md b/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md index 9791ff86bc..b42b3ab2f1 100644 --- a/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md +++ b/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md @@ -156,8 +156,5 @@ Back to [main document][main]. [arXiv]: https://arxiv.org/abs/1807.04938 -[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md -[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md -[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md diff --git a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md index 2eb32bc7df..1b0f9a3b70 100644 --- a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md +++ b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md @@ -18,7 +18,8 @@ There exists a system parameter `PRECISION` such that for any two correct valida ### Message Delays -We do not want to interfere with the Tendermint timing assumptions. We will postulate a timing restriction, which, if satisfied, ensures that liveness is preserved. +We do not want to interfere with the timing assumptions of Tendermint consensus algorithm. +We will postulate a timing restriction, which, if satisfied, ensures that liveness is preserved. In general the local clock may drift from the global time. (It may progress faster, e.g., one second of clock time might take 1.005 seconds of real-time). As a result the local clock and the global clock may be measured in different time units. Usually, the message delay is measured in global clock time units. To estimate the correct local timeout precisely, we would need to estimate the clock time duration of a message delay taking into account the clock drift. For simplicity we ignore this, and directly postulate the message delay assumption in terms of local time. @@ -36,7 +37,7 @@ The message end-to-end delay between a correct proposer and a correct validator ## Problem Statement -In this section we define the properties of Tendermint consensus (cf. the [arXiv paper][arXiv]) in this new system model. +In this section we define the properties of Tendermint consensus algorithm (cf. the [arXiv paper][arXiv]) in this new system model. #### **[PBTS-PROPOSE.0]** @@ -52,7 +53,7 @@ A proposer proposes a pair `(v,t)` of consensus value `v` and time `t`. [Time-Validity] If a correct validator decides on `t` then `t` is "OK" (we will formalize this below), even if up to `2f` validators are faulty. -However, the properties of Tendermint consensus are of more interest with respect to the blocks, that is, what is written into a block and when. We therefore, in the following, will give the safety and liveness properties from this block-centric viewpoint. +However, the properties of Tendermint consensus algorithm are of more interest with respect to the blocks, that is, what is written into a block and when. We therefore, in the following, will give the safety and liveness properties from this block-centric viewpoint. For this, observe that the time `t` decided at consensus height `k` will be written in the block of height `k+1`, and will be supported by `2f + 1` `PRECOMMIT` messages of the same consensus round `r`. The time written in the block, we will denote by `b.time` (to distinguish it from the term `bfttime` used for median-based time). For this, it is important to have the following consensus algorithm property: #### **[PBTS-INV-TIME-AGR.0]** @@ -69,11 +70,11 @@ However, as most consensus instances terminate within one round on the Cosmos hu -Finally, observe that the agreement ([Agreement] and [Time-Agreement]) properties are based on the Tendermint security model [TMBC-FM-2THIRDS.0] of more than 2/3 correct validators, while [Time-Validity] is based on more than 1/3 correct validators. +Finally, observe that the agreement ([Agreement] and [Time-Agreement]) properties are based on the Cosmos security model [CMBC-FM-2THIRDS.0][CMBC-FM-2THIRDS-link] of more than 2/3 correct validators, while [Time-Validity] is based on more than 1/3 correct validators. ### SAFETY -Here we will provide specifications that relate local time to block time. However, since we do not assume (by now) that local time is linked to real-time, these specifications also do not provide a relation between block time and real-time. Such properties are given [later](#REAL-TIME-SAFETY). +Here we will provide specifications that relate local time to block time. However, since we do not assume (by now) that local time is linked to real-time, these specifications also do not provide a relation between block time and real-time. Such properties are given [later](#real-time-safety). For a correct validator `V`, let `beginConsensus(V,k)` be the local time when it sets its height to `k`, and let `endConsensus(V,k)` be the time when it sets its height to `k + 1`. @@ -110,7 +111,7 @@ then the time `b.time` in the block `b` that is signed by `c` satisfies If the proposer of round 1 is correct, and -- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and +- [CMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and - [PBTS-MSG-FAIR.0], and - [PBTS-CLOCK-PRECISION.0], and - [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?) @@ -121,7 +122,7 @@ then eventually (within bounded time) every correct validator decides in round 1 If the proposer of round 1 is correct, and -- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and +- [CMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and - [PBTS-MSG-FAIR.0], and - [PBTS-CLOCK-PRECISION.0], and - [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?) @@ -135,7 +136,7 @@ then `beginConsensus_k <= b.time <= last-beginConsensus_k`. If -- [TMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and +- [CMBC-FM-2THIRDS.0] holds for a block of height `k - 1`, and - [PBTS-MSG-FAIR.0], - [PBTS-CLOCK.0], and - [PBTS-CLOCK-GROW.0] (**TODO:** is that enough?) @@ -187,12 +188,4 @@ Back to [main document][main]. [arXiv]: https://arxiv.org/abs/1807.04938 -[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md - -[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md - -[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md - -[algorithm]: ./pbts-algorithm_001_draft.md - -[sysmodel]: ./pbts-sysmodel_001_draft.md +[CMBC-FM-2THIRDS-link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-fm-2thirds1 diff --git a/spec/consensus/proposer-based-timestamp/pbts_001_draft.md b/spec/consensus/proposer-based-timestamp/pbts_001_draft.md index a4d876b28c..bcb01d7364 100644 --- a/spec/consensus/proposer-based-timestamp/pbts_001_draft.md +++ b/spec/consensus/proposer-based-timestamp/pbts_001_draft.md @@ -4,7 +4,7 @@ ### Description -In Tendermint consensus, the first version of how time is computed and stored in a block works as follows: +In CometBFT, the first version of how time is computed and stored in a block works as follows: - validators send their current local time as part of `precommit` messages - upon collecting the `precommit` messages that the proposer uses to build a commit to be put in the next block, the proposer computes the `time` of the next block as the median (weighted over voting power) of the times in the `precommit` messages. @@ -255,16 +255,15 @@ This specification describes the changes needed to be done to the Tendermint con [arXiv]: https://arxiv.org/abs/1807.04938 -[tlatender]: https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md +[tlatender]: ../../light-client/accountability/README.md -[bfttime]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/spec/consensus/bft-time.md +[bfttime]: ../bft-time.md -[lcspec]: https://github.com/tendermint/spec/blob/439a5bcacb5ef6ef1118566d7b0cd68fff3553d4/rust-spec/lightclient/README.md +[lcspec]: ../../light-client/README.md [algorithm]: ./pbts-algorithm_001_draft.md [sysmodel]: ./pbts-sysmodel_001_draft.md -[main]: ./pbts_001_draft.md [proposertla]: ./tla/TendermintPBT_001_draft.tla diff --git a/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla b/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla index c84b024e75..585d0d8e8e 100644 --- a/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla +++ b/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla @@ -1,9 +1,9 @@ -------------------- MODULE TendermintPBT_001_draft --------------------------- (* - A TLA+ specification of a simplified Tendermint consensus, with added clocks + A TLA+ specification of a simplified Tendermint consensus algorithm, with added clocks and proposer-based timestamps. This TLA+ specification extends and modifies the Tendermint TLA+ specification for fork accountability: - https://github.com/tendermint/tendermint/blob/v0.34.x/spec/light-client/accountability/TendermintAcc_004_draft.tla + https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/accountability/TendermintAcc_004_draft.tla * Version 1. A preliminary specification. diff --git a/spec/consensus/proposer-selection.md b/spec/consensus/proposer-selection.md index 3cea3d5cde..885682f2fe 100644 --- a/spec/consensus/proposer-selection.md +++ b/spec/consensus/proposer-selection.md @@ -4,8 +4,8 @@ order: 3 # Proposer Selection Procedure -This document specifies the Proposer Selection Procedure that is used in Tendermint to choose a round proposer. -As Tendermint is “leader-based protocol”, the proposer selection is critical for its correct functioning. +This document specifies the Proposer Selection Procedure that is used in Tendermint, the consensus algorithm adopted in CometBFT, to choose a round proposer. +As Tendermint is “leader-based consensus protocol”, the proposer selection is critical for its correct functioning. At a given block height, the proposer selection algorithm runs with the same validator set at each round . Between heights, an updated validator set may be specified by the application as part of the ABCIResponses' EndBlock. @@ -200,7 +200,7 @@ In the next run, p3 will still be ahead in the queue, elected as proposer and mo | Priority Run | -13 | -9 | -5 | -2 | -1 | 0 | 1 | 2 | 5 | 6 | 7 | Alg step | |----------------|-----|----|----|----|----|---|---|----|----|----|----|-----------------------| | last run | | | | p2 | | | | p1 | | | | __add p3__ | -| | p3 | | | p2 | | | | p1 | | | | A(p3) = -4 | +| | p3 | | | p2 | | | | p1 | | | | A(p3) = -13 | | next run | | p3 | | | | | | p2 | | p1 | | A(i) -= avg, avg = -4 | | | | | | | p3 | | | | p2 | | p1 | A(i)+=VP(i) | | | | | p1 | | p3 | | | | p2 | | | A(p1)-=P | @@ -215,7 +215,7 @@ Validator | p1 | p2 | Comment ----------|------|------|------------------ VP | 80k | 10 | A | 0 | -90k | __added p2__ -A | -45k | 45k | __run selection__ +A | 45k | -45k | __run selection__ Then execute the following steps: diff --git a/spec/consensus/readme.md b/spec/consensus/readme.md index aa79ba1929..9dbee537e1 100644 --- a/spec/consensus/readme.md +++ b/spec/consensus/readme.md @@ -7,14 +7,14 @@ parent: # Consensus -Specification of the Tendermint consensus protocol. +Specification of the consensus protocol implemented in CometBFT. ## Contents - [Consensus Paper](./consensus-paper) - Latex paper on [arxiv](https://arxiv.org/abs/1807.04938) describing the - core Tendermint consensus state machine with proofs of safety and termination. -- [BFT Time](./bft-time.md) - How the timestamp in a Tendermint + Tendermint consensus algorithm, adopted in CometBFT, with proofs of safety and termination. +- [BFT Time](./bft-time.md) - How the timestamp in a CometBFT block header is computed in a Byzantine Fault Tolerant manner - [Creating Proposal](./creating-proposal.md) - How a proposer creates a block proposal for consensus @@ -25,8 +25,5 @@ Specification of the Tendermint consensus protocol. - [Write Ahead Log](./wal.md) - Write ahead log used by the consensus state machine to recover from crashes. -The protocol used to gossip consensus messages between peers, which is critical -for liveness, is described in the [reactors section](../reactors/consensus/consensus.md). - There is also a [stale markdown description](consensus.md) of the consensus state machine (TODO update this). diff --git a/spec/consensus/signing.md b/spec/consensus/signing.md index 907a5a01af..4954e3cf84 100644 --- a/spec/consensus/signing.md +++ b/spec/consensus/signing.md @@ -147,8 +147,8 @@ these basic validation rules. ## Double Signing Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating". -Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished -by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future. +CometBFT has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished +by the application. Note CometBFT does not currently handle evidence of conflciting proposals, though it may in the future. ### State @@ -212,7 +212,7 @@ sign another vote with the same type for the same height and round where ### Other Rules -According to the rules of Tendermint consensus, once a validator precommits for +According to the rules of Tendermint consensus algorithm, adopted in CometBFT, once a validator precommits for a block, they become "locked" on that block, which means they can't prevote for another block unless they see sufficient justification (ie. a polka from a higher round). For more details, see the [consensus diff --git a/spec/consensus/wal.md b/spec/consensus/wal.md index 597d1b79e1..b02ce7d70c 100644 --- a/spec/consensus/wal.md +++ b/spec/consensus/wal.md @@ -7,7 +7,7 @@ It also issues fsync syscall through node (to prevent double signing). Under the hood, it uses -[autofile.Group](https://godoc.org/github.com/tendermint/tmlibs/autofile#Group), +[autofile.Group](https://github.com/cometbft/cometbft/blob/af3bc47df982e271d4d340a3c5e0d773e440466d/libs/autofile/group.go#L54), which rotates files when those get too big (> 10MB). The total maximum size is 1GB. We only need the latest block and the block before it, @@ -28,5 +28,5 @@ WAL. Then it will go to precommit, and that time it will work because the private validator contains the `LastSignBytes` and then we’ll replay the precommit from the WAL. -Make sure to read about [WAL corruption](https://github.com/tendermint/tendermint/blob/v0.34.x/docs/tendermint-core/running-in-production.md#wal-corruption) +Make sure to read about [WAL corruption](https://github.com/cometbft/cometbft/blob/v0.34.x/docs/tendermint-core/running-in-production.md#wal-corruption) and recovery strategies. diff --git a/spec/core/data_structures.md b/spec/core/data_structures.md index d89681dcd4..ccdc260e24 100644 --- a/spec/core/data_structures.md +++ b/spec/core/data_structures.md @@ -1,42 +1,42 @@ # Data Structures -Here we describe the data structures in the Tendermint blockchain and the rules for validating them. +Here we describe the data structures in the CometBFT blockchain and the rules for validating them. -The Tendermint blockchains consists of a short list of data types: +The CometBFT blockchain consists of a short list of data types: - [Data Structures](#data-structures) - - [Block](#block) - - [Execution](#execution) - - [Header](#header) - - [Version](#version) - - [BlockID](#blockid) - - [PartSetHeader](#partsetheader) - - [Part](#part) - - [Time](#time) - - [Data](#data) - - [Commit](#commit) - - [CommitSig](#commitsig) - - [BlockIDFlag](#blockidflag) - - [Vote](#vote) - - [CanonicalVote](#canonicalvote) - - [Proposal](#proposal) - - [SignedMsgType](#signedmsgtype) - - [Signature](#signature) - - [EvidenceList](#evidencelist) - - [Evidence](#evidence) - - [DuplicateVoteEvidence](#duplicatevoteevidence) - - [LightClientAttackEvidence](#lightclientattackevidence) - - [LightBlock](#lightblock) - - [SignedHeader](#signedheader) - - [ValidatorSet](#validatorset) - - [Validator](#validator) - - [Address](#address) - - [ConsensusParams](#consensusparams) - - [BlockParams](#blockparams) - - [EvidenceParams](#evidenceparams) - - [ValidatorParams](#validatorparams) - - [VersionParams](#versionparams) - - [Proof](#proof) + - [Block](#block) + - [Execution](#execution) + - [Header](#header) + - [Version](#version) + - [BlockID](#blockid) + - [PartSetHeader](#partsetheader) + - [Part](#part) + - [Time](#time) + - [Data](#data) + - [Commit](#commit) + - [CommitSig](#commitsig) + - [BlockIDFlag](#blockidflag) + - [Vote](#vote) + - [CanonicalVote](#canonicalvote) + - [Proposal](#proposal) + - [SignedMsgType](#signedmsgtype) + - [Signature](#signature) + - [EvidenceList](#evidencelist) + - [Evidence](#evidence) + - [DuplicateVoteEvidence](#duplicatevoteevidence) + - [LightClientAttackEvidence](#lightclientattackevidence) + - [LightBlock](#lightblock) + - [SignedHeader](#signedheader) + - [ValidatorSet](#validatorset) + - [Validator](#validator) + - [Address](#address) + - [ConsensusParams](#consensusparams) + - [BlockParams](#blockparams) + - [EvidenceParams](#evidenceparams) + - [ValidatorParams](#validatorparams) + - [VersionParams](#versionparams) + - [Proof](#proof) ## Block @@ -47,8 +47,8 @@ and a list of evidence of malfeasance (ie. signing conflicting votes). | Name | Type | Description | Validation | |--------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| | Header | [Header](#header) | Header corresponding to the block. This field contains information used throughout consensus and other areas of the protocol. To find out what it contains, visit [header](#header) | Must adhere to the validation rules of [header](#header) | -| Data | [Data](#data) | Data contains a list of transactions. The contents of the transaction is unknown to Tendermint. | This field can be empty or populated, but no validation is performed. Applications can perform validation on individual transactions prior to block creation using [checkTx](../abci/abci.md#checktx). -| Evidence | [EvidenceList](#evidence_list) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceList](#evidence_list) apply | +| Data | [Data](#data) | Data contains a list of transactions. The contents of the transaction is unknown to CometBFT. | This field can be empty or populated, but no validation is performed. Applications can perform validation on individual transactions prior to block creation using [checkTx](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/abci/abci%2B%2B_methods.md#checktx). +| Evidence | [EvidenceList](#evidencelist) | Evidence contains a list of infractions committed by validators. | Can be empty, but when populated the validations rules from [evidenceList](#evidencelist) apply | | LastCommit | [Commit](#commit) | `LastCommit` includes one vote for every validator. All votes must either be for the previous block, nil or absent. If a vote is for the previous block it must have a valid signature from the corresponding validator. The sum of the voting power of the validators that voted must be greater than 2/3 of the total voting power of the complete validator set. The number of votes in a commit is limited to 10000 (see `types.MaxVotesCount`). | Must be empty for the initial height and must adhere to the validation rules of [commit](#commit). | ## Execution @@ -128,9 +128,9 @@ the data in the current block, the previous block, and the results returned by t | ValidatorHash | slice of bytes (`[]byte`) | MerkleRoot of the current validator set. The validators are first sorted by voting power (descending), then by address (ascending) prior to computing the MerkleRoot. | Must be of length 32 | | NextValidatorHash | slice of bytes (`[]byte`) | MerkleRoot of the next validator set. The validators are first sorted by voting power (descending), then by address (ascending) prior to computing the MerkleRoot. | Must be of length 32 | | ConsensusHash | slice of bytes (`[]byte`) | Hash of the protobuf encoded consensus parameters. | Must be of length 32 | -| AppHash | slice of bytes (`[]byte`) | Arbitrary byte array returned by the application after executing and commiting the previous block. It serves as the basis for validating any merkle proofs that comes from the ABCI application and represents the state of the actual application rather than the state of the blockchain itself. The first block's `block.Header.AppHash` is given by `ResponseInitChain.app_hash`. | This hash is determined by the application, Tendermint can not perform validation on it. | +| AppHash | slice of bytes (`[]byte`) | Arbitrary byte array returned by the application after executing and commiting the previous block. It serves as the basis for validating any merkle proofs that comes from the ABCI application and represents the state of the actual application rather than the state of the blockchain itself. The first block's `block.Header.AppHash` is given by `ResponseInitChain.app_hash`. | This hash is determined by the application, CometBFT can not perform validation on it. | | LastResultHash | slice of bytes (`[]byte`) | `LastResultsHash` is the root hash of a Merkle tree built from `ResponseDeliverTx` responses (`Log`,`Info`, `Codespace` and `Events` fields are ignored). | Must be of length 32. The first block has `block.Header.ResultsHash == MerkleRoot(nil)`, i.e. the hash of an empty input, for RFC-6962 conformance. | -| EvidenceHash | slice of bytes (`[]byte`) | MerkleRoot of the evidence of Byzantine behaviour included in this block. | Must be of length 32 | +| EvidenceHash | slice of bytes (`[]byte`) | MerkleRoot of the evidence of Byzantine behavior included in this block. | Must be of length 32 | | ProposerAddress | slice of bytes (`[]byte`) | Address of the original proposer of the block. Validator must be in the current validatorSet. | Must be of length 20 | ## Version @@ -142,7 +142,7 @@ versioning that this can refer to) | Name | type | Description | Validation | |-------|--------|-----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| | Block | uint64 | This number represents the version of the block protocol and must be the same throughout an operational network | Must be equal to protocol version being used in a network (`block.Version.Block == state.Version.Consensus.Block`) | -| App | uint64 | App version is decided on by the application. Read [here](../abci/abci.md#info) | `block.Version.App == state.Version.Consensus.App` | +| App | uint64 | App version is decided on by the application. Read [here](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/abci/abci++_app_requirements.md) | `block.Version.App == state.Version.Consensus.App` | ## BlockID @@ -151,7 +151,7 @@ The `BlockID` contains two distinct Merkle roots of the block. The `BlockID` inc | Name | Type | Description | Validation | |---------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------| | Hash | slice of bytes (`[]byte`) | MerkleRoot of all the fields in the header (ie. `MerkleRoot(header)`. | hash must be of length 32 | -| PartSetHeader | [PartSetHeader](#PartSetHeader) | Used for secure gossiping of the block during consensus, is the MerkleRoot of the complete serialized block cut into parts (ie. `MerkleRoot(MakeParts(block))`). | Must adhere to the validation rules of [PartSetHeader](#PartSetHeader) | +| PartSetHeader | [PartSetHeader](#partsetheader) | Used for secure gossiping of the block during consensus, is the MerkleRoot of the complete serialized block cut into parts (ie. `MerkleRoot(MakeParts(block))`). | Must adhere to the validation rules of [PartSetHeader](#partsetheader) | See [MerkleRoot](./encoding.md#MerkleRoot) for details. @@ -164,7 +164,7 @@ See [MerkleRoot](./encoding.md#MerkleRoot) for details. ## Part -Part defines a part of a block. In Tendermint blocks are broken into `parts` for gossip. +Part defines a part of a block. In CometBFT blocks are broken into `parts` for gossip. | Name | Type | Description | Validation | |-------|-----------------|-----------------------------------|----------------------| @@ -174,7 +174,7 @@ Part defines a part of a block. In Tendermint blocks are broken into `parts` for ## Time -Tendermint uses the [Google.Protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) +CometBFT uses the [Google.Protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) format, which uses two integers, one 64 bit integer for Seconds and a 32 bit integer for Nanoseconds. ## Data @@ -183,7 +183,7 @@ Data is just a wrapper for a list of transactions, where transactions are arbitr | Name | Type | Description | Validation | |------|----------------------------|------------------------|-----------------------------------------------------------------------------| -| Txs | Matrix of bytes ([][]byte) | Slice of transactions. | Validation does not occur on this field, this data is unknown to Tendermint | +| Txs | Matrix of bytes ([][]byte) | Slice of transactions. | Validation does not occur on this field, this data is unknown to CometBFT | ## Commit @@ -202,15 +202,15 @@ Commit is a simple wrapper for a list of signatures, with one for each validator a particular `BlockID` or was absent. It's a part of the `Commit` and can be used to reconstruct the vote set given the validator set. -| Name | Type | Description | Validation | -|------------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| -| BlockIDFlag | [BlockIDFlag](#blockidflag) | Represents the validators participation in consensus: Either voted for the block that received the majority, voted for another block, voted nil or did not vote | Must be one of the fields in the [BlockIDFlag](#blockidflag) enum | -| ValidatorAddress | [Address](#address) | Address of the validator | Must be of length 20 | -| Timestamp | [Time](#time) | This field will vary from `CommitSig` to `CommitSig`. It represents the timestamp of the validator. | [Time](#time) | -| Signature | [Signature](#signature) | Signature corresponding to the validators participation in consensus. | The length of the signature must be > 0 and < than 64 | +| Name | Type | Description | Validation | +|------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| BlockIDFlag | [BlockIDFlag](#blockidflag) | Represents the validators participation in consensus: its vote was not received, voted for the block that received the majority, or voted for nil | Must be one of the fields in the [BlockIDFlag](#blockidflag) enum | +| ValidatorAddress | [Address](#address) | Address of the validator | Must be of length 20 | +| Timestamp | [Time](#time) | This field will vary from `CommitSig` to `CommitSig`. It represents the timestamp of the validator. | [Time](#time) | +| Signature | [Signature](#signature) | Signature corresponding to the validators participation in consensus. | The length of the signature must be > 0 and < than 64 | NOTE: `ValidatorAddress` and `Timestamp` fields may be removed in the future -(see [ADR-25](https://github.com/tendermint/tendermint/blob/main/docs/architecture/adr-025-commit.md)). +(see [ADR-25](https://github.com/cometbft/cometbft/blob/v0.34.x/docs/architecture/adr-025-commit.md)). ## BlockIDFlag @@ -218,10 +218,10 @@ BlockIDFlag represents which BlockID the [signature](#commitsig) is for. ```go enum BlockIDFlag { - BLOCK_ID_FLAG_UNKNOWN = 0; - BLOCK_ID_FLAG_ABSENT = 1; // signatures for other blocks are also considered absent - BLOCK_ID_FLAG_COMMIT = 2; - BLOCK_ID_FLAG_NIL = 3; + BLOCK_ID_FLAG_UNKNOWN = 0; // indicates an error condition + BLOCK_ID_FLAG_ABSENT = 1; // the vote was not received + BLOCK_ID_FLAG_COMMIT = 2; // voted for the block that received the majority + BLOCK_ID_FLAG_NIL = 3; // voted for nil } ``` @@ -236,7 +236,7 @@ The vote includes information about the validator signing it. When stored in the | Height | uint64 | Height for which this vote was created for | Must be > 0 | | Round | int32 | Round that the commit corresponds to. | Must be > 0 | | BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | [BlockID](#blockid) | -| Timestamp | [Time](#Time) | Timestamp represents the time at which a validator signed. | [Time](#time) | +| Timestamp | [Time](#time) | Timestamp represents the time at which a validator signed. | [Time](#time) | | ValidatorAddress | slice of bytes (`[]byte`) | Address of the validator | Length must be equal to 20 | | ValidatorIndex | int32 | Index at a specific block height that corresponds to the Index of the validator in the set. | must be > 0 | | Signature | slice of bytes (`[]byte`) | Signature by the validator if they participated in consensus for the associated bock. | Length of signature must be > 0 and < 64 | @@ -291,7 +291,7 @@ is locked in POLRound. The message is signed by the validator private key. | Round | int32 | Round that the commit corresponds to. | Must be > 0 | | POLRound | int64 | Proof of lock | Must be > 0 | | BlockID | [BlockID](#blockid) | The blockID of the corresponding block. | [BlockID](#blockid) | -| Timestamp | [Time](#Time) | Timestamp represents the time at which a validator signed. | [Time](#time) | +| Timestamp | [Time](#time) | Timestamp represents the time at which a validator signed. | [Time](#time) | | Signature | slice of bytes (`[]byte`) | Signature by the validator if they participated in consensus for the associated bock. | Length of signature must be > 0 and < 64 | ## SignedMsgType @@ -313,7 +313,7 @@ enum SignedMsgType { ## Signature -Signatures in Tendermint are raw bytes representing the underlying signature. +Signatures in CometBFT are raw bytes representing the underlying signature. See the [signature spec](./encoding.md#key-types) for more. @@ -327,9 +327,9 @@ EvidenceList is a simple wrapper for a list of evidence: ## Evidence -Evidence in Tendermint is used to indicate breaches in the consensus by a validator. +Evidence in CometBFT is used to indicate breaches in the consensus by a validator. -More information on how evidence works in Tendermint can be found [here](../consensus/evidence.md) +More information on how evidence works in CometBFT can be found [here](../consensus/evidence.md) ### DuplicateVoteEvidence @@ -342,7 +342,7 @@ in the same round of the same height. Votes are lexicographically sorted on `Blo | VoteB | [Vote](#vote) | The second vote submitted by a validator when they equivocated | VoteB must adhere to [Vote](#vote) validation rules | | TotalVotingPower | int64 | The total power of the validator set at the height of equivocation | Must be equal to nodes own copy of the data | | ValidatorPower | int64 | Power of the equivocating validator at the height | Must be equal to the nodes own copy of the data | -| Timestamp | [Time](#Time) | Time of the block where the equivocation occurred | Must be equal to the nodes own copy of the data | +| Timestamp | [Time](#time) | Time of the block where the equivocation occurred | Must be equal to the nodes own copy of the data | ### LightClientAttackEvidence @@ -353,11 +353,11 @@ and Amnesia. These attacks are exhaustive. You can find a more detailed overview | Name | Type | Description | Validation | |----------------------|------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------------| -| ConflictingBlock | [LightBlock](#LightBlock) | Read Below | Must adhere to the validation rules of [lightBlock](#lightblock) | +| ConflictingBlock | [LightBlock](#lightblock) | Read Below | Must adhere to the validation rules of [lightBlock](#lightblock) | | CommonHeight | int64 | Read Below | must be > 0 | -| Byzantine Validators | Array of [Validators](#Validators) | validators that acted maliciously | Read Below | +| Byzantine Validators | Array of [Validators](#validator) | validators that acted maliciously | Read Below | | TotalVotingPower | int64 | The total power of the validator set at the height of the infraction | Must be equal to the nodes own copy of the data | -| Timestamp | [Time](#Time) | Time of the block where the infraction occurred | Must be equal to the nodes own copy of the data | +| Timestamp | [Time](#time) | Time of the block where the infraction occurred | Must be equal to the nodes own copy of the data | ## LightBlock @@ -376,7 +376,7 @@ The SignedhHeader is the [header](#header) accompanied by the commit to prove it | Name | Type | Description | Validation | |--------|-------------------|-------------------|-----------------------------------------------------------------------------------| -| Header | [Header](#Header) | [Header](#header) | Header cannot be nil and must adhere to the [Header](#Header) validation criteria | +| Header | [Header](#header) | [Header](#header) | Header cannot be nil and must adhere to the [Header](#header) validation criteria | | Commit | [Commit](#commit) | [Commit](#commit) | Commit cannot be nil and must adhere to the [Commit](#commit) criteria | ## ValidatorSet @@ -415,7 +415,7 @@ func SumTruncated(bz []byte) []byte { | Name | Type | Description | Field Number | |-----------|-------------------------------------|------------------------------------------------------------------------------|--------------| | block | [BlockParams](#blockparams) | Parameters limiting the size of a block and time between consecutive blocks. | 1 | -| evidence | [EvidenceParams](#evidenceparams) | Parameters limiting the validity of evidence of byzantine behaviour. | 2 | +| evidence | [EvidenceParams](#evidenceparams) | Parameters limiting the validity of evidence of byzantine behavior. | 2 | | validator | [ValidatorParams](#validatorparams) | Parameters limiting the types of public keys validators can use. | 3 | | version | [BlockParams](#blockparams) | The ABCI application version. | 4 | diff --git a/spec/core/encoding.md b/spec/core/encoding.md index c137575d78..48e201b02c 100644 --- a/spec/core/encoding.md +++ b/spec/core/encoding.md @@ -2,7 +2,7 @@ ## Protocol Buffers -Tendermint uses [Protocol Buffers](https://developers.google.com/protocol-buffers), specifically proto3, for all data structures. +CometBFT uses [Protocol Buffers](https://developers.google.com/protocol-buffers), specifically proto3, for all data structures. Please see the [Proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) for more details. @@ -20,15 +20,15 @@ be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300. ## Hashing -Tendermint uses `SHA256` as its hash function. +CometBFT uses `SHA256` as its hash function. Objects are always serialized before being hashed. So `SHA256(obj)` is short for `SHA256(ProtoEncoding(obj))`. ## Public Key Cryptography -Tendermint uses Protobuf [Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof) +CometBFT uses Protobuf [Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof) to distinguish between different types public keys, and signatures. -Additionally, for each public key, Tendermint +Additionally, for each public key, CometBFT defines an Address function that can be used as a more compact identifier in place of the public key. Here we list the concrete types, their names, and prefix bytes for public keys and signatures, as well as the address schemes @@ -49,9 +49,9 @@ address = SHA256(pubkey)[:20] The signature is the raw 64-byte ED25519 signature. -Tendermint adopted [zip215](https://zips.z.cash/zip-0215) for verification of ed25519 signatures. +CometBFT adopts [zip215](https://zips.z.cash/zip-0215) for verification of ed25519 signatures. -> Note: This change will be released in the next major release of Tendermint-Go (0.35). +> Note: This change will be released in the next major release of CometBFT. #### Secp256k1 @@ -99,7 +99,7 @@ See details of SimpleProof, below. ### MakeParts Encode an object using Protobuf and slice it into parts. -Tendermint uses a part size of 65536 bytes, and allows a maximum of 1601 parts +CometBFT uses a part size of 65536 bytes, and allows a maximum of 1601 parts (see `types.MaxBlockPartsCount`). This corresponds to the hard-coded block size limit of 100MB. @@ -113,7 +113,7 @@ For an overview of Merkle trees, see [wikipedia](https://en.wikipedia.org/wiki/Merkle_tree) We use the RFC 6962 specification of a merkle tree, with sha256 as the hash function. -Merkle trees are used throughout Tendermint to compute a cryptographic digest of a data structure. +Merkle trees are used throughout CometBFT to compute a cryptographic digest of a data structure. The differences between RFC 6962 and the simplest form a merkle tree are that: 1. leaf nodes and inner nodes have different hashes. @@ -243,11 +243,11 @@ conceivable purpose. ### IAVL+ Tree -Because Tendermint only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/ae77f0080a724b159233bd9b289b2e91c0de21b5/docs/interfaces/lite/specification.md) +Because CometBFT only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/ae77f0080a724b159233bd9b289b2e91c0de21b5/docs/interfaces/lite/specification.md) ## JSON -Tendermint has its own JSON encoding in order to keep backwards compatibility with the previous RPC layer. +CometBFT has its own JSON encoding in order to keep backwards compatibility with the previous RPC layer. Registered types are encoded as: diff --git a/spec/core/genesis.md b/spec/core/genesis.md index 1dc019e77e..63db11bd46 100644 --- a/spec/core/genesis.md +++ b/spec/core/genesis.md @@ -1,6 +1,6 @@ # Genesis -The genesis file is the starting point of a chain. An application will populate the `app_state` field in the genesis with their required fields. Tendermint is not able to validate this section because it is unaware what application state consists of. +The genesis file is the starting point of a chain. An application will populate the `app_state` field in the genesis with their required fields. CometBFT is not able to validate this section because it is unaware what application state consists of. ## Genesis Fields @@ -11,7 +11,7 @@ The genesis file is the starting point of a chain. An application will populate - `block` - `max_bytes`: The max amount of bytes a block can be. - `max_gas`: The maximum amount of gas that a block can have. - - `time_iota_ms`: This parameter has no value anymore in Tendermint-core. + - `time_iota_ms`: This parameter has no value anymore in CometBFT. - `evidence` - `max_age_num_blocks`: After this preset amount of blocks has passed a single piece of evidence is considered invalid @@ -21,7 +21,7 @@ The genesis file is the starting point of a chain. An application will populate > Note: For evidence to be considered invalid, evidence must be older than both `max_age_num_blocks` and `max_age_duration` - `validator` - - `pub_key_types`: Defines which curves are to be accepted as a valid validator consensus key. Tendermint supports ed25519, sr25519 and secp256k1. + - `pub_key_types`: Defines which curves are to be accepted as a valid validator consensus key. CometBFT supports ed25519, sr25519 and secp256k1. - `version` - `app_version`: The version of the application. This is set by the application and is used to identify which version of the app a user should be using in order to operate a node. @@ -31,4 +31,4 @@ The genesis file is the starting point of a chain. An application will populate - `app_hash`: The applications state root hash. This field does not need to be populated at the start of the chain, the application may provide the needed information via `Initchain`. -- `app_state`: This section is filled in by the application and is unknown to Tendermint. +- `app_state`: This section is filled in by the application and is unknown to CometBFT. diff --git a/spec/core/readme.md b/spec/core/readme.md index 46f95f1b76..d34d987c19 100644 --- a/spec/core/readme.md +++ b/spec/core/readme.md @@ -5,7 +5,7 @@ parent: order: 3 --- -This section describes the core types and functionality of the Tendermint protocol implementation. +This section describes the core types and functionality of the CometBFT protocol implementation. - [Core Data Structures](./data_structures.md) - [Encoding](./encoding.md) diff --git a/spec/ivy-proofs/domain_model.ivy b/spec/ivy-proofs/domain_model.ivy index 0f12f7288a..1fd3cc99ef 100644 --- a/spec/ivy-proofs/domain_model.ivy +++ b/spec/ivy-proofs/domain_model.ivy @@ -131,13 +131,14 @@ object nset = { # the type of node sets object classic_bft = { relation quorum_intersection private { - definition [quorum_intersection_def] quorum_intersection = forall Q1,Q2. exists N. well_behaved(N) & nset.member(N, Q1) & nset.member(N, Q2) # every two quorums have a well-behaved node in common + definition [quorum_intersection_def] quorum_intersection = forall Q1,Q2. nset.is_quorum(Q1) & nset.is_quorum(Q2) + -> exists N. well_behaved(N) & nset.member(N, Q1) & nset.member(N, Q2) # every two quorums have a well-behaved node in common } } trusted isolate accountable_bft = { # this is our baseline assumption about quorums: private { - property [max_2f_byzantine] exists N . well_behaved(N) & nset.member(N,Q) # every quorum has a well-behaved member + property [max_2f_byzantine] nset.is_quorum(Q) -> exists N . well_behaved(N) & nset.member(N,Q) # every quorum has a well-behaved member } } diff --git a/spec/light-client/README.md b/spec/light-client/README.md index 1fec7a5eab..10fa783e88 100644 --- a/spec/light-client/README.md +++ b/spec/light-client/README.md @@ -10,7 +10,7 @@ parent: This directory contains work-in-progress English and TLA+ specifications for the Light Client protocol. Implementations of the light client can be found in [Rust](https://github.com/informalsystems/tendermint-rs/tree/master/light-client) and -[Go](https://github.com/tendermint/tendermint/tree/v0.34.x/light). +[Go](https://github.com/cometbft/cometbft/tree/v0.34.x/light). Light clients are assumed to be initialized once from a trusted source with a trusted header and validator set. The light client @@ -19,21 +19,21 @@ verifying a minimal set of data from a network of full nodes (at least one of wh The light client is decomposed into two main components: -- [Commit Verification](#Commit-Verification) - verify signed headers and associated validator +- [Commit Verification](#commit-verification) - verify signed headers and associated validator set changes from a single full node, called primary -- [Attack Detection](#Attack-Detection) - verify commits across multiple full nodes (called secondaries) and detect conflicts (ie. the existence of a lightclient attack) +- [Attack Detection](#attack-detection) - verify commits across multiple full nodes (called secondaries) and detect conflicts (ie. the existence of a lightclient attack) In case a lightclient attack is detected, the lightclient submits evidence to a full node which is responsible for "accountability", that is, punishing attackers: -- [Accountability](#Accountability) - given evidence for an attack, compute a set of validators that are responsible for it. +- [Accountability](#accountability) - given evidence for an attack, compute a set of validators that are responsible for it. ## Commit Verification The [English specification](verification/verification_001_published.md) describes the light client commit verification problem in terms of the temporal properties -[LCV-DIST-SAFE.1](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification_001_published.md#lcv-dist-safe1) and -[LCV-DIST-LIVE.1](https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification/verification_001_published.md#lcv-dist-live1). -Commit verification is assumed to operate within the Tendermint Failure Model, where +2/3 of validators are correct for some time period and +[LCV-DIST-SAFE.1](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) and +[LCV-DIST-LIVE.1](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-live1). +Commit verification is assumed to operate within the Cosmos Failure Model, where +2/3 of validators are correct for some time period and validator sets can change arbitrarily at each height. A light client protocol is also provided, including all checks that @@ -124,7 +124,7 @@ protocol matches corresponding headers provided by the secondaries. If this is not the case, the protocol analyses the verification traces of the involved full nodes and generates -[evidence](detection/detection_003_reviewed.md#tmbc-lc-evidence-data1) +[evidence](detection/detection_003_reviewed.md#cmbc-lc-evidence-data1) of misbehavior that can be submitted to a full node so that the faulty validators can be punished. @@ -136,7 +136,7 @@ termination, which can be model checked with Apalache. The `LCD_MC*.tla` files contain concrete parameters for the [TLA+ specification](detection/LCDetector_003_draft.tla), in order to run the model checker. -For instance, [LCD_MC4_4_faulty.tla](detection/MC4_4_faulty.tla) +For instance, [LCD_MC4_4_faulty.tla](./verification/MC4_4_faulty.tla) contains the following parameters for the nodes, heights, the trusting period, the clock drifts, correctness of the nodes, and the ratio of the faulty processes: @@ -186,7 +186,7 @@ The detailed experimental results are to be added soon. ## Accountability The [English specification](attacks/isolate-attackers_002_reviewed.md) -defines the protocol that is executed on a full node upon receiving attack [evidence](detection/detection_003_reviewed.md#tmbc-lc-evidence-data1) from a lightclient. In particular, the protocol handles three types of attacks +defines the protocol that is executed on a full node upon receiving attack [evidence](detection/detection_003_reviewed.md#cmbc-lc-evidence-data1) from a lightclient. In particular, the protocol handles three types of attacks - lunatic - equivocation diff --git a/spec/light-client/accountability/README.md b/spec/light-client/accountability/README.md index bb872649bb..64b475bec7 100644 --- a/spec/light-client/accountability/README.md +++ b/spec/light-client/accountability/README.md @@ -9,7 +9,7 @@ parent: ## Problem Statement -Tendermint consensus guarantees the following specifications for all heights: +Tendermint consensus algorithm guarantees the following specifications for all heights: * agreement -- no two correct full nodes decide differently. * validity -- the decided block satisfies the predefined predicate *valid()*. @@ -22,9 +22,9 @@ The agreement property says that for a given height, any two correct validators However, faulty nodes may forge blocks and try to convince users (light clients) that the blocks had been correctly generated. In addition, Tendermint agreement might be violated in the case where 1/3 or more of the voting power belongs to faulty validators: Two correct validators decide on different blocks. The latter case motivates the term "fork": as Tendermint consensus also agrees on the next validator set, correct validators may have decided on disjoint next validator sets, and the chain branches into two or more partitions (possibly having faulty validators in common) and each branch continues to generate blocks independently of the other. -We say that a fork is a case in which there are two commits for different blocks at the same height of the blockchain. The proplem is to ensure that in those cases we are able to detect faulty validators (and not mistakenly accuse correct validators), and incentivize therefore validators to behave according to the protocol specification. +We say that a fork is a case in which there are two commits for different blocks at the same height of the blockchain. The problem is to ensure that in those cases we are able to detect faulty validators (and not mistakenly accuse correct validators), and incentivize therefore validators to behave according to the protocol specification. -**Conceptual Limit.** In order to prove misbehavior of a node, we have to show that the behavior deviates from correct behavior with respect to a given algorithm. Thus, an algorithm that detects misbehavior of nodes executing some algorithm *A* must be defined with respect to algorithm *A*. In our case, *A* is Tendermint consensus (+ other protocols in the infrastructure; e.g.,full nodes and the Light Client). If the consensus algorithm is changed/updated/optimized in the future, we have to check whether changes to the accountability algorithm are also required. All the discussions in this document are thus inherently specific to Tendermint consensus and the Light Client specification. +**Conceptual Limit.** In order to prove misbehavior of a node, we have to show that the behavior deviates from correct behavior with respect to a given algorithm. Thus, an algorithm that detects misbehavior of nodes executing some algorithm *A* must be defined with respect to algorithm *A*. In our case, *A* is Tendermint consensus (+ other protocols in the infrastructure; e.g., Cosmos full nodes and the Light Client). If the consensus algorithm is changed/updated/optimized in the future, we have to check whether changes to the accountability algorithm are also required. All the discussions in this document are thus inherently specific to Tendermint consensus and the Light Client specification. **Q:** Should we distinguish agreement for validators and full nodes for agreement? The case where all correct validators agree on a block, but a correct full node decides on a different block seems to be slightly less severe that the case where two correct validators decide on different blocks. Still, if a contaminated full node becomes validator that may be problematic later on. Also it is not clear how gossiping is impaired if a contaminated full node is on a different branch. @@ -136,7 +136,7 @@ Validators: * CA and CB are disjoint * F - a set of faulty validators with 1/3 or more voting power -Observe that this setting violates the Tendermint failure model. +Observe that this setting violates the Cosmos failure model. Execution: @@ -207,7 +207,9 @@ Execution: *Remark.* In this case, the more than 1/3 of faulty validators do not need to commit an equivocation (F1) as they only vote once per round in the execution. -Detecting faulty validators in the case of such an attack can be done by the fork accountability mechanism described in: . +Detecting faulty validators in the case of such an attack can be done by the fork accountability mechanism described in: + +. If a light client is attacked using this attack with 1/3 or more of voting power (and less than 2/3), the attacker cannot change the application state arbitrarily. Rather, the attacker is limited to a state a correct validator finds acceptable: In the execution above, correct validators still find the value acceptable, however, the block the light client trusts deviates from the one on the main chain. @@ -231,7 +233,7 @@ Consequences: * The validators in F1 will be detectable by the the fork accountability mechanisms. * The validators in F2 cannot be detected using this mechanism. -Only in case they signed something which conflicts with the application this can be used against them. Otherwise they do not do anything incorrect. +Only in case they signed something which conflicts with the application this can be used against them. Otherwise they do not do anything incorrect. * This case is not covered by the report as it only assumes at most 2/3 of faulty validators. **Q:** do we need to define a special kind of attack for the case where a validator sign arbitrarily state? It seems that detecting such attack requires a different mechanism that would require as an evidence a sequence of blocks that led to that state. This might be very tricky to implement. @@ -249,7 +251,7 @@ Validators: * C1 and C2 are disjoint * F - a set of faulty validators with less than 1/3 voting power * one additional faulty process *q* -* F and *q* violate the Tendermint failure model. +* F and *q* violate the Cosmos failure model. Execution: @@ -265,7 +267,7 @@ Consequences: * Only a single faulty validator that previously precommited nil did equivocation, while the other 1/3 of faulty validators actually executed an attack that has exactly the same sequence of messages as part of amnesia attack. Detecting this kind of attack boil down to mechanisms for equivocation and amnesia. -**Q:** should we keep this as a separate kind of attack? It seems that equivocation, amnesia and phantom validators are the only kind of attack we need to support and this gives us security also in other cases. This would not be surprising as equivocation and amnesia are attacks that followed from the protocol and phantom attack is not really an attack to Tendermint but more to the Proof of Stake module. +**Q:** should we keep this as a separate kind of attack? It seems that equivocation, amnesia and phantom validators are the only kind of attack we need to support and this gives us security also in other cases. This would not be surprising as equivocation and amnesia are attacks that followed from the protocol and phantom attack is not really an attack to Tendermint but more to the Cosmos Proof of Stake module. ### Phantom validators diff --git a/spec/light-client/accountability/Synopsis.md b/spec/light-client/accountability/Synopsis.md index 76da3868c7..e8cf15bfa3 100644 --- a/spec/light-client/accountability/Synopsis.md +++ b/spec/light-client/accountability/Synopsis.md @@ -1,7 +1,7 @@ # Synopsis - A TLA+ specification of a simplified Tendermint consensus, tuned for + A TLA+ specification of a simplified Tendermint consensus algorithm, tuned for fork accountability. The simplifications are as follows: - the procotol runs for one height, that is, one-shot consensus diff --git a/spec/light-client/accountability/TendermintAccInv_004_draft.tla b/spec/light-client/accountability/TendermintAccInv_004_draft.tla index 5dd15396de..96d27b0c00 100644 --- a/spec/light-client/accountability/TendermintAccInv_004_draft.tla +++ b/spec/light-client/accountability/TendermintAccInv_004_draft.tla @@ -338,7 +338,7 @@ AllIfValidRoundThenProposal == (* Under this condition, the faulty processes can decide alone *) FaultyQuorum == Cardinality(Faulty) >= THRESHOLD2 -(* The standard condition of the Tendermint security model *) +(* The standard condition of the Cosmos security model *) LessThanThirdFaulty == N > 3 * T /\ Cardinality(Faulty) <= T (* diff --git a/spec/light-client/accountability/TendermintAcc_004_draft.tla b/spec/light-client/accountability/TendermintAcc_004_draft.tla index 9d3a543d4e..29e2e88c08 100644 --- a/spec/light-client/accountability/TendermintAcc_004_draft.tla +++ b/spec/light-client/accountability/TendermintAcc_004_draft.tla @@ -1,6 +1,6 @@ -------------------- MODULE TendermintAcc_004_draft --------------------------- (* - A TLA+ specification of a simplified Tendermint consensus, tuned for + A TLA+ specification of a simplified Tendermint consensus algorithm, tuned for fork accountability. The simplifications are as follows: - the protocol runs for one height, that is, it is one-shot consensus diff --git a/spec/light-client/attacks/Blockchain_003_draft.tla b/spec/light-client/attacks/Blockchain_003_draft.tla index fb6e6e8e87..6b725d83d6 100644 --- a/spec/light-client/attacks/Blockchain_003_draft.tla +++ b/spec/light-client/attacks/Blockchain_003_draft.tla @@ -1,6 +1,6 @@ ------------------------ MODULE Blockchain_003_draft ----------------------------- (* - This is a high-level specification of Tendermint blockchain + This is a high-level specification of a Cosmos blockchain that is designed specifically for the light client. Validators have the voting power of one. If you like to model various voting powers, introduce multiple copies of the same validator diff --git a/spec/light-client/attacks/Isolation_001_draft.tla b/spec/light-client/attacks/Isolation_001_draft.tla index 7406b89422..b00e3dae3d 100644 --- a/spec/light-client/attacks/Isolation_001_draft.tla +++ b/spec/light-client/attacks/Isolation_001_draft.tla @@ -7,14 +7,14 @@ * * It follows the English specification: * - * https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/attacks/isolate-attackers_001_draft.md + * https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/attacks/isolate-attackers_001_draft.md * * The assumptions made in this specification: * * - the voting power of every validator is 1 * (add more validators, if you need more validators) * - * - Tendermint security model is violated + * - Cosmos security model is violated * (there are Byzantine validators who signed a conflicting block) * * Igor Konnov, Zarko Milosevic, Josef Widder, Informal Systems, 2020 @@ -35,7 +35,7 @@ CONSTANTS (* the period within which the validators are trusted *) FAULTY_RATIO (* a pair <> that limits that ratio of faulty validator in the blockchain - from above (exclusive). Tendermint security model prescribes 1 / 3. *) + from above (exclusive). Cosmos security model prescribes 1 / 3. *) VARIABLES blockchain, (* the chain at the full node *) diff --git a/spec/light-client/attacks/LCVerificationApi_003_draft.tla b/spec/light-client/attacks/LCVerificationApi_003_draft.tla index 909eab92b8..8ab5e8259c 100644 --- a/spec/light-client/attacks/LCVerificationApi_003_draft.tla +++ b/spec/light-client/attacks/LCVerificationApi_003_draft.tla @@ -15,7 +15,7 @@ CONSTANTS be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) FAULTY_RATIO (* a pair <> that limits that ratio of faulty validator in the blockchain - from above (exclusive). Tendermint security model prescribes 1 / 3. *) + from above (exclusive). Cosmos security model prescribes 1 / 3. *) VARIABLES localClock (* current time as measured by the light client *) diff --git a/spec/light-client/attacks/isolate-attackers_001_draft.md b/spec/light-client/attacks/isolate-attackers_001_draft.md index e4f585f4a8..5408527921 100644 --- a/spec/light-client/attacks/isolate-attackers_001_draft.md +++ b/spec/light-client/attacks/isolate-attackers_001_draft.md @@ -1,21 +1,23 @@ - + # Lightclient Attackers Isolation > Warning: This is the beginning of an unfinished draft. Don't continue reading! -Adversarial nodes may have the incentive to lie to a lightclient about the state of a Tendermint blockchain. An attempt to do so is called attack. Light client [verification][verification] checks incoming data by checking a so-called "commit", which is a forwarded set of signed messages that is (supposedly) produced during executing Tendermint consensus. Thus, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. +Adversarial nodes may have the incentive to lie to a lightclient about the +state of a Cosmos blockchain, built using Tendermint consensus algorithm. +An attempt to do so is called attack. Light client [verification][verification] checks incoming data by checking a so-called "commit", which is a forwarded set of signed messages that is (supposedly) produced during executing Tendermint consensus. Thus, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. -As Tendermint consensus and light client verification is safe under the assumption of more than 2/3 of correct voting power per block [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link], this implies that if there was an attack then [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] was violated, that is, there is a block such that +As Tendermint consensus and light client verification is safe under the assumption of more than 2/3 of correct voting power per block [[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link], this implies that if there was an attack then [[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link] was violated, that is, there is a block such that - validators deviated from the protocol, and - these validators represent more than 1/3 of the voting power in that block. In the case of an [attack][node-based-attack-characterization], the lightclient [attack detection mechanism][detection] computes data, so called evidence [[LC-DATA-EVIDENCE.1]][LC-DATA-EVIDENCE-link], that can be used -- to proof that there has been attack [[TMBC-LC-EVIDENCE-DATA.1]][TMBC-LC-EVIDENCE-DATA-link] and -- as basis to find the actual nodes that deviated from the Tendermint protocol. +- to proof that there has been attack [[CMBC-LC-EVIDENCE-DATA.1]][CMBC-LC-EVIDENCE-DATA-link] and +- as basis to find the actual nodes that deviated from the Tendermint algorithm. -This specification considers how a full node in a Tendermint blockchain can isolate a set of attackers that launched the attack. The set should satisfy +This specification considers how a full node in a Cosmos blockchain can isolate a set of attackers that launched the attack. The set should satisfy - the set does not contain a correct validator - the set contains validators that represent more than 1/3 of the voting power of a block that is still within the unbonding period @@ -26,7 +28,7 @@ This specification considers how a full node in a Tendermint blockchain can isol # Part I - Basics -For definitions of data structures used here, in particular LightBlocks [[LCV-DATA-LIGHTBLOCK.1]](https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightblock1), cf. [Light Client Verification][verification]. +For definitions of data structures used here, in particular LightBlocks [[LCV-DATA-LIGHTBLOCK.1]](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-data-lightblock1), cf. [Light Client Verification][verification]. # Part II - Definition of the Problem @@ -59,7 +61,7 @@ When an output is generated it satisfies the following properties: - Validators in `ev.ConflictingBlock.Commit` represent more than 1/3 of the voting power in `bc[ev.CommonHeight].NextValidators` - Then: A set of validators in `bc[CommonHeight].NextValidators` that - represent more than 1/3 of the voting power in `bc[ev.commonHeight].NextValidators` - - signed Tendermint consensus messages for height `ev.ConflictingBlock.Header.Height` by violating the Tendermint consensus protocol. + - signed Tendermint consensus messages for height `ev.ConflictingBlock.Header.Height` by violating the Tendermint consensus algorithm. - Else: the empty set. # Part IV - Protocol @@ -133,7 +135,7 @@ func violatesTMValidity(ref Header, ev Header) boolean ``` - Implementation remarks - - checks whether the evidence header `ev` violates the validity property of Tendermint Consensus, by checking agains a reference header + - checks whether the evidence header `ev` violates the validity property of Tendermint consensus algorithm, by checking agains a reference header - Expected precondition - `ref.Height == ev.Height` - Expected postcondition @@ -189,7 +191,7 @@ The main function `isolateMisbehavingProcesses` distinguishes three kinds of wro The question is whether this captures all attacks. First observe that the first checking in `isolateMisbehavingProcesses` is `violatesTMValidity`. It takes care of lunatic attacks. If this check passes, that is, if `violatesTMValidity` returns `FALSE` this means that [FN-NONVALID-OUTPUT] evaluates to false, which implies that `ref.ValidatorsHash = ev.ValidatorsHash`. Hence after `violatesTMValidity`, all the involved validators are the ones from the blockchain. It is thus sufficient to analyze one instance of Tendermint consensus with a fixed group membership (set of validators). Also it is sufficient to consider two different valid consensus values, that is, binary consensus. -**TODO** we have analyzed Tendermint consensus with TLA+ and have accompanied Galois in an independent study of the protocol based on [Ivy proofs](https://github.com/tendermint/spec/tree/master/ivy-proofs). +**TODO** we have analyzed Tendermint consensus algorithm with TLA+ and have accompanied Galois in an independent study of the protocol based on [Ivy proofs](https://github.com/cometbft/cometbft/tree/v0.34.x/spec/ivy-proofs). # References @@ -200,22 +202,22 @@ First observe that the first checking in `isolateMisbehavingProcesses` is `viola [[detection]] The specification of the light client attack detection mechanism. [supervisor]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/supervisor/supervisor_001_draft.md -[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md +[verification]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md [detection]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md [LC-DATA-EVIDENCE-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#lc-data-evidence1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#lc-data-evidence1 -[TMBC-LC-EVIDENCE-DATA-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#tmbc-lc-evidence-data1 +[CMBC-LC-EVIDENCE-DATA-link]: +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#cmbc-lc-evidence-data1 [node-based-attack-characterization]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#node-based-characterization-of-attacks +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#block-based-characterization-of-attacks -[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-fm-2thirds1 +[CMBC-FM-2THIRDS-link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-fm-2thirds1 -[LCV-FUNC-VALID.link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-valid2 +[LCV-FUNC-VALID.link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-func-valid2 diff --git a/spec/light-client/attacks/isolate-attackers_002_reviewed.md b/spec/light-client/attacks/isolate-attackers_002_reviewed.md index febcc10a82..2713156fd2 100644 --- a/spec/light-client/attacks/isolate-attackers_002_reviewed.md +++ b/spec/light-client/attacks/isolate-attackers_002_reviewed.md @@ -1,29 +1,32 @@ + # Lightclient Attackers Isolation -Adversarial nodes may have the incentive to lie to a lightclient about the state of a Tendermint blockchain. An attempt to do so is called attack. Light client [verification][verification] checks incoming data by checking a so-called "commit", which is a forwarded set of signed messages that is (supposedly) produced during executing Tendermint consensus. Thus, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. +Adversarial nodes may have the incentive to lie to a lightclient about the +state of a Cosmos blockchain, built using Tendermint consensus algorithm. +An attempt to do so is called attack. Light client [verification][verification] checks incoming data by checking a so-called "commit", which is a forwarded set of signed messages that is (supposedly) produced during executing Tendermint consensus. Thus, an attack boils down to creating and signing Tendermint consensus messages in deviation from the Tendermint consensus algorithm rules. -As Tendermint consensus and light client verification is safe under the assumption of more than 2/3 of correct voting power per block [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link], this implies that if there was an attack then [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] was violated, that is, there is a block such that +As Tendermint consensus and light client verification is safe under the assumption of more than 2/3 of correct voting power per block [[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link], this implies that if there was an attack then [[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link] was violated, that is, there is a block such that - validators deviated from the protocol, and - these validators represent more than 1/3 of the voting power in that block. In the case of an [attack][node-based-attack-characterization], the lightclient [attack detection mechanism][detection] computes data, so called evidence [[LC-DATA-EVIDENCE.1]][LC-DATA-EVIDENCE-link], that can be used -- to proof that there has been attack [[TMBC-LC-EVIDENCE-DATA.1]][TMBC-LC-EVIDENCE-DATA-link] and -- as basis to find the actual nodes that deviated from the Tendermint protocol. +- to proof that there has been attack [[CMBC-LC-EVIDENCE-DATA.1]][CMBC-LC-EVIDENCE-DATA-link] and +- as basis to find the actual nodes that deviated from the Tendermint algorithm. -This specification considers how a full node in a Tendermint blockchain can isolate a set of attackers that launched the attack. The set should satisfy +This specification considers how a full node in a Cosmos blockchain can isolate a set of attackers that launched the attack. The set should satisfy - the set does not contain a correct validator - the set contains validators that represent more than 1/3 of the voting power of a block that is still within the unbonding period # Outline -After providing the [problem statement](#Part-I---Basics-and-Definition-of-the-Problem), we specify the [isolator function](#Part-II---Protocol) and close with the discussion about its [correctness](#Part-III---Completeness) which is based on computer-aided analysis of Tendermint Consensus. +After providing the [problem statement](#Part-I---Basics-and-Definition-of-the-Problem), we specify the [isolator function](#Part-II---Protocol) and close with the discussion about its [correctness](#Part-III---Completeness) which is based on computer-aided analysis of Tendermint consensus algorithm. # Part I - Basics and Definition of the Problem -For definitions of data structures used here, in particular LightBlocks [[LCV-DATA-LIGHTBLOCK.1]](https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightblock1), we refer to the specification of [Light Client Verification][verification]. +For definitions of data structures used here, in particular LightBlocks [[LCV-DATA-LIGHTBLOCK.1]](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-data-lightblock1), we refer to the specification of [Light Client Verification][verification]. The specification of the [detection mechanism][detection] describes @@ -54,7 +57,7 @@ When an output is generated it satisfies the following properties: - Validators in `ev.ConflictingBlock.Commit` represent more than 1/3 of the voting power in `bc[ev.CommonHeight].NextValidators` - Then: The output is a set of validators in `bc[CommonHeight].NextValidators` that - represent more than 1/3 of the voting power in `bc[ev.commonHeight].NextValidators` - - signed Tendermint consensus messages for height `ev.ConflictingBlock.Header.Height` by violating the Tendermint consensus protocol. + - signed Tendermint consensus messages for height `ev.ConflictingBlock.Header.Height` by violating the Tendermint consensus algorithm. - Else: the empty set. # Part II - Protocol @@ -65,7 +68,7 @@ Here we discuss how to solve the problem of isolating misbehaving processes. We ### Outline -We first check whether the conflicting block can indeed be verified from the common height. We then first check whether it was a lunatic attack (violating validity). If this is not the case, we check for equivocation. If this also is not the case, we start the on-chain [accountability protocol](https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit). +We first check whether the conflicting block can indeed be verified from the common height. We then first check whether it was a lunatic attack (violating validity). If this is not the case, we check for equivocation. If this also is not the case, we start the on-chain [accountability protocol](https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit). #### **[LCAI-FUNC-MAIN.1]** @@ -128,7 +131,7 @@ func violatesTMValidity(ref Header, ev Header) boolean ``` - Implementation remarks - - checks whether the evidence header `ev` violates the validity property of Tendermint Consensus, by checking against a reference header + - checks whether the evidence header `ev` violates the validity property of Tendermint consensus algorithm, by checking against a reference header - Expected precondition - `ref.Height == ev.Height` - Expected postcondition @@ -144,7 +147,7 @@ func violatesTMValidity(ref Header, ev Header) boolean func IsolateAmnesiaAttacker(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress ``` -- Implementation remarks +- Implementation remarks - This triggers the [query/response protocol](https://docs.google.com/document/d/11ZhMsCj3y7zIZz4udO9l25xqb0kl7gmWqNpGVRzOeyY/edit). - Expected postcondition - returns attackers according to [LCAI-INV-Output.1]. @@ -186,7 +189,7 @@ The main function `isolateMisbehavingProcesses` distinguishes three kinds of wro The question is whether this captures all attacks. First observe that the first check in `isolateMisbehavingProcesses` is `violatesTMValidity`. It takes care of lunatic attacks. If this check passes, that is, if `violatesTMValidity` returns `FALSE` this means that [[LCAI-NONVALID-OUTPUT.1]](#LCAI-FUNC-NONVALID1]) evaluates to false, which implies that `ref.ValidatorsHash = ev.ValidatorsHash`. Hence, after `violatesTMValidity`, all the involved validators are the ones from the blockchain. It is thus sufficient to analyze one instance of Tendermint consensus with a fixed group membership (set of validators). Also, as we have two different blocks for the same height, it is sufficient to consider two different valid consensus values, that is, binary consensus. -For this fixed group membership, we have analyzed the attacks using the TLA+ specification of [Tendermint Consensus in TLA+][tendermint-accountability]. We checked that indeed the only possible scenarios that can lead to violation of agreement are **equivocation** and **amnesia**. An independent study by Galois of the protocol based on [Ivy proofs](https://github.com/tendermint/spec/tree/master/ivy-proofs) led to the same conclusion. +For this fixed group membership, we have analyzed the attacks using the TLA+ specification of [Tendermint Consensus in TLA+][tendermint-accountability]. We checked that indeed the only possible scenarios that can lead to violation of agreement are **equivocation** and **amnesia**. An independent study by Galois of the protocol based on [Ivy proofs](https://github.com/cometbft/cometbft/tree/v0.34.x/spec/ivy-proofs) led to the same conclusion. # References @@ -196,28 +199,27 @@ For this fixed group membership, we have analyzed the attacks using the TLA+ spe [[detection]] The specification of the light client attack detection mechanism. -[[tendermint-accountability]]: TLA+ specification to check the types of attacks [tendermint-accountability]: -https://github.com/tendermint/spec/blob/master/rust-spec/tendermint-accountability/README.md +https://github.com/cometbft/cometbft/tree/v0.34.x/spec/light-client/accountability [supervisor]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/supervisor/supervisor_001_draft.md -[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md +[verification]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md [detection]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md [LC-DATA-EVIDENCE-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#lc-data-evidence1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#lc-data-evidence1 -[TMBC-LC-EVIDENCE-DATA-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#tmbc-lc-evidence-data1 +[CMBC-LC-EVIDENCE-DATA-link]: +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#cmbc-lc-evidence-data1 [node-based-attack-characterization]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md#node-based-characterization-of-attacks +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#block-based-characterization-of-attacks -[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-fm-2thirds1 +[CMBC-FM-2THIRDS-link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-fm-2thirds1 -[LCV-FUNC-VALID.link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-valid2 +[LCV-FUNC-VALID.link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-func-valid2 diff --git a/spec/light-client/attacks/notes-on-evidence-handling.md b/spec/light-client/attacks/notes-on-evidence-handling.md index 4b7d819191..242df12c23 100644 --- a/spec/light-client/attacks/notes-on-evidence-handling.md +++ b/spec/light-client/attacks/notes-on-evidence-handling.md @@ -19,7 +19,7 @@ detects an attack, it needs to send to a witness only missing data (common heigh and conflicting light block) as it has its trace. Keeping light client attack data of constant size saves bandwidth and reduces an attack surface. As we will explain below, although in the context of light client core -[verification](https://github.com/informalsystems/tendermint-rs/tree/master/docs/spec/lightclient/verification) +[verification](https://github.com/cometbft/cometbft/tree/main/spec/light-client/verification) the roles of primary and witness are clearly defined, in case of the attack, we run the same attack detection procedure twice where the roles are swapped. The rationale is that the light client does not know what peer is correct (on a right main branch) @@ -68,7 +68,7 @@ The following invariant holds for the primary trace: ### Witness with a conflicting header The verified header at height `h` is cross-checked with every witness as part of -[detection](https://github.com/informalsystems/tendermint-rs/tree/master/docs/spec/lightclient/detection). +[detection](https://github.com/cometbft/cometbft/tree/main/spec/light-client/detection). If a witness returns the conflicting header at the height `h` the following procedure is executed to verify if the conflicting header comes from the valid trace and if that's the case to create an attack evidence: @@ -133,7 +133,7 @@ func DetectLightClientAttack(trace []LightBlock, peer PeerID) (LightClientAttack As part of on chain evidence handling, full nodes identifies misbehaving processes and informs the application, so they can be slashed. Note that only bonded validators should be reported to the application. There are three types of attacks that can be executed against -Tendermint light client: +light client: - lunatic attack - equivocation attack and diff --git a/spec/light-client/detection/Blockchain_003_draft.tla b/spec/light-client/detection/Blockchain_003_draft.tla index 2b37c1b181..c57bc46938 100644 --- a/spec/light-client/detection/Blockchain_003_draft.tla +++ b/spec/light-client/detection/Blockchain_003_draft.tla @@ -1,6 +1,6 @@ ------------------------ MODULE Blockchain_003_draft ----------------------------- (* - This is a high-level specification of Tendermint blockchain + This is a high-level specification of a Cosmos blockchain that is designed specifically for the light client. Validators have the voting power of one. If you like to model various voting powers, introduce multiple copies of the same validator diff --git a/spec/light-client/detection/LCDetector_003_draft.tla b/spec/light-client/detection/LCDetector_003_draft.tla index e2d32e996f..fd1f60c09f 100644 --- a/spec/light-client/detection/LCDetector_003_draft.tla +++ b/spec/light-client/detection/LCDetector_003_draft.tla @@ -3,7 +3,7 @@ * This is a specification of the light client detector module. * It follows the English specification: * - * https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md + * https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md * * The assumptions made in this specification: * @@ -38,7 +38,7 @@ CONSTANTS be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) FAULTY_RATIO, (* a pair <> that limits that ratio of faulty validator in the blockchain - from above (exclusive). Tendermint security model prescribes 1 / 3. *) + from above (exclusive). Cosmos security model prescribes 1 / 3. *) IS_PRIMARY_CORRECT, IS_SECONDARY_CORRECT diff --git a/spec/light-client/detection/LCVerificationApi_003_draft.tla b/spec/light-client/detection/LCVerificationApi_003_draft.tla index 909eab92b8..8ab5e8259c 100644 --- a/spec/light-client/detection/LCVerificationApi_003_draft.tla +++ b/spec/light-client/detection/LCVerificationApi_003_draft.tla @@ -15,7 +15,7 @@ CONSTANTS be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) FAULTY_RATIO (* a pair <> that limits that ratio of faulty validator in the blockchain - from above (exclusive). Tendermint security model prescribes 1 / 3. *) + from above (exclusive). Cosmos security model prescribes 1 / 3. *) VARIABLES localClock (* current time as measured by the light client *) diff --git a/spec/light-client/detection/README.md b/spec/light-client/detection/README.md index 50d8ab6fe1..86fea07a35 100644 --- a/spec/light-client/detection/README.md +++ b/spec/light-client/detection/README.md @@ -5,16 +5,16 @@ parent: order: 2 --- -# Tendermint fork detection and IBC fork detection +# Cosmos fork detection and IBC fork detection ## Status This is a work in progress. This directory captures the ongoing work and discussion on fork -detection both in the context of a Tendermint light node and in the +detection both in the context of a Cosmos light node and in the context of IBC. It contains the following files -### [detection.md](./detection.md) +### [detection.md](./detection_003_reviewed.md) a draft of the light node fork detection including "proof of fork" definition, that is, the data structure to submit evidence to full @@ -33,7 +33,7 @@ A collection of ideas and intuitions from recent discussions - a collection of requirements for fork detection in the IBC context. In particular it contains a section "Required Changes in - ICS 007" with necessary updates to ICS 007 to support Tendermint + ICS 007" with necessary updates to ICS 007 to support Cosmos fork detection ### [draft-functions.md](./draft-functions.md) diff --git a/spec/light-client/detection/detection_001_reviewed.md b/spec/light-client/detection/detection_001_reviewed.md index db8c29a143..b204677d69 100644 --- a/spec/light-client/detection/detection_001_reviewed.md +++ b/spec/light-client/detection/detection_001_reviewed.md @@ -1,3 +1,4 @@ + # ***This an unfinished draft. Comments are welcome!*** **TODO:** We will need to do small adaptations to the verification @@ -24,13 +25,13 @@ specification. So we should decide how In this specification, we strengthen the light client to be resistant against so-called light client attacks. In a light client attack, all -the correct Tendermint full nodes agree on the sequence of generated +the correct Cosmos full nodes agree on the sequence of generated blocks (no fork), but a set of faulty full nodes attack a light client by generating (signing) a block that deviates from the block of the same height on the blockchain. In order to do so, some of these faulty full nodes must have been validators before and violate -[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link), as otherwise, if -[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) would hold, +[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link), as otherwise, if +[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link) would hold, [verification](verification) would satisfy [[LCV-SEQ-SAFE.1]](LCV-SEQ-SAFE-link). @@ -43,20 +44,20 @@ height *root* (that serves as a root of trust), and a verification trace (a sequence of lightblocks) that the primary provided. In case the detector observes a light client attack, it computes -evidence data that can be used by Tendermint full nodes to isolate a +evidence data that can be used by Cosmos full nodes to isolate a set of faulty full nodes that are still within the unbonding period (more than 1/3 of the voting power of the validator set at some block of the chain), -and report them via ABCI to the application of a Tendermint blockchain +and report them via ABCI to the application of a Cosmos blockchain in order to punish faulty nodes. ## Context of this document The light client [verification](verification) specification is -designed for the Tendermint failure model (1/3 assumption) -[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link). It is safe under this +designed for the Cosmos failure model (1/3 assumption) +[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link). It is safe under this assumption, and live if it can reliably (that is, no message loss, no duplication, and eventually delivered) and timely communicate with a -correct full node. If [[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) assumption is violated, the light client +correct full node. If [[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link) assumption is violated, the light client can be fooled to trust a light block that was not generated by Tendermint consensus. @@ -84,27 +85,27 @@ mean by light client attacks (that are considered in this specification) and how they differ from main-chain forks. To this end we start by defining some properties of the sequence of blocks that is decided upon by Tendermint consensus in normal operation (if the -Tendermint failure model holds -[[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link)), +Cosmos failure model holds +[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link)), and then define different deviations that correspond to attack scenarios. -#### **[TMBC-GENESIS.1]** +#### **[CMBC-GENESIS.1]** Let *Genesis* be the agreed-upon initial block (file). -#### **[TMBC-FUNC-SIGN.1]** +#### **[CMBC-FUNC-SIGN.1]** Let *b* and *c* be two light blocks with *b.Header.Height + 1 = c.Header.Height*. We define the predicate **signs(b,c)** to hold iff *c.Header.LastCommit* is in *PossibleCommit(b)*. -[[TMBC-SOUND-DISTR-POSS-COMMIT.1]](TMBC-SOUND-DISTR-POSS-COMMIT-link). +[[CMBC-SOUND-DISTR-POSS-COMMIT.1]](CMBC-SOUND-DISTR-POSS-COMMIT-link). > The above encodes sequential verification, that is, intuitively, > b.Header.NextValidators = c.Header.Validators and 2/3 of > these Validators signed c? -#### **[TMBC-FUNC-SUPPORT.1]** +#### **[CMBC-FUNC-SUPPORT.1]** Let *b* and *c* be two light blocks. We define the predicate **supports(b,c,t)** to hold iff @@ -113,13 +114,13 @@ Let *b* and *c* be two light blocks. We define the predicate - the voting power in *b.NextValidators* of nodes in *c.Commit* is more than 1/3 of *TotalVotingPower(b.Header.NextValidators)* -> That is, if the [Tendermint failure model](TMBC-FM-2THIRDS-link) +> That is, if the [Cosmos failure model](CMBC-FM-2THIRDS-link) > holds, then *c* has been signed by at least one correct full node, cf. -> [[TMBC-VAL-CONTAINS-CORR.1]](TMBC-VAL-CONTAINS-CORR-link). +> [[CMBC-VAL-CONTAINS-CORR.1]](CMBC-VAL-CONTAINS-CORR-link). > The following formalizes that *b* was properly generated by > Tendermint; *b* can be traced back to genesis -#### **[TMBC-SEQ-ROOTED.1]** +#### **[CMBC-SEQ-ROOTED.1]** Let *b* be a light block. We define *sequ-rooted(b)* iff for all *i*, *1 <= i < h = b.Header.Height*, @@ -133,7 +134,7 @@ there exist light blocks *a(i)* s.t. > skipping verification. Observe that we do not require here (yet) > that *b* was properly generated. -#### **[TMBC-SKIP-TRACE.1]** +#### **[CMBC-SKIP-TRACE.1]** Let *b* and *c* be light blocks. We define *skip-trace(b,c,t)* if at time t there exists an *h* and a sequence *a(1)*, ... *a(h)* s.t. @@ -151,7 +152,7 @@ We call such a sequence *a(1)*, ... *a(h)* a **verification trace**. > maximum height of all blocks decided upon by Tendermint at this > moment. -#### **[TMBC-SIGN-SKIP-MATCH.1]** +#### **[CMBC-SIGN-SKIP-MATCH.1]** Let *a*, *b*, *c*, be light blocks and *t* a time, we define *sign-skip-match(a,b,c,t) = true* iff the following implication @@ -171,7 +172,7 @@ implies *b.Header = c.Header*. > *c* that both can be verified from a common block *a* from the > chain. Thus, the following describes an attack. -#### **[TMBC-ATTACK.1]** +#### **[CMBC-ATTACK.1]** If there exists three light blocks a, b, and c, with *sign-skip-match(a,b,c,t) = false* then we have an *attack*. We say @@ -182,7 +183,7 @@ we have **an attack at height** *b.Header.Height* and write > several blocks that satisfy the above requirement for the same > blocks *b* and *c*. -[[TMBC-ATTACK.1]](#TMBC-ATTACK1) is a formalization of the violation +[[CMBC-ATTACK.1]](#CMBC-ATTACK1) is a formalization of the violation of the agreement property based on the result of consensus, that is, the generated blocks. @@ -201,7 +202,7 @@ discussion we also provide a ### Node-based characterization of attacks -#### **[TMBC-MC-FORK.1]** +#### **[CMBC-MC-FORK.1]** We say there is a (main chain) fork at time *t* if @@ -211,11 +212,11 @@ We say there is a (main chain) fork at time *t* if - *j* has decided on *c* and - there exist *a* such that *attack(a,b,c,t)*. -#### **[TMBC-LC-ATTACK.1]** +#### **[CMBC-LC-ATTACK.1]** We say there is a light client attack at time *t*, if -- there is **no** (main chain) fork [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1), and +- there is **no** (main chain) fork [[CMBC-MC-FORK.1]](#CMBC-MC-FORK1), and - there exist nodes that have computed light blocks *b* and *c* and - there exist *a* such that *attack(a,b,c,t)*. @@ -228,14 +229,14 @@ We say the attack is at height *a.Header.Height*. > convince > the light client that *c* is the block from the chain. -#### **[TMBC-LC-ATTACK-EVIDENCE.1]** +#### **[CMBC-LC-ATTACK-EVIDENCE.1]** We consider the following case of a light client attack -[[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1): +[[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1): - *attack(a,b,c,t)* - there is a peer p1 that has a sequence *chain* of blocks from *a* to *b* -- *skip-trace(a,c,t)*: by [[TMBC-SKIP-TRACE.1]](#TMBC-SKIP-TRACE1) there is a +- *skip-trace(a,c,t)*: by [[CMBC-SKIP-TRACE.1]](#CMBC-SKIP-TRACE1) there is a verification trace *v* of the form *a = v(1)*, ... *v(h) = c* Evidence for p1 (that proves an attack) consists for index i @@ -264,7 +265,7 @@ Now by contradiction assume there is no evidence. Thus i = h - 1 we get **NOT E1(h-1)**. Contradiction. QED. -#### **[TMBC-LC-EVIDENCE-DATA.1]** +#### **[CMBC-LC-EVIDENCE-DATA.1]** To prove the attack to p1, because of Point E1, it is sufficient to submit @@ -284,7 +285,7 @@ operational. > They might be relevant for a closer analysis of fork scenarios on the > chain, which is out of the scope of this specification. -#### **[TMBC-SIGN-UNIQUE.1]** +#### **[CMBC-SIGN-UNIQUE.1]** Let *b* and *c* be light blocks, we define the predicate *sign-unique(b,c)* to evaluate to true iff the following implication @@ -296,24 +297,24 @@ evaluates to true: implies *b = c*. -#### **[TMBC-BLOCKS-MCFORK.1]** +#### **[CMBC-BLOCKS-MCFORK.1]** If there exists two light blocks b and c, with *sign-unique(b,c) = false* then we have a *fork*. > The difference of the above definition to -> [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1) is subtle. The latter requires a +> [[CMBC-MC-FORK.1]](#CMBC-MC-FORK1) is subtle. The latter requires a > full node being affected by a bad block while -> [[TMBC-BLOCKS-MCFORK.1]](#TMBC-BLOCKS-MCFORK1) just requires that a +> [[CMBC-BLOCKS-MCFORK.1]](#CMBC-BLOCKS-MCFORK1) just requires that a > bad block exists, possibly in memory of an attacker. > The following captures a light client fork. There is no fork up to > the height of block b. However, c is of that height, is different, > and passes skipping verification. It is a stricter property than -> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1), as -> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) requires that no correct full +> [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1), as +> [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1) requires that no correct full > node is affected. -#### **[TMBC-BLOCKS-LCFORK.1]** +#### **[CMBC-BLOCKS-LCFORK.1]** Let *a*, *b*, *c*, be light blocks and *t* a time. We define *light-client-fork(a,b,c,t)* iff @@ -329,7 +330,7 @@ Let *a*, *b*, *c*, be light blocks and *t* a time. We define > *a.height < b.height* (which is implied by the definitions which > unfold until *supports()*). -#### **[TMBC-BOGUS.1]** +#### **[CMBC-BOGUS.1]** Let *b* be a light block and *t* a time. We define *bogus(b,t)* iff @@ -352,13 +353,13 @@ which use the light client protocols to observe the state of a blockchain). In order to provide full nodes the incentive to follow the protocols when communicating with the light client, this specification also considers the generation of evidence that will -also be processed by the Tendermint blockchain. +also be processed by the Cosmos blockchain. #### **[LCD-IP-MODEL.1]** The detector is designed under the assumption that -- [[TMBC-FM-2THIRDS]](TMBC-FM-2THIRDS-link) may be violated +- [[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link) may be violated - there is no fork on the main chain. > As a result some faulty full nodes may launch an attack on a light @@ -449,7 +450,7 @@ before the timeout expires. ### Evidence Following the definition of -[[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1), by evidence +[[CMBC-LC-ATTACK-EVIDENCE.1]](#CMBC-LC-ATTACK-EVIDENCE1), by evidence we refer to a variable of the following type #### **[LC-DATA-EVIDENCE.1]** @@ -504,8 +505,8 @@ been verified before, and that were provided by the primary. #### **[LCD-DIST-INV-ATTACK.1]** If the detector returns evidence for height *h* -[[TMBC-LC-EVIDENCE-DATA.1]](#TMBC-LC-EVIDENCE-DATA1), then there is an -attack at height *h*. [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) +[[CMBC-LC-EVIDENCE-DATA.1]](#CMBC-LC-EVIDENCE-DATA1), then there is an +attack at height *h*. [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1) #### **[LCD-DIST-INV-STORE.1]** @@ -766,23 +767,19 @@ Once a bogus block is recognized as such the secondary is removed. [[supervisor]] The specification of the light client supervisor. -[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md +[verification]: https://github.com/cometbft/cometbft/tree/v0.34.x/spec/light-client/verification -[supervisor]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor.md +[supervisor]: https://github.com/cometbft/cometbft/tree/v0.34.x/spec/light-client/supervisor -[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md -[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#tmbc-fm-2thirds1 -[TMBC-SOUND-DISTR-POSS-COMMIT-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#tmbc-sound-distr-poss-commit1 -[LCV-SEQ-SAFE-link]:https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#lcv-seq-safe1 -[TMBC-VAL-CONTAINS-CORR-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#tmbc-val-contains-corr1 +[CMBC-VAL-CONTAINS-CORR-link]: +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-val-contains-corr1 [fetch]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#lcv-func-fetch1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-func-fetch1 [LCV-INV-TP1-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification.md#lcv-inv-tp1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-inv-tp1 diff --git a/spec/light-client/detection/detection_003_reviewed.md b/spec/light-client/detection/detection_003_reviewed.md index 13f6e9716a..89fd846c42 100644 --- a/spec/light-client/detection/detection_003_reviewed.md +++ b/spec/light-client/detection/detection_003_reviewed.md @@ -1,15 +1,16 @@ + # Light Client Attack Detector In this specification, we strengthen the light client to be resistant against so-called light client attacks. In a light client attack, all -the correct Tendermint full nodes agree on the sequence of generated +the correct Cosmos full nodes agree on the sequence of generated blocks (no fork), but a set of faulty full nodes attack a light client by generating (signing) a block that deviates from the block of the same height on the blockchain. In order to do so, some of these faulty full nodes must have been validators before and violate the assumption of more than two thirds of "correct voting power" -[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link], as otherwise, if -[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] would hold, +[[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link], as otherwise, if +[[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link] would hold, [verification][verification] would satisfy [[LCV-SEQ-SAFE.1]][LCV-SEQ-SAFE-link]. @@ -22,22 +23,22 @@ height *root* (that serves as a root of trust), and a verification trace (a sequence of lightblocks) that the primary provided. In case the detector observes a light client attack, it computes -evidence data that can be used by Tendermint full nodes to isolate a +evidence data that can be used by Cosmos full nodes to isolate a set of faulty full nodes that are still within the unbonding period (more than 1/3 of the voting power of the validator set at some block of the chain), and report them via ABCI (application/blockchain interface) to the application of a -Tendermint blockchain in order to punish faulty nodes. +Cosmos blockchain in order to punish faulty nodes. ## Context of this document The light client [verification][verification] specification is -designed for the Tendermint failure model (1/3 assumption) -[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link]. It is safe under this +designed for the Cosmos failure model (1/3 assumption) +[[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link]. It is safe under this assumption, and live if it can reliably (that is, no message loss, no duplication, and eventually delivered) and timely communicate with a -correct full node. If [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] +correct full node. If [[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link] assumption is violated, the light client can be fooled to trust a light block that was not generated by Tendermint consensus. @@ -93,28 +94,28 @@ mean by light client attacks (that are considered in this specification) and how they differ from main-chain forks. To this end, we start by defining some properties of the sequence of blocks that is decided upon by Tendermint consensus in normal operation (if the -Tendermint failure model holds -[[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link]), +Cosmos failure model holds +[[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link]), and then define different deviations that correspond to attack scenarios. We consider the notion of [light blocks][LCV-LB-link] and [headers][LVC-HD-link]. -#### **[TMBC-GENESIS.1]** +#### **[CMBC-GENESIS.1]** Let *Genesis* be the agreed-upon initial block (file). -#### **[TMBC-FUNC-SIGN.1]** +#### **[CMBC-FUNC-SIGN.1]** Let *b* and *c* be two light blocks with *b.Header.Height + 1 = c.Header.Height*. We define the predicate **signs(b,c)** to hold iff *c.Header.LastCommit* is in *PossibleCommit(b)*. -[[TMBC-SOUND-DISTR-POSS-COMMIT.1]][TMBC-SOUND-DISTR-POSS-COMMIT-link]. +[[CMBC-SOUND-DISTR-POSS-COMMIT.1]][CMBC-SOUND-DISTR-POSS-COMMIT-link]. > The above encodes sequential verification, that is, intuitively, > b.Header.NextValidators = c.Header.Validators and 2/3 of > these Validators signed c. -#### **[TMBC-FUNC-SUPPORT.1]** +#### **[CMBC-FUNC-SUPPORT.1]** Let *b* and *c* be two light blocks. We define the predicate **supports(b,c,t)** to hold iff @@ -123,13 +124,13 @@ Let *b* and *c* be two light blocks. We define the predicate - the voting power in *b.NextValidators* of nodes in *c.Commit* is more than 1/3 of *TotalVotingPower(b.Header.NextValidators)* -> That is, if the [Tendermint failure model][TMBC-FM-2THIRDS-link] +> That is, if the [Cosmos failure model][CMBC-FM-2THIRDS-link] > holds, then *c* has been signed by at least one correct full node, cf. -> [[TMBC-VAL-CONTAINS-CORR.1]][TMBC-VAL-CONTAINS-CORR-link]. +> [[CMBC-VAL-CONTAINS-CORR.1]][CMBC-VAL-CONTAINS-CORR-link]. > The following formalizes that *b* was properly generated by > Tendermint; *b* can be traced back to genesis. -#### **[TMBC-SEQ-ROOTED.1]** +#### **[CMBC-SEQ-ROOTED.1]** Let *b* be a light block. We define *sequ-rooted(b)* iff for all *i*, *1 <= i < h = b.Header.Height*, @@ -143,7 +144,7 @@ there exist light blocks *a(i)* s.t. > skipping verification. Observe that we do not require here (yet) > that *b* was properly generated. -#### **[TMBC-SKIP-TRACE.1]** +#### **[CMBC-SKIP-TRACE.1]** Let *b* and *c* be light blocks. We define *skip-trace(b,c,t)* if at time t there exists an integer *h* and a sequence *a(1)*, ... *a(h)* s.t. @@ -161,7 +162,7 @@ We call such a sequence *a(1)*, ... *a(h)* a **verification trace**. > maximum height of all blocks decided upon by Tendermint at this > moment. -#### **[TMBC-SIGN-SKIP-MATCH.1]** +#### **[CMBC-SIGN-SKIP-MATCH.1]** Let *a*, *b*, *c*, be light blocks and *t* a time, we define *sign-skip-match(a,b,c,t) = true* iff the following implication @@ -181,7 +182,7 @@ implies *b.Header = c.Header*. > *c* that both can be verified from a common block *a* from the > chain. Thus, the following describes an attack. -#### **[TMBC-ATTACK.1]** +#### **[CMBC-ATTACK.1]** If there exists three light blocks a, b, and c, with *sign-skip-match(a,b,c,t) = false* then we have an *attack*. We say @@ -192,7 +193,7 @@ we have **an attack at height** *b.Header.Height* and write > several blocks that satisfy the above requirement for the same > blocks *b* and *c*. -[[TMBC-ATTACK.1]](#TMBC-ATTACK1) is a formalization of the violation +[[CMBC-ATTACK.1]](#CMBC-ATTACK1) is a formalization of the violation of the agreement property based on the result of consensus, that is, the generated blocks. @@ -211,7 +212,7 @@ discussion we also provide a ## Node-based characterization of attacks -#### **[TMBC-MC-FORK.1]** +#### **[CMBC-MC-FORK.1]** We say there is a (main chain) fork at time *t* if @@ -221,11 +222,11 @@ We say there is a (main chain) fork at time *t* if - *j* has decided on *c* and - there exist *a* such that *attack(a,b,c,t)*. -#### **[TMBC-LC-ATTACK.1]** +#### **[CMBC-LC-ATTACK.1]** We say there is a light client attack at time *t*, if -- there is **no** (main chain) fork [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1), and +- there is **no** (main chain) fork [[CMBC-MC-FORK.1]](#CMBC-MC-FORK1), and - there exist nodes that have computed light blocks *b* and *c* and - there exist *a* such that *attack(a,b,c,t)*. @@ -238,14 +239,14 @@ We say the attack is at height *a.Header.Height*. > convince > the light client that *c* is the block from the chain. -#### **[TMBC-LC-ATTACK-EVIDENCE.1]** +#### **[CMBC-LC-ATTACK-EVIDENCE.1]** We consider the following case of a light client attack -[[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1): +[[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1): - *attack(a,b,c,t)* - there is a peer p1 that has a sequence *chain* of blocks from *a* to *b* -- *skip-trace(a,c,t)*: by [[TMBC-SKIP-TRACE.1]](#TMBC-SKIP-TRACE1) there is a +- *skip-trace(a,c,t)*: by [[CMBC-SKIP-TRACE.1]](#CMBC-SKIP-TRACE1) there is a verification trace *v* of the form *a = v(1)*, ... *v(h) = c* Evidence for p1 (that proves an attack to p1) consists for index i @@ -260,7 +261,7 @@ of v(i) and v(i+1) such that > - check that v(i+1) differs from its block at that height, and > - verify v(i+1) in one step from v(i) as v is a verification trace. -#### **[TMBC-LC-EVIDENCE-DATA.1]** +#### **[CMBC-LC-EVIDENCE-DATA.1]** To prove the attack to p1, because of Point E1, it is sufficient to submit @@ -280,7 +281,7 @@ operational. > They might be relevant for a closer analysis of fork scenarios on the > chain, which is out of the scope of this specification. -#### **[TMBC-SIGN-UNIQUE.1]** +#### **[CMBC-SIGN-UNIQUE.1]** Let *b* and *c* be light blocks, we define the predicate *sign-unique(b,c)* to evaluate to true iff the following implication @@ -292,24 +293,24 @@ evaluates to true: implies *b = c*. -#### **[TMBC-BLOCKS-MCFORK.1]** +#### **[CMBC-BLOCKS-MCFORK.1]** If there exists two light blocks b and c, with *sign-unique(b,c) = false* then we have a *fork*. > The difference of the above definition to -> [[TMBC-MC-FORK.1]](#TMBC-MC-FORK1) is subtle. The latter requires a +> [[CMBC-MC-FORK.1]](#CMBC-MC-FORK1) is subtle. The latter requires a > full node being affected by a bad block while -> [[TMBC-BLOCKS-MCFORK.1]](#TMBC-BLOCKS-MCFORK1) just requires that a +> [[CMBC-BLOCKS-MCFORK.1]](#CMBC-BLOCKS-MCFORK1) just requires that a > bad block exists, possibly in memory of an attacker. > The following captures a light client fork. There is no fork up to > the height of block b. However, c is of that height, is different, > and passes skipping verification. It is a stricter property than -> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1), as -> [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) requires that no correct full +> [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1), as +> [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1) requires that no correct full > node is affected. -#### **[TMBC-BLOCKS-LCFORK.1]** +#### **[CMBC-BLOCKS-LCFORK.1]** Let *a*, *b*, *c*, be light blocks and *t* a time. We define *light-client-fork(a,b,c,t)* iff @@ -325,7 +326,7 @@ Let *a*, *b*, *c*, be light blocks and *t* a time. We define > *a.height < b.height* (which is implied by the definitions which > unfold until *supports()*). -#### **[TMBC-BOGUS.1]** +#### **[CMBC-BOGUS.1]** Let *b* be a light block and *t* a time. We define *bogus(b,t)* iff @@ -350,13 +351,13 @@ which use the light client protocols to observe the state of a blockchain). In order to provide full nodes the incentive to follow the protocols when communicating with the light client, this specification also considers the generation of evidence that will -also be processed by the Tendermint blockchain. +also be processed by the Cosmos blockchain. #### **[LCD-IP-MODEL.1]** The detector is designed under the assumption that -- [[TMBC-FM-2THIRDS]][TMBC-FM-2THIRDS-link] may be violated +- [[CMBC-FM-2THIRDS]][CMBC-FM-2THIRDS-link] may be violated - there is no fork on the main chain. > As a result some faulty full nodes may launch an attack on a light @@ -448,7 +449,7 @@ before the timeout expires. ### Evidence Following the definition of -[[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1), by evidence +[[CMBC-LC-ATTACK-EVIDENCE.1]](#CMBC-LC-ATTACK-EVIDENCE1), by evidence we refer to a variable of the following type #### **[LC-DATA-EVIDENCE.1]** @@ -508,8 +509,8 @@ been verified before, and that were provided by the primary. #### **[LCD-DIST-INV-ATTACK.1]** If the detector returns evidence for height *h* -[[TMBC-LC-EVIDENCE-DATA.1]](#TMBC-LC-EVIDENCE-DATA1), then there is an -attack at height *h*. [[TMBC-LC-ATTACK.1]](#TMBC-LC-ATTACK1) +[[CMBC-LC-EVIDENCE-DATA.1]](#CMBC-LC-EVIDENCE-DATA1), then there is an +attack at height *h*. [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1) #### **[LCD-DIST-INV-STORE.1]** @@ -739,7 +740,7 @@ func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore) #### On the existence of evidence **Proposition.** In the case of attack, -evidence [[TMBC-LC-ATTACK-EVIDENCE.1]](#TMBC-LC-ATTACK-EVIDENCE1) +evidence [[CMBC-LC-ATTACK-EVIDENCE.1]](#CMBC-LC-ATTACK-EVIDENCE1) exists. *Proof.* First observe that @@ -802,38 +803,37 @@ Once a bogus block is recognized as such the secondary is removed. [[supervisor]] The specification of the light client supervisor. -[verification]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md +[verification]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md -[supervisor]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md +[supervisor]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/supervisor/supervisor_001_draft.md -[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md -[TMBC-FM-2THIRDS-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-fm-2thirds1 +[CMBC-FM-2THIRDS-link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-fm-2thirds1 -[TMBC-SOUND-DISTR-POSS-COMMIT-link]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-sound-distr-poss-commit1 +[CMBC-SOUND-DISTR-POSS-COMMIT-link]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-sound-distr-poss-commit1 -[LCV-SEQ-SAFE-link]:https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-seq-safe1 +[LCV-SEQ-SAFE-link]:https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-seq-safe1 -[TMBC-VAL-CONTAINS-CORR-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-val-contains-corr1 +[CMBC-VAL-CONTAINS-CORR-link]: +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-val-contains-corr1 [fetch]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-fetch1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-func-fetch1 [LCV-INV-TP1-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-inv-tp1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-inv-tp1 [LCV-LB-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightblock1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-data-lightblock1 [LCV-LS-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-data-lightstore2 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-data-lightstore2 [LVC-HD-link]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#tmbc-header-fields2 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#cmbc-header-fields2 [repl]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/supervisor/supervisor_001_draft.md#lc-func-replace-secondary1 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/supervisor/supervisor_001_draft.md#lc-func-replace-secondary1 [vtt]: -https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/verification/verification_002_draft.md#lcv-func-main2 +https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_002_draft.md#lcv-func-main2 diff --git a/spec/light-client/detection/discussions.md b/spec/light-client/detection/discussions.md index 82702dd69d..30d892e921 100644 --- a/spec/light-client/detection/discussions.md +++ b/spec/light-client/detection/discussions.md @@ -43,7 +43,7 @@ - Observe that just two blocks for the same height are not sufficient proof of fork. -One of the blocks may be bogus [TMBC-BOGUS.1] which does +One of the blocks may be bogus [CMBC-BOGUS.1] which does not constitute slashable behavior. Which leads to the question whether the light node should try to do fork detection on its initial block (from subjective diff --git a/spec/light-client/detection/draft-functions.md b/spec/light-client/detection/draft-functions.md index c56594a533..f983fded12 100644 --- a/spec/light-client/detection/draft-functions.md +++ b/spec/light-client/detection/draft-functions.md @@ -3,7 +3,7 @@ This document collects drafts of function for generating and submitting proof of fork in the IBC context -- [IBC](#on---chain-ibc-component) +- [IBC](#on-chain-ibc-component) - [Relayer](#relayer) @@ -14,7 +14,7 @@ submitting proof of fork in the IBC context #### [TAG-IBC-MISBEHAVIOR.1] ```go -func checkMisbehaviourAndUpdateState(cs: ClientState, PoF: LightNodeProofOfFork) +func checkMisbehaviorAndUpdateState(cs: ClientState, PoF: LightNodeProofOfFork) ``` **TODO:** finish conditions @@ -26,7 +26,7 @@ func checkMisbehaviourAndUpdateState(cs: ClientState, PoF: LightNodeProofOfFork) - both traces end with header of same height - headers are different - both traces are supported by PoF.TrustedBlock (`supports` - defined in [TMBC-FUNC]), that is, for `t = currentTimestamp()` (see + defined in [CMBC-FUNC]), that is, for `t = currentTimestamp()` (see ICS 024) - supports(PoF.TrustedBlock, PoF.PrimaryTrace[1], t) - supports(PoF.PrimaryTrace[i], PoF.PrimaryTrace[i+1], t) for @@ -113,7 +113,7 @@ func SubmitIBCProofOfFork( ibc IBCComponent) (Error) { if ibc.queryChainConsensusState(PoF.TrustedBlock.Height) = PoF.TrustedBlock { // IBC component has root of PoF on store, we can just submit - ibc.submitMisbehaviourToClient(ibc.id,PoF) + ibc.submitMisbehaviorToClient(ibc.id,PoF) return Success // note sure about the id parameter } @@ -127,7 +127,7 @@ func SubmitIBCProofOfFork( if result = Success { newPoF = extendPoF(ibcLightBlock, lblock, lightStore, PoF) - ibc.submitMisbehaviourToClient(ibc.id, newPoF) + ibc.submitMisbehaviorToClient(ibc.id, newPoF) return Success } else{ diff --git a/spec/light-client/detection/req-ibc-detection.md b/spec/light-client/detection/req-ibc-detection.md index 439ca26b64..ceffef1f84 100644 --- a/spec/light-client/detection/req-ibc-detection.md +++ b/spec/light-client/detection/req-ibc-detection.md @@ -4,17 +4,17 @@ In the following, I distilled what I considered relevant from - + ### Components and their interface -#### Tendermint Blockchains +#### Cosmos Blockchains > I assume you know what that is. -#### An IBC/Tendermint correspondence +#### An IBC/Cosmos correspondence -| IBC Term | Tendermint-RS Spec Term | Comment | +| IBC Term | Cosmos Spec Term | Comment | |----------|-------------------------| --------| | `CommitmentRoot` | AppState | app hash | | `ConsensusState` | Lightblock | not all fields are there. NextValidator is definitly needed | @@ -24,7 +24,7 @@ In the following, I distilled what I considered relevant from | `Height` | (no epochs) | (epoch,height) pair in lexicographical order (`compare`) | | `Header` | ~signed header | validatorSet explicit (no hash); nextValidators missing | | `Evidence` | t.b.d. | definition unclear "which the light client would have considered valid". Data structure will need to change | -| `verify` | `ValidAndVerified` | signature does not match perfectly (ClientState vs. LightBlock) + in `checkMisbehaviourAndUpdateState` it is unclear whether it uses traces or goes to h1 and h2 in one step | +| `verify` | `ValidAndVerified` | signature does not match perfectly (ClientState vs. LightBlock) + in `checkMisbehaviorAndUpdateState` it is unclear whether it uses traces or goes to h1 and h2 in one step | #### Some IBC links @@ -32,14 +32,14 @@ In the following, I distilled what I considered relevant from #### Required Changes in ICS 007 -- `assert(height > 0)` in definition of `initialise` doesn't match +- `assert(height > 0)` in definition of `initialize` doesn't match definition of `Height` as *(epoch,height)* pair. -- `initialise` needs to be updated to new data structures +- `initialize` needs to be updated to new data structures - `clientState.frozenHeight` semantics seem not totally consistent in document. E.g., `min` needs to be defined over optional value in - `checkMisbehaviourAndUpdateState`. Also, if you are frozen, why do + `checkMisbehaviorAndUpdateState`. Also, if you are frozen, why do you accept more evidence. - `checkValidityAndUpdateState` @@ -53,13 +53,13 @@ In the following, I distilled what I considered relevant from - clienstState needs to be updated according to complete data structure -- `checkMisbehaviourAndUpdateState`: as evidence will contain a trace +- `checkMisbehaviorAndUpdateState`: as evidence will contain a trace (or two), the assertion that uses verify will need to change. - ICS 002 states w.r.t. `queryChainConsensusState` that "Note that retrieval of past consensus states by height (as opposed to just the current consensus state) is convenient but not required." For - Tendermint fork detection, this seems to be a necessity. + Cosmos fork detection, this seems to be a necessity. - `Header` should become a lightblock @@ -68,7 +68,7 @@ In the following, I distilled what I considered relevant from - `upgradeClientState` what is the semantics (in particular what is `height` doing?). -- `checkMisbehaviourAndUpdateState(cs: ClientState, PoF: +- `checkMisbehaviorAndUpdateState(cs: ClientState, PoF: LightNodeProofOfFork)` needs to be adapted #### Handler @@ -86,7 +86,7 @@ A blockchain runs a **handler** that passively collects information about type checkValidityAndUpdateState = (Header) => Void ``` - For Tendermint, it will perform + For Cosmos, it will perform `ValidandVerified`, that is, it does the trusting period check and the +1/3 check (+2/3 for sequential headers). If it verifies a header, it adds it to its lightstore, @@ -104,7 +104,7 @@ type checkValidityAndUpdateState = (Header) => Void will need to make precise eventually) to a handler ```go -type checkMisbehaviourAndUpdateState = (bytes) => Void +type checkMisbehaviorAndUpdateState = (bytes) => Void ``` We have to design this, and the data that the handler can use to @@ -126,7 +126,7 @@ type queryChainConsensusState = (height: uint64) => ConsensusState - the relayer send headers and data to the handler to invoke `checkValidityAndUpdateState` and - `checkMisbehaviourAndUpdateState`. It may also query + `checkMisbehaviorAndUpdateState`. It may also query `queryChainConsensusState`. - multiple relayers may talk to one handler. Some relayers might be @@ -187,7 +187,7 @@ relayer can figure that out: headers (tagged as verified). - eventually, a handler should be informed - (`checkMisbehaviourAndUpdateState`) + (`checkMisbehaviorAndUpdateState`) by some relayer that it has verified a header from a fork. Then the handler should do what is required by IBC in this case (stop?) @@ -254,7 +254,7 @@ validators of some smaller height. In principle everyone can detect a fork -- ./detection talks about the Tendermint light client with a focus on +- ./detection talks about the Cosmos light client with a focus on light nodes. A relayer runs such light clients and may detect forks in this way diff --git a/spec/light-client/supervisor/supervisor_001_draft.md b/spec/light-client/supervisor/supervisor_001_draft.md index eb0dd190b9..c8bbe8916c 100644 --- a/spec/light-client/supervisor/supervisor_001_draft.md +++ b/spec/light-client/supervisor/supervisor_001_draft.md @@ -7,17 +7,17 @@ verification specification. So some hyperlinks have to be placed to the correct files eventually. # Light Client Sequential Supervisor - + The light client implements a read operation of a -[header](TMBC-HEADER-link) from the [blockchain](TMBC-SEQ-link), by +[header](CMBC-HEADER-link) from the [blockchain](CMBC-SEQ-link), by communicating with full nodes, a so-called primary and several so-called witnesses. As some full nodes may be faulty, this functionality must be implemented in a fault-tolerant way. -In the Tendermint blockchain, the validator set may change with every +In a Cosmos blockchain, the validator set may change with every new block. The staking and unbonding mechanism induces a [security -model](TMBC-FM-2THIRDS-link): starting at time *Time* of the -[header](TMBC-HEADER-link), +model](CMBC-FM-2THIRDS-link): starting at time *Time* of the +[header](CMBC-HEADER-link), more than two-thirds of the next validators of a new block are correct for the duration of *TrustedPeriod*. @@ -26,9 +26,9 @@ operation designed for this security model. That is, it is safe if the model assumptions are satisfied and makes progress if it communicates to a correct primary. -However, if the [security model](TMBC-FM-2THIRDS-link) is violated, +However, if the [security model](CMBC-FM-2THIRDS-link) is violated, faulty peers (that have been validators at some point in the past) may -launch attacks on the Tendermint network, and on the light +launch attacks on the Cosmos network, and on the light client. These attacks as well as an axiomatization of blocks in general are defined in [a document that contains the definitions that are currently in detection.md](https://informal.systems). @@ -79,7 +79,7 @@ it assumes certain details of [verification](https://informal.systems) and versions yet. This inconsistencies will be addresses over several upcoming PRs. -# Part I - Tendermint Blockchain +# Part I - Cosmos Blockchain See [verification spec](addLinksWhenDone) @@ -306,8 +306,8 @@ type LCInitData struct { ``` where only one of the components must be provided. `GenesisDoc` is -defined in the [Tendermint -Types](https://github.com/tendermint/tendermint/blob/v0.34.x/types/genesis.go). +defined in the [CometBFT +Types](https://github.com/cometbft/cometbft/blob/v0.34.x/types/genesis.go). #### **[LC-DATA-GENESIS.1]** @@ -532,7 +532,7 @@ func InitLightClient (initData LCInitData) (LightStore, Error) { - none - Expected precondition - *LCInitData* contains either a genesis file of a lightblock - - if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems) + - if genesis it passes `ValidateAndComplete()` see [CometBFT](https://informal.systems) - Expected postcondition - *lightStore* initialized with trusted lightblock. It has either been cross-checked (from genesis) or it has initial trust from the diff --git a/spec/light-client/supervisor/supervisor_002_draft.md b/spec/light-client/supervisor/supervisor_002_draft.md index 9286560e79..b9ee84136f 100644 --- a/spec/light-client/supervisor/supervisor_002_draft.md +++ b/spec/light-client/supervisor/supervisor_002_draft.md @@ -16,8 +16,8 @@ type LCInitData struct { ``` where only one of the components must be provided. `GenesisDoc` is -defined in the [Tendermint -Types](https://github.com/tendermint/tendermint/blob/v0.34.x/types/genesis.go). +defined in the [CometBFT +Types](https://github.com/cometbft/cometbft/blob/v0.34.x/types/genesis.go). ### Initialization @@ -25,7 +25,7 @@ Types](https://github.com/tendermint/tendermint/blob/v0.34.x/types/genesis.go). The light client is based on subjective initialization. It has to trust the initial data given to it by the user. It cannot perform any detection of an attack yet instead requires an initial point of trust. -There are three forms of initial data which are used to obtain the +There are three forms of initial data which are used to obtain the first trusted block: - A trusted block from a prior initialization @@ -45,8 +45,8 @@ able to verify anything. Cross-checking this trusted block with providers upon initialization is helpful for ensuring that the node is responsive and correctly configured but does not increase trust since proving a conflicting block is a -[light client attack](https://github.com/tendermint/tendermint/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#tmbc-lc-attack1) -and not just a [bogus](https://github.com/tendermint/tendermint/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#tmbc-bogus1) block could result in +[light client attack](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#cmbc-lc-attack1) +and not just a [bogus](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_003_reviewed.md#cmbc-bogus1) block could result in performing backwards verification beyond the trusted period, thus a fruitless endeavour. @@ -118,7 +118,7 @@ func CrossCheck(lb LightBlock, witnesses []Provider) error { - none - Expected precondition - *LCInitData* contains either a genesis file of a lightblock - - if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems) + - if genesis it passes `ValidateAndComplete()` see [CometBFT](https://informal.systems) - Expected postcondition - *lightStore* initialized with trusted lightblock. It has either been cross-checked (from genesis) or it has initial trust from the diff --git a/spec/light-client/verification/Blockchain_002_draft.tla b/spec/light-client/verification/Blockchain_002_draft.tla index f2ca5aba5a..df22b346dc 100644 --- a/spec/light-client/verification/Blockchain_002_draft.tla +++ b/spec/light-client/verification/Blockchain_002_draft.tla @@ -1,6 +1,6 @@ ------------------------ MODULE Blockchain_002_draft ----------------------------- (* - This is a high-level specification of Tendermint blockchain + This is a high-level specification of a Cosmos blockchain that is designed specifically for the light client. Validators have the voting power of one. If you like to model various voting powers, introduce multiple copies of the same validator @@ -101,7 +101,7 @@ IsCorrectPower(pFaultyNodes, pVS) == \* CP + FP = TP is the total voting power, so we write CP > 2.0 / 3 * TP as follows: CP > 2 * FP \* Note: when FP = 0, this implies CP > 0. -(* This is what we believe is the assumption about failures in Tendermint *) +(* This is what we believe is the assumption about failures in Cosmos *) FaultAssumption(pFaultyNodes, pNow, pBlockchain) == \A h \in Heights: pBlockchain[h].time + TRUSTING_PERIOD > pNow => @@ -143,7 +143,7 @@ InitToHeight == ] \****** -(* is the blockchain in the faulty zone where the Tendermint security model does not apply *) +(* is the blockchain in the faulty zone where the Cosmos security model does not apply *) InFaultyZone == ~FaultAssumption(Faulty, now, blockchain) diff --git a/spec/light-client/verification/Blockchain_003_draft.tla b/spec/light-client/verification/Blockchain_003_draft.tla index 2b37c1b181..c57bc46938 100644 --- a/spec/light-client/verification/Blockchain_003_draft.tla +++ b/spec/light-client/verification/Blockchain_003_draft.tla @@ -1,6 +1,6 @@ ------------------------ MODULE Blockchain_003_draft ----------------------------- (* - This is a high-level specification of Tendermint blockchain + This is a high-level specification of a Cosmos blockchain that is designed specifically for the light client. Validators have the voting power of one. If you like to model various voting powers, introduce multiple copies of the same validator diff --git a/spec/light-client/verification/Blockchain_A_1.tla b/spec/light-client/verification/Blockchain_A_1.tla index 70f59bf975..cf8f45967b 100644 --- a/spec/light-client/verification/Blockchain_A_1.tla +++ b/spec/light-client/verification/Blockchain_A_1.tla @@ -1,6 +1,6 @@ ------------------------ MODULE Blockchain_A_1 ----------------------------- (* - This is a high-level specification of Tendermint blockchain + This is a high-level specification of a Cosmos blockchain that is designed specifically for the light client. Validators have the voting power of one. If you like to model various voting powers, introduce multiple copies of the same validator @@ -101,7 +101,7 @@ IsCorrectPower(pFaultyNodes, pVS) == \* CP + FP = TP is the total voting power, so we write CP > 2.0 / 3 * TP as follows: CP > 2 * FP \* Note: when FP = 0, this implies CP > 0. -(* This is what we believe is the assumption about failures in Tendermint *) +(* This is what we believe is the assumption about failures in Cosmos *) FaultAssumption(pFaultyNodes, pNow, pBlockchain) == \A h \in Heights: pBlockchain[h].time + TRUSTING_PERIOD > pNow => @@ -143,7 +143,7 @@ InitToHeight == ] \****** -(* is the blockchain in the faulty zone where the Tendermint security model does not apply *) +(* is the blockchain in the faulty zone where the Cosmos security model does not apply *) InFaultyZone == ~FaultAssumption(Faulty, now, blockchain) diff --git a/spec/light-client/verification/LCVerificationApi_003_draft.tla b/spec/light-client/verification/LCVerificationApi_003_draft.tla index 909eab92b8..8ab5e8259c 100644 --- a/spec/light-client/verification/LCVerificationApi_003_draft.tla +++ b/spec/light-client/verification/LCVerificationApi_003_draft.tla @@ -15,7 +15,7 @@ CONSTANTS be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) FAULTY_RATIO (* a pair <> that limits that ratio of faulty validator in the blockchain - from above (exclusive). Tendermint security model prescribes 1 / 3. *) + from above (exclusive). Cosmos security model prescribes 1 / 3. *) VARIABLES localClock (* current time as measured by the light client *) diff --git a/spec/light-client/verification/Lightclient_003_draft.tla b/spec/light-client/verification/Lightclient_003_draft.tla index e17a88491b..158146eb1c 100644 --- a/spec/light-client/verification/Lightclient_003_draft.tla +++ b/spec/light-client/verification/Lightclient_003_draft.tla @@ -25,7 +25,7 @@ CONSTANTS (* is primary correct? *) FAULTY_RATIO (* a pair <> that limits that ratio of faulty validator in the blockchain - from above (exclusive). Tendermint security model prescribes 1 / 3. *) + from above (exclusive). Cosmos security model prescribes 1 / 3. *) VARIABLES (* see TypeOK below for the variable types *) localClock, (* the local clock of the light client *) diff --git a/spec/light-client/verification/Lightclient_A_1.tla b/spec/light-client/verification/Lightclient_A_1.tla index 70e6caf002..6274632d01 100644 --- a/spec/light-client/verification/Lightclient_A_1.tla +++ b/spec/light-client/verification/Lightclient_A_1.tla @@ -1,4 +1,5 @@ -------------------------- MODULE Lightclient_A_1 ---------------------------- + (** * A state-machine specification of the lite client, following the English spec: * diff --git a/spec/light-client/verification/README.md b/spec/light-client/verification/README.md index 8777374ac9..57e3cc7451 100644 --- a/spec/light-client/verification/README.md +++ b/spec/light-client/verification/README.md @@ -27,7 +27,7 @@ Given a known bound `TRUSTED_PERIOD`, and a block `b` with header `h` generated in `validators(b.Header.NextValidatorsHash)` is correct until time `b.Header.Time + TRUSTED_PERIOD`. *Assumption*: "correct" is defined w.r.t. realtime (some Newtonian global notion of time, i.e., wall time), -while `Header.Time` corresponds to the [BFT time](../consensus/bft-time.md). In this note, we assume that clocks of correct processes +while `Header.Time` corresponds to the [BFT time](../../consensus/bft-time.md). In this note, we assume that clocks of correct processes are synchronized (for example using NTP), and therefore there is bounded clock drift (`CLOCK_DRIFT`) between local clocks and BFT time. More precisely, for every correct light client process and every `header.Time` (i.e. BFT Time, for a header correctly generated by the Tendermint consensus), the following inequality holds: `Header.Time < now + CLOCK_DRIFT`, @@ -47,7 +47,7 @@ Mechanisms like `fork accountability` and `evidence submission` are defined in t they incentivize validators to follow the protocol specification defined in this document. If they don't, and we have 1/3 (or more) faulty validators, safety may be violated. Our approach then is to *detect* these cases (after the fact), and take suitable repair actions (automatic and social). -This is discussed in document on [Fork accountability](./accountability.md). +This is discussed in document on [Fork accountability](../../consensus/light-client/accountability.md). The term "trusted" above indicates that the correctness of the protocol depends on this assumption. It is in the responsibility of the user that runs the light client to make sure that the risk @@ -113,8 +113,8 @@ In the following, only the details of the data structures needed for this specif ### Functions -For the purpose of this light client specification, we assume that the Tendermint Full Node -exposes the following functions over Tendermint RPC: +For the purpose of this light client specification, we assume that the Cosmos Full Node +exposes the following functions over RPC: ```go // returns signed header: Header with Commit, for the given height @@ -323,8 +323,8 @@ from `trustedState` to `newTrustedState` happened during the trusted period of In case `VerifyHeaderAtHeight` returns with an error, then either (i) the full node we are talking to is faulty or (ii) the trusted header has expired (it is outside its trusted period). In case (i) the full node is faulty so -light client should disconnect and reinitialise with new peer. In the case (ii) as the trusted header has expired, -we need to reinitialise light client with a new trusted header (that is within its trusted period), +light client should disconnect and reinitialize with new peer. In the case (ii) as the trusted header has expired, +we need to reinitialize light client with a new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node we are talking to (as we haven't observed full node misbehavior in this case). **VerifyBisection.** The function `VerifyBisection` implements @@ -459,10 +459,10 @@ We consider the following set-up: - the light client communicates with one full node - the light client locally stores all the headers that has passed basic verification and that are within light client trust period. In the pseudo code below we write *Store.Add(header)* for this. If a header failed to verify, then -the full node we are talking to is faulty and we should disconnect from it and reinitialise with new peer. +the full node we are talking to is faulty and we should disconnect from it and reinitialize with new peer. - If `CanTrust` returns *error*, then the light client has seen a forged header or the trusted header has expired (it is outside its trusted period). - - In case of forged header, the full node is faulty so light client should disconnect and reinitialise with new peer. If the trusted header has expired, - we need to reinitialise light client with new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node + - In case of forged header, the full node is faulty so light client should disconnect and reinitialize with new peer. If the trusted header has expired, + we need to reinitialize light client with new trusted header (that is within its trusted period), but we don't necessarily need to disconnect from the full node we are talking to (as we haven't observed full node misbehavior in this case). ## Correctness of the Light Client Protocols @@ -545,7 +545,7 @@ Light Client Completeness: - The check is successful if sufficiently many validators of `trustedState` are still validators in the height `untrustedHeader.Height` and signed `untrustedHeader`. - If `untrustedHeader.Height = trustedHeader.Height + 1`, and both headers were generated correctly, the test passes. -*Verification Condition:* We may need a Tendermint invariant stating that if `untrustedSignedHeader.Header.Height = trustedHeader.Height + 1` then +*Verification Condition:* We may need an invariant stating that if `untrustedSignedHeader.Header.Height = trustedHeader.Height + 1` then `signers(untrustedSignedHeader.Commit) \subseteq validators(trustedHeader.NextValidatorsHash)`. *Remark*: The variable `trustThreshold` can be used if the user believes that relying on one correct validator is not sufficient. diff --git a/spec/light-client/verification/verification_001_published.md b/spec/light-client/verification/verification_001_published.md index 59aa0ff206..b650e3e718 100644 --- a/spec/light-client/verification/verification_001_published.md +++ b/spec/light-client/verification/verification_001_published.md @@ -1,14 +1,14 @@ # Light Client Verification The light client implements a read operation of a -[header][TMBC-HEADER-link] from the [blockchain][TMBC-SEQ-link], by +[header][#cmbc-header1] from the [blockchain][cmbc-seq1], by communicating with full nodes. As some full nodes may be faulty, this functionality must be implemented in a fault-tolerant way. -In the Tendermint blockchain, the validator set may change with every +In a Cosmos blockchain, the validator set may change with every new block. The staking and unbonding mechanism induces a [security -model][TMBC-FM-2THIRDS-link]: starting at time *Time* of the -[header][TMBC-HEADER-link], +model][CMBC-FM-2THIRDS-link]: starting at time *Time* of the +[header][#cmbc-header1], more than two-thirds of the next validators of a new block are correct for the duration of *TrustedPeriod*. The fault-tolerant read operation is designed for this security model. @@ -19,7 +19,7 @@ greater than *h1*. Checking all headers of heights from *h1* to *h2* might be too costly (e.g., in terms of energy for mobile devices). This specification tries to reduce the number of intermediate blocks that need to be checked, by exploiting the guarantees provided by the -[security model][TMBC-FM-2THIRDS-link]. +[security model][cmbc-fm-2thirds1]. # Status @@ -39,17 +39,17 @@ in computing more efficiently the proof of fork). # Outline -- [Part I](#part-i---tendermint-blockchain): Introduction of - relevant terms of the Tendermint +- [Part I](#part-i---cosmos-blockchain): Introduction of + relevant terms of the Cosmos blockchain. - [Part II](#part-ii---sequential-definition-of-the-verification-problem): Introduction of the problem addressed by the Lightclient Verification protocol. - [Verification Informal Problem - statement](#Verification-Informal-Problem-statement): For the general + statement](#verification-informal-problem-statement): For the general audience, that is, engineers who want to get an overview over what the component is doing from a bird's eye view. - - [Sequential Problem statement](#Sequential-Problem-statement): + - [Sequential Problem statement](#sequential-problem-statement): Provides a mathematical definition of the problem statement in its sequential form, that is, ignoring the distributed aspect of the implementation of the blockchain. @@ -61,17 +61,17 @@ of the problem addressed by the Lightclient Verification protocol. - [Incentives](#incentives): how faulty full nodes may benefit from misbehaving and how correct full nodes benefit from cooperating. - - [Computational Model](#Computational-Model): + - [Computational Model](#computational-model): timing and correctness assumptions. - - [Distributed Problem Statement](#Distributed-Problem-Statement): + - [Distributed Problem Statement](#distributed-problem-statement): temporal properties that formalize safety and liveness properties in the distributed setting. - [Part IV](#part-iv---light-client-verification-protocol): Specification of the protocols. - - [Definitions](#Definitions): Describes inputs, outputs, + - [Definitions](#definitions): Describes inputs, outputs, variables used by the protocol, auxiliary functions - [Core Verification](#core-verification): gives an outline of the solution, @@ -98,7 +98,7 @@ In this document we quite extensively use tags in order to be able to reference assumptions, invariants, etc. in future communication. In these tags we frequently use the following short forms: -- TMBC: Tendermint blockchain +- CMBC: Cosmos blockchain - SEQ: for sequential specifications - LCV: Lightclient Verification - LIVE: liveness @@ -107,11 +107,11 @@ these tags we frequently use the following short forms: - INV: invariant - A: assumption -# Part I - Tendermint Blockchain +# Part I - Cosmos Blockchain ## Header Fields necessary for the Light Client -#### **[TMBC-HEADER.1]** +#### **[CMBC-HEADER.1]** A set of blockchain transactions is stored in a data structure called *block*, which contains a field called *header*. (The data structure @@ -120,13 +120,13 @@ the relevant fields of the block, for the purpose of this specification, we will assume that the blockchain is a list of headers, rather than a list of blocks. -#### **[TMBC-HASH-UNIQUENESS.1]** +#### **[CMBC-HASH-UNIQUENESS.1]** We assume that every hash in the header identifies the data it hashes. Therefore, in this specification, we do not distinguish between hashes and the data they represent. -#### **[TMBC-HEADER-FIELDS.1]** +#### **[CMBC-HEADER-FIELDS.1]** A header contains the following fields: @@ -140,11 +140,11 @@ A header contains the following fields: - `AppState`: DomainApp - `LastResults`: DomainRes -#### **[TMBC-SEQ.1]** +#### **[CMBC-SEQ.1]** -The Tendermint blockchain is a list *chain* of headers. +The Cosmos blockchain is a list *chain* of headers. -#### **[TMBC-VALIDATOR-PAIR.1]** +#### **[CMBC-VALIDATOR-PAIR.1]** Given a full node, a *validator pair* is a pair *(peerID, voting_power)*, where @@ -156,13 +156,13 @@ Given a full node, a > In the Golang implementation the data type for *validator pair* is called `Validator` -#### **[TMBC-VALIDATOR-SET.1]** +#### **[CMBC-VALIDATOR-SET.1]** A *validator set* is a set of validator pairs. For a validator set *vs*, we write *TotalVotingPower(vs)* for the sum of the voting powers of its validator pairs. -#### **[TMBC-VOTE.1]** +#### **[CMBC-VOTE.1]** A *vote* contains a `prevote` or `precommit` message sent and signed by a validator node during the execution of [consensus][arXiv]. Each @@ -173,13 +173,13 @@ message contains the following fields - `Round` a positive integer - `BlockID` a Hashvalue of a block (not necessarily a block of the chain) -#### **[TMBC-COMMIT.1]** +#### **[CMBC-COMMIT.1]** A commit is a set of `precommit` message. -## Tendermint Failure Model +## Cosmos Failure Model -#### **[TMBC-AUTH-BYZ.1]** +#### **[CMBC-AUTH-BYZ.1]** We assume the authenticated Byzantine fault model in which no node (faulty or correct) may break digital signatures, but otherwise, no additional @@ -187,46 +187,46 @@ assumption is made about the internal behavior of faulty nodes. That is, faulty nodes are only limited in that they cannot forge messages. -#### **[TMBC-TIME-PARAMS.1]** +#### **[CMBC-TIME-PARAMS.1]** -A Tendermint blockchain has the following configuration parameters: +A Cosmos blockchain has the following configuration parameters: - *unbondingPeriod*: a time duration. - *trustingPeriod*: a time duration smaller than *unbondingPeriod*. -#### **[TMBC-CORRECT.1]** +#### **[CMBC-CORRECT.1]** We define a predicate *correctUntil(n, t)*, where *n* is a node and *t* is a time point. The predicate *correctUntil(n, t)* is true if and only if the node *n* follows all the protocols (at least) until time *t*. -#### **[TMBC-FM-2THIRDS.1]** +#### **[CMBC-FM-2THIRDS.1]** If a block *h* is in the chain, then there exists a subset *CorrV* of *h.NextValidators*, such that: - *TotalVotingPower(CorrV) > 2/3 - TotalVotingPower(h.NextValidators)*; cf. [TMBC-VALIDATOR-SET.1] + TotalVotingPower(h.NextValidators)*; cf. [CMBC-VALIDATOR-SET.1] - For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n, - h.Time + trustingPeriod)*; cf. [TMBC-CORRECT.1] + h.Time + trustingPeriod)*; cf. [CMBC-CORRECT.1] > The definition of correct -> [**[TMBC-CORRECT.1]**][TMBC-CORRECT-link] refers to realtime, while it +> [**[CMBC-CORRECT.1]**][CMBC-CORRECT-link] refers to realtime, while it > is used here with *Time* and *trustingPeriod*, which are "hardware > times". We do not make a distinction here. -#### **[TMBC-CORR-FULL.1]** +#### **[CMBC-CORR-FULL.1]** Every correct full node locally stores a prefix of the -current list of headers from [**[TMBC-SEQ.1]**][TMBC-SEQ-link]. +current list of headers from [**[CMBC-SEQ.1]**][CMBC-SEQ-link]. ## What the Light Client Checks -> From [TMBC-FM-2THIRDS.1] we directly derive the following observation: +> From [CMBC-FM-2THIRDS.1] we directly derive the following observation: -#### **[TMBC-VAL-CONTAINS-CORR.1]** +#### **[CMBC-VAL-CONTAINS-CORR.1]** Given a (trusted) block *tb* of the blockchain, a given set of full nodes *N* contains a correct node at a real-time *t*, if @@ -238,11 +238,11 @@ Given a (trusted) block *tb* of the blockchain, a given set of full nodes > The following describes how a commit for a given block *b* must look > like. -#### **[TMBC-SOUND-DISTR-POSS-COMMIT.1]** +#### **[CMBC-SOUND-DISTR-POSS-COMMIT.1]** For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies: -- *pc* contains only votes (cf. [TMBC-VOTE.1]) +- *pc* contains only votes (cf. [CMBC-VOTE.1]) by validators from *b.Validators* - the sum of the voting powers in *pc* is greater than 2/3 *TotalVotingPower(b.Validators)* @@ -257,7 +257,7 @@ For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies: > `BlockID` of the new (to-be-decided) block is equal to the hash of > the last block. -#### **[TMBC-VAL-COMMIT.1]** +#### **[CMBC-VAL-COMMIT.1]** If for a block *b*, a commit *c* @@ -277,8 +277,8 @@ header it receives coincides with the one generated by Tendermint consensus. The two - properties [[TMBC-VAL-CONTAINS-CORR.1]][TMBC-VAL-CONTAINS-CORR-link] and -[[TMBC-VAL-COMMIT]][TMBC-VAL-COMMIT-link] formalize the checks done + properties [[CMBC-VAL-CONTAINS-CORR.1]][CMBC-VAL-CONTAINS-CORR-link] and +[[CMBC-VAL-COMMIT]][CMBC-VAL-COMMIT-link] formalize the checks done by this specification: Given a trusted block *tb* and an untrusted block *ub* with a commit *cub*, one has to check that *cub* is in *PossibleCommit(ub)*, and that *cub* @@ -290,7 +290,7 @@ contains a correct node using *tb*. Given a height *targetHeight* as an input, the *Verifier* eventually stores a header *h* of height *targetHeight* locally. This header *h* -is generated by the Tendermint [blockchain][block]. In +is generated by the Cosmos [blockchain][block]. In particular, a header that was not generated by the blockchain should never be stored. @@ -340,12 +340,12 @@ before the timeout expires. #### **[LCV-A-TFM.1]** -The Tendermint blockchain satisfies the Tendermint failure model [**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. +The Cosmos blockchain satisfies the Cosmos failure model [**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link]. #### **[LCV-A-VAL.1]** -The system satisfies [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] and -[**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. Thus, there is a +The system satisfies [**[CMBC-AUTH-BYZ.1]**][CMBC-Auth-Byz-link] and +[**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link]. Thus, there is a blockchain that satisfies the soundness requirements (that is, the validation rules in [[block]]). @@ -406,9 +406,9 @@ Each instance must eventually terminate. > These definitions imply that if the primary is faulty, a header may or > may not be added to *LightStore*. In any case, -> [**[LCV-DIST-SAFE.1]**](#lcv-vc-inv) must hold. -> The invariant [**[LCV-DIST-SAFE.1]**](#lcv-dist-safe) and the liveness -> requirement [**[LCV-DIST-LIVE.1]**](#lcv-dist-life) +> [**[LCV-DIST-SAFE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) must hold. +> The invariant [**[LCV-DIST-SAFE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) and the liveness +> requirement [**[LCV-DIST-LIVE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-life1) > allow that verified headers are added to *LightStore* whose > height was not passed > to the verifier (e.g., intermediate headers used in bisection; see below). @@ -425,16 +425,16 @@ Each instance must eventually terminate. This specification provides a partial solution to the sequential specification. The *Verifier* solves the invariant of the sequential part -[**[LCV-DIST-SAFE.1]**](#lcv-vc-inv) => [**[LCV-SEQ-SAFE.1]**](#lcv-seq-inv) +[**[LCV-DIST-SAFE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) => [**[LCV-SEQ-SAFE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-seq-inv) In the case the primary is correct, and there is a recent header in *LightStore*, the verifier satisfies the liveness requirements. ⋀ *primary is correct* ⋀ always ∃ verified header in LightStore. *header.Time* > *now* - *trustingPeriod* -⋀ [**[LCV-A-Comm.1]**](#lcv-a-comm) ⋀ ( - ( [**[TMBC-CorrFull.1]**][TMBC-CorrFull-link] ⋀ - [**[LCV-DIST-LIVE.1]**](#lcv-vc-live) ) - ⟹ [**[LCV-SEQ-LIVE.1]**](#lcv-seq-live) +⋀ [**[LCV-A-Comm.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-a-comm) ⋀ ( + ( [**[CMBC-CorrFull.1]**][CMBC-CorrFull-link] ⋀ + [**[LCV-DIST-LIVE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-live1) ) + ⟹ [**[LCV-SEQ-LIVE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-seq-live1) ) # Part IV - Light Client Verification Protocol @@ -570,7 +570,7 @@ func (ls LightStore) FilterVerified() LightSTore ### Configuration Parameters - *trustThreshold*: a float. Can be used if correctness should not be based on more voting power and 1/3. -- *trustingPeriod*: a time duration [**[TMBC-TIME_PARAMS.1]**][TMBC-TIME_PARAMS-link]. +- *trustingPeriod*: a time duration [**[CMBC-TIME_PARAMS.1]**][CMBC-TIME_PARAMS-link]. - *clockDrift*: a time duration. Correction parameter dealing with only approximately synchronized clocks. ### Variables @@ -600,7 +600,7 @@ It is always the case that *LightStore.LatestTrusted.Header.Time > now - trustin ### Used Remote Functions We use the functions `commit` and `validators` that are provided -by the [RPC client for Tendermint][RPC]. +by the [RPC client][RPC]. ```go func Commit(height int64) (SignedHeader, error) @@ -688,7 +688,7 @@ func FetchLightBlock(peer PeerID, height Height) LightBlock - *lb.Validators* is the validator set of the blockchain at height *nextHeight* - *lb.NextValidators* is the validator set of the blockchain at height *nextHeight + 1* - if *node* is faulty: Returns a LightBlock with arbitrary content - [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] + [**[CMBC-AUTH-BYZ.1]**][CMBC-Auth-Byz-link] - Error condition - if *n* is correct: precondition violated - if *n* is faulty: arbitrary error @@ -767,7 +767,7 @@ func VerifyToTarget(primary PeerID, lightStore LightStore, - Error conditions - if the precondition is violated - if `ValidAndVerified` or `FetchLightBlock` report an error - - if [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) is violated + - if [**[LCV-INV-TP.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-inv-tp1) is violated ### Details of the Functions @@ -806,14 +806,14 @@ func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result *max(1/3,trustThreshold)* of voting power in *trusted.Nextvalidators* is contained in *untrusted.Commit* (that is, header passes the tests - [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] - and [**[TMBC-VAL-COMMIT.1]**][TMBC-VAL-COMMIT-link]) + [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link] + and [**[CMBC-VAL-COMMIT.1]**][CMBC-VAL-COMMIT-link]) - Returns `NOT_ENOUGH_TRUST` if: - *untrusted* is *not* the immediate successor of *trusted* and the *max(1/3,trustThreshold)* threshold is not reached (that is, if - [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] + [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link] fails and header is does not violate the soundness checks [[block]]). - Error condition: @@ -854,16 +854,16 @@ func Schedule(lightStore, nextHeight, targetHeight) Height *trustedStore* is implemented by the light blocks in lightStore that have the state *StateVerified*. -#### Argument for [**[LCV-DIST-SAFE.1]**](#lcv-dist-safe) +#### Argument for [**[LCV-DIST-SAFE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe) - `ValidAndVerified` implements the soundness checks and the checks - [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] and - [**[TMBC-VAL-COMMIT.1]**][TMBC-VAL-COMMIT-link] under - the assumption [**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link] + [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link] and + [**[CMBC-VAL-COMMIT.1]**][CMBC-VAL-COMMIT-link] under + the assumption [**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link] - Only if `ValidAndVerified` returns with `SUCCESS`, the state of a light block is set to *StateVerified*. -#### Argument for [**[LCV-DIST-LIVE.1]**](#lcv-dist-life) +#### Argument for [**[LCV-DIST-LIVE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-life) - If *primary* is correct, - `FetchLightBlock` will always return a light block consistent @@ -871,7 +871,7 @@ have the state *StateVerified*. - `ValidAndVerified` either verifies the header using the trusting period or falls back to sequential verification - - If [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) holds, eventually every + - If [**[LCV-INV-TP.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-inv-tp1) holds, eventually every header will be verified and core verification **terminates successfully**. - successful termination depends on the age of *lightStore.LatestVerified* (for instance, initially on the age of *trustedHeader*) and the @@ -887,7 +887,7 @@ have the state *StateVerified*. ## Liveness Scenarios -The liveness argument above assumes [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) +The liveness argument above assumes [**[LCV-INV-TP.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-inv-tp1) which requires that there is a header that does not expire before the target height is reached. Here we discuss scenarios to ensure this. @@ -967,8 +967,8 @@ If on the blockchain the validator set of the block at height # Part V - Supporting the IBC Relayer The above specification focuses on the most common case, which also -constitutes the most challenging task: using the Tendermint [security -model][TMBC-FM-2THIRDS-link] to verify light blocks without +constitutes the most challenging task: using the Cosmos [security +model][CMBC-FM-2THIRDS-link] to verify light blocks without downloading all intermediate blocks. To focus on this challenge, above we have restricted ourselves to the case where *targetHeight* is greater than the height of any trusted header. This simplified @@ -1138,7 +1138,7 @@ func Main (primary PeerID, lightStore LightStore, targetHeight Height) [[block]] Specification of the block data structure. -[[RPC]] RPC client for Tendermint +[[RPC]] RPC client [[fork-detector]] The specification of the light client fork detector. @@ -1148,31 +1148,23 @@ func Main (primary PeerID, lightStore LightStore, targetHeight Height) [[lightclient]] The light client ADR [77d2651 on Dec 27, 2019]. -[RPC]: https://docs.tendermint.com/v0.34/rpc/ +[RPC]: https://docs.cometbft.com/v0.34/rpc/ -[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md +[block]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/core/data_structures.md -[TMBC-HEADER-link]: #tmbc-header1 -[TMBC-SEQ-link]: #tmbc-seq1 -[TMBC-CorrFull-link]: #tmbc-corr-full1 -[TMBC-Auth-Byz-link]: #tmbc-auth-byz1 -[TMBC-TIME_PARAMS-link]: #tmbc-time-params1 -[TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds1 -[TMBC-VAL-CONTAINS-CORR-link]: #tmbc-val-contains-corr1 -[TMBC-VAL-COMMIT-link]: #tmbc-val-commit1 -[TMBC-SOUND-DISTR-POSS-COMMIT-link]: #tmbc-sound-distr-poss-commit1 +[CMBC-SEQ-link]: #cmbc-seq1 +[CMBC-CorrFull-link]: #cmbc-corr-full1 +[CMBC-Auth-Byz-link]: #cmbc-auth-byz1 +[CMBC-TIME_PARAMS-link]: #cmbc-time-params1 +[CMBC-FM-2THIRDS-link]: #cmbc-fm-2thirds1 +[CMBC-VAL-CONTAINS-CORR-link]: #cmbc-val-contains-corr1 +[CMBC-VAL-COMMIT-link]: #cmbc-val-commit1 [lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md -[fork-detector]: https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/detection.md -[fullnode]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md +[fork-detector]: https://github.com/cometbft/cometbft/tree/v0.34.x/spec/light-client/detection +[fullnode]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/blockchain [ibc-rs]:https://github.com/informalsystems/ibc-rs -[FN-LuckyCase-link]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md#fn-luckycase - -[blockchain-validator-set]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/blockchain.md#data-structures -[fullnode-data-structures]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md#data-structures - -[FN-ManifestFaulty-link]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md#fn-manifestfaulty [arXiv]: https://arxiv.org/abs/1807.04938 diff --git a/spec/light-client/verification/verification_002_draft.md b/spec/light-client/verification/verification_002_draft.md index 4a9d777645..9d328ef1e4 100644 --- a/spec/light-client/verification/verification_002_draft.md +++ b/spec/light-client/verification/verification_002_draft.md @@ -1,14 +1,14 @@ # Light Client Verification The light client implements a read operation of a -[header][TMBC-HEADER-link] from the [blockchain][TMBC-SEQ-link], by +[header][CMBC-HEADER-link] from the [blockchain][CMBC-SEQ-link], by communicating with full nodes. As some full nodes may be faulty, this functionality must be implemented in a fault-tolerant way. -In the Tendermint blockchain, the validator set may change with every +In a Cosmos blockchain, the validator set may change with every new block. The staking and unbonding mechanism induces a [security -model][TMBC-FM-2THIRDS-link]: starting at time *Time* of the -[header][TMBC-HEADER-link], +model][CMBC-FM-2THIRDS-link]: starting at time *Time* of the +[header][CMBC-HEADER-link], more than two-thirds of the next validators of a new block are correct for the duration of *TrustedPeriod*. The fault-tolerant read operation is designed for this security model. @@ -19,7 +19,7 @@ greater than *h1*. Checking all headers of heights from *h1* to *h2* might be too costly (e.g., in terms of energy for mobile devices). This specification tries to reduce the number of intermediate blocks that need to be checked, by exploiting the guarantees provided by the -[security model][TMBC-FM-2THIRDS-link]. +[security model][CMBC-FM-2THIRDS-link]. # Status @@ -56,17 +56,17 @@ needed to be made. In contrast to [version # Outline -- [Part I](#part-i---tendermint-blockchain): Introduction of - relevant terms of the Tendermint +- [Part I](#part-i---cosmos-blockchain): Introduction of + relevant terms of the Cosmos blockchain. - [Part II](#part-ii---sequential-definition-of-the-verification-problem): Introduction of the problem addressed by the Lightclient Verification protocol. - [Verification Informal Problem - statement](#Verification-Informal-Problem-statement): For the general + statement](#verification-informal-problem-statement): For the general audience, that is, engineers who want to get an overview over what the component is doing from a bird's eye view. - - [Sequential Problem statement](#Sequential-Problem-statement): + - [Sequential Problem statement](#sequential-problem-statement): Provides a mathematical definition of the problem statement in its sequential form, that is, ignoring the distributed aspect of the implementation of the blockchain. @@ -78,17 +78,17 @@ of the problem addressed by the Lightclient Verification protocol. - [Incentives](#incentives): how faulty full nodes may benefit from misbehaving and how correct full nodes benefit from cooperating. - - [Computational Model](#Computational-Model): + - [Computational Model](#computational-model): timing and correctness assumptions. - - [Distributed Problem Statement](#Distributed-Problem-Statement): + - [Distributed Problem Statement](#distributed-problem-statement): temporal properties that formalize safety and liveness properties in the distributed setting. - [Part IV](#part-iv---light-client-verification-protocol): Specification of the protocols. - - [Definitions](#Definitions): Describes inputs, outputs, + - [Definitions](#definitions): Describes inputs, outputs, variables used by the protocol, auxiliary functions - [Core Verification](#core-verification): gives an outline of the solution, @@ -115,7 +115,7 @@ In this document we quite extensively use tags in order to be able to reference assumptions, invariants, etc. in future communication. In these tags we frequently use the following short forms: -- TMBC: Tendermint blockchain +- CMBC: Cosmos blockchain - SEQ: for sequential specifications - LCV: Lightclient Verification - LIVE: liveness @@ -124,11 +124,11 @@ these tags we frequently use the following short forms: - INV: invariant - A: assumption -# Part I - Tendermint Blockchain +# Part I - Cosmos Blockchain ## Header Fields necessary for the Light Client -#### **[TMBC-HEADER.1]** +#### **[CMBC-HEADER.1]** A set of blockchain transactions is stored in a data structure called *block*, which contains a field called *header*. (The data structure @@ -137,13 +137,13 @@ the relevant fields of the block, for the purpose of this specification, we will assume that the blockchain is a list of headers, rather than a list of blocks. -#### **[TMBC-HASH-UNIQUENESS.1]** +#### **[CMBC-HASH-UNIQUENESS.1]** We assume that every hash in the header identifies the data it hashes. Therefore, in this specification, we do not distinguish between hashes and the data they represent. -#### **[TMBC-HEADER-FIELDS.2]** +#### **[CMBC-HEADER-FIELDS.2]** A header contains the following fields: @@ -157,11 +157,11 @@ A header contains the following fields: - `AppState`: DomainApp - `LastResults`: DomainRes -#### **[TMBC-SEQ.1]** +#### **[CMBC-SEQ.1]** -The Tendermint blockchain is a list *chain* of headers. +The Cosmos blockchain is a list *chain* of headers. -#### **[TMBC-VALIDATOR-PAIR.1]** +#### **[CMBC-VALIDATOR-PAIR.1]** Given a full node, a *validator pair* is a pair *(peerID, voting_power)*, where @@ -173,13 +173,13 @@ Given a full node, a > In the Golang implementation the data type for *validator pair* is called `Validator` -#### **[TMBC-VALIDATOR-SET.1]** +#### **[CMBC-VALIDATOR-SET.1]** A *validator set* is a set of validator pairs. For a validator set *vs*, we write *TotalVotingPower(vs)* for the sum of the voting powers of its validator pairs. -#### **[TMBC-VOTE.1]** +#### **[CMBC-VOTE.1]** A *vote* contains a `prevote` or `precommit` message sent and signed by a validator node during the execution of [consensus][arXiv]. Each @@ -190,13 +190,13 @@ message contains the following fields - `Round` a positive integer - `BlockID` a Hashvalue of a block (not necessarily a block of the chain) -#### **[TMBC-COMMIT.1]** +#### **[CMBC-COMMIT.1]** A commit is a set of `precommit` message. -## Tendermint Failure Model +## Cosmos Failure Model -#### **[TMBC-AUTH-BYZ.1]** +#### **[CMBC-AUTH-BYZ.1]** We assume the authenticated Byzantine fault model in which no node (faulty or correct) may break digital signatures, but otherwise, no additional @@ -204,46 +204,46 @@ assumption is made about the internal behavior of faulty nodes. That is, faulty nodes are only limited in that they cannot forge messages. -#### **[TMBC-TIME-PARAMS.1]** +#### **[CMBC-TIME-PARAMS.1]** -A Tendermint blockchain has the following configuration parameters: +A Cosmos blockchain has the following configuration parameters: - *unbondingPeriod*: a time duration. - *trustingPeriod*: a time duration smaller than *unbondingPeriod*. -#### **[TMBC-CORRECT.1]** +#### **[CMBC-CORRECT.1]** We define a predicate *correctUntil(n, t)*, where *n* is a node and *t* is a time point. The predicate *correctUntil(n, t)* is true if and only if the node *n* follows all the protocols (at least) until time *t*. -#### **[TMBC-FM-2THIRDS.1]** +#### **[CMBC-FM-2THIRDS.1]** If a block *h* is in the chain, then there exists a subset *CorrV* of *h.NextValidators*, such that: - *TotalVotingPower(CorrV) > 2/3 - TotalVotingPower(h.NextValidators)*; cf. [TMBC-VALIDATOR-SET.1] + TotalVotingPower(h.NextValidators)*; cf. [CMBC-VALIDATOR-SET.1] - For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n, - h.Time + trustingPeriod)*; cf. [TMBC-CORRECT.1] + h.Time + trustingPeriod)*; cf. [CMBC-CORRECT.1] > The definition of correct -> [**[TMBC-CORRECT.1]**][TMBC-CORRECT-link] refers to realtime, while it +> [**[CMBC-CORRECT.1]**][CMBC-CORRECT-link] refers to realtime, while it > is used here with *Time* and *trustingPeriod*, which are "hardware > times". We do not make a distinction here. -#### **[TMBC-CORR-FULL.1]** +#### **[CMBC-CORR-FULL.1]** Every correct full node locally stores a prefix of the -current list of headers from [**[TMBC-SEQ.1]**][TMBC-SEQ-link]. +current list of headers from [**[CMBC-SEQ.1]**][CMBC-SEQ-link]. ## What the Light Client Checks -> From [TMBC-FM-2THIRDS.1] we directly derive the following observation: +> From [CMBC-FM-2THIRDS.1] we directly derive the following observation: -#### **[TMBC-VAL-CONTAINS-CORR.1]** +#### **[CMBC-VAL-CONTAINS-CORR.1]** Given a (trusted) block *tb* of the blockchain, a given set of full nodes *N* contains a correct node at a real-time *t*, if @@ -255,11 +255,11 @@ Given a (trusted) block *tb* of the blockchain, a given set of full nodes > The following describes how a commit for a given block *b* must look > like. -#### **[TMBC-SOUND-DISTR-POSS-COMMIT.1]** +#### **[CMBC-SOUND-DISTR-POSS-COMMIT.1]** For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies: -- *pc* contains only votes (cf. [TMBC-VOTE.1]) +- *pc* contains only votes (cf. [CMBC-VOTE.1]) by validators from *b.Validators* - the sum of the voting powers in *pc* is greater than 2/3 *TotalVotingPower(b.Validators)* @@ -274,7 +274,7 @@ For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies: > `BlockID` of the new (to-be-decided) block is equal to the hash of > the last block. -#### **[TMBC-VAL-COMMIT.1]** +#### **[CMBC-VAL-COMMIT.1]** If for a block *b*, a commit *c* @@ -294,8 +294,8 @@ header it receives coincides with the one generated by Tendermint consensus. The two - properties [[TMBC-VAL-CONTAINS-CORR.1]][TMBC-VAL-CONTAINS-CORR-link] and -[[TMBC-VAL-COMMIT]][TMBC-VAL-COMMIT-link] formalize the checks done + properties [[CMBC-VAL-CONTAINS-CORR.1]][CMBC-VAL-CONTAINS-CORR-link] and +[[CMBC-VAL-COMMIT]][CMBC-VAL-COMMIT-link] formalize the checks done by this specification: Given a trusted block *tb* and an untrusted block *ub* with a commit *cub*, one has to check that *cub* is in *PossibleCommit(ub)*, and that *cub* @@ -307,7 +307,7 @@ contains a correct node using *tb*. Given a height *targetHeight* as an input, the *Verifier* eventually stores a header *h* of height *targetHeight* locally. This header *h* -is generated by the Tendermint [blockchain][block]. In +is generated by the Cosmos [blockchain][block]. In particular, a header that was not generated by the blockchain should never be stored. @@ -357,12 +357,12 @@ before the timeout expires. #### **[LCV-A-TFM.1]** -The Tendermint blockchain satisfies the Tendermint failure model [**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. +The Cosmos blockchain satisfies the Cosmos failure model [**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link]. #### **[LCV-A-VAL.1]** -The system satisfies [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] and -[**[TMBC-FM-2THIRDS.1]**][TMBC-FM-2THIRDS-link]. Thus, there is a +The system satisfies [**[CMBC-AUTH-BYZ.1]**][CMBC-Auth-Byz-link] and +[**[CMBC-FM-2THIRDS.1]**][CMBC-FM-2THIRDS-link]. Thus, there is a blockchain that satisfies the soundness requirements (that is, the validation rules in [[block]]). @@ -420,9 +420,9 @@ must eventually terminate. > These definitions imply that if the primary is faulty, a header may or > may not be added to *LightStore*. In any case, -> [**[LCV-DIST-SAFE.2]**](#lcv-dist-safe2) must hold. -> The invariant [**[LCV-DIST-SAFE.2]**](#lcv-dist-safe2) and the liveness -> requirement [**[LCV-DIST-LIVE.2]**](#lcv-dist-life) +> [**[LCV-DIST-SAFE.2]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe2) must hold. +> The invariant [**[LCV-DIST-SAFE.2]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe2) and the liveness +> requirement [**[LCV-DIST-LIVE.2]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-life) > allow that verified headers are added to *LightStore* whose > height was not passed > to the verifier (e.g., intermediate headers used in bisection; see below). @@ -439,16 +439,16 @@ must eventually terminate. This specification provides a partial solution to the sequential specification. The *Verifier* solves the invariant of the sequential part -[**[LCV-DIST-SAFE.2]**](#lcv-dist-safe2) => [**[LCV-SEQ-SAFE.1]**](#lcv-seq-safe1) +[**[LCV-DIST-SAFE.2]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe2) => [**[LCV-SEQ-SAFE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-safe1) In the case the primary is correct, and *root* is a recent header in *LightStore*, the verifier satisfies the liveness requirements. ⋀ *primary is correct* ⋀ *root.header.Time* > *now* - *trustingPeriod* -⋀ [**[LCV-A-Comm.1]**](#lcv-a-comm) ⋀ ( - ( [**[TMBC-CorrFull.1]**][TMBC-CorrFull-link] ⋀ - [**[LCV-DIST-LIVE.2]**](#lcv-dist-live2) ) - ⟹ [**[LCV-SEQ-LIVE.1]**](#lcv-seq-live1) +⋀ [**[LCV-A-Comm.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-a-comm) ⋀ ( + ( [**[CMBC-CorrFull.1]**][CMBC-CorrFull-link] ⋀ + [**[LCV-DIST-LIVE.2]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-live2) ) + ⟹ [**[LCV-SEQ-LIVE.1]**](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/verification/verification_001_published.md#lcv-dist-live1) ) # Part IV - Light Client Verification Protocol @@ -612,7 +612,7 @@ func (ls LightStore) TraceTo(lightBlock LightBlock) (LightBlock, LightStore) - returns a **trusted** lightblock `root` from the lightstore with a height less than `lightBlock` - returns a lightstore that contains lightblocks that constitute a - [verification trace](TODOlinkToDetectorSpecOnceThere) from + [verification trace](https://github.com/cometbft/cometbft/tree/v0.34.x/spec/light-client/detection) from `root` to `lightBlock` (including `lightBlock`) ### Inputs @@ -624,7 +624,7 @@ func (ls LightStore) TraceTo(lightBlock LightBlock) (LightBlock, LightStore) ### Configuration Parameters - *trustThreshold*: a float. Can be used if correctness should not be based on more voting power and 1/3. -- *trustingPeriod*: a time duration [**[TMBC-TIME_PARAMS.1]**][TMBC-TIME_PARAMS-link]. +- *trustingPeriod*: a time duration [**[CMBC-TIME_PARAMS.1]**][CMBC-TIME_PARAMS-link]. - *clockDrift*: a time duration. Correction parameter dealing with only approximately synchronized clocks. ### Variables @@ -655,7 +655,7 @@ It is always the case that *LightStore.LatestTrusted.Header.Time > now - trustin ### Used Remote Functions We use the functions `commit` and `validators` that are provided -by the [RPC client for Tendermint][RPC]. +by the [RPC client][RPC]. ```go func Commit(height int64) (SignedHeader, error) @@ -743,7 +743,7 @@ func FetchLightBlock(peer PeerID, height Height) LightBlock - *lb.Validators* is the validator set of the blockchain at height *nextHeight* - *lb.NextValidators* is the validator set of the blockchain at height *nextHeight + 1* - if *node* is faulty: Returns a LightBlock with arbitrary content - [**[TMBC-AUTH-BYZ.1]**][TMBC-Auth-Byz-link] + [**[CMBC-AUTH-BYZ.1]**][CMBC-Auth-Byz-link] - Error condition - if *n* is correct: precondition violated - if *n* is faulty: arbitrary error @@ -823,7 +823,7 @@ func VerifyToTarget(primary PeerID, root LightBlock, - Error conditions - if the precondition is violated - if `ValidAndVerified` or `FetchLightBlock` report an error - - if [**[LCV-INV-TP.1]**](#LCV-INV-TP.1) is violated + - if [**[LCV-INV-TP.1]**](#lcv-inv-tp1) is violated ### Details of the Functions @@ -859,14 +859,14 @@ func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result *max(1/3,trustThreshold)* of voting power in *trusted.Nextvalidators* is contained in *untrusted.Commit* (that is, header passes the tests - [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] - and [**[TMBC-VAL-COMMIT.1]**][TMBC-VAL-COMMIT-link]) + [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link] + and [**[CMBC-VAL-COMMIT.1]**][CMBC-VAL-COMMIT-link]) - Returns `NOT_ENOUGH_TRUST` if: - *untrusted* is *not* the immediate successor of *trusted* and the *max(1/3,trustThreshold)* threshold is not reached (that is, if - [**[TMBC-VAL-CONTAINS-CORR.1]**][TMBC-VAL-CONTAINS-CORR-link] + [**[CMBC-VAL-CONTAINS-CORR.1]**][CMBC-VAL-CONTAINS-CORR-link] fails and header is does not violate the soundness checks [[block]]). - Error condition: @@ -913,8 +913,8 @@ Analogous to [[001_published]](./verification_001_published.md#liveness-scenario # Part V - Supporting the IBC Relayer The above specification focuses on the most common case, which also -constitutes the most challenging task: using the Tendermint [security -model][TMBC-FM-2THIRDS-link] to verify light blocks without +constitutes the most challenging task: using the Cosmos [security +model][CMBC-FM-2THIRDS-link] to verify light blocks without downloading all intermediate blocks. To focus on this challenge, above we have restricted ourselves to the case where *targetHeight* is greater than the height of any trusted header. This simplified @@ -927,14 +927,14 @@ For [IBC][ibc-rs] there are two additional challenges: 1. it might be that some "older" header is needed, that is, *targetHeight < lightStore.LatestVerified()*. The -[supervisor](../supervisor/supervisor.md) checks whether it is in this +[supervisor](../supervisor/supervisor_002_draft.md) checks whether it is in this case by calling `LatestPrevious` and `MinVerified` and if so it calls `Backwards`. All these functions are specified below. 2. In order to submit proof of a light client attack, a relayer may need to submit a verification trace. This it is important to compute such a trace efficiently. That it can be done is based on - the invariant [[LCV-INV-LS-ROOT.2]](#LCV-INV-LS-ROOT2) that needs + the invariant [[LCV-INV-LS-ROOT.2]](#lcv-inv-ls-root2) that needs to be maintained by the light client. In particular `VerifyToTarget` and `Backwards` need to take care of setting `verification-root`. @@ -1023,7 +1023,7 @@ func Backwards (primary PeerID, root LightBlock, targetHeight Height) [[block]] Specification of the block data structure. -[[RPC]] RPC client for Tendermint +[[RPC]] RPC client [[attack-detector]] The specification of the light client attack detector. @@ -1033,29 +1033,24 @@ func Backwards (primary PeerID, root LightBlock, targetHeight Height) [[lightclient]] The light client ADR [77d2651 on Dec 27, 2019]. -[RPC]: https://docs.tendermint.com/v0.34/rpc/ +[RPC]: https://docs.cometbft.com/v0.34/rpc/ -[block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md +[block]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/core/data_structures.md -[TMBC-HEADER-link]: #tmbc-header1 -[TMBC-SEQ-link]: #tmbc-seq1 -[TMBC-CorrFull-link]: #tmbc-corr-full1 -[TMBC-Auth-Byz-link]: #tmbc-auth-byz1 -[TMBC-TIME_PARAMS-link]: #tmbc-time-params1 -[TMBC-FM-2THIRDS-link]: #tmbc-fm-2thirds1 -[TMBC-VAL-CONTAINS-CORR-link]: #tmbc-val-contains-corr1 -[TMBC-VAL-COMMIT-link]: #tmbc-val-commit1 -[TMBC-SOUND-DISTR-POSS-COMMIT-link]: #tmbc-sound-distr-poss-commit1 +[CMBC-HEADER-link]: #cmbc-header1 +[CMBC-SEQ-link]: #cmbc-seq1 +[CMBC-CorrFull-link]: #cmbc-corr-full1 +[CMBC-Auth-Byz-link]: #cmbc-auth-byz1 +[CMBC-TIME_PARAMS-link]: #cmbc-time-params1 +[CMBC-FM-2THIRDS-link]: #cmbc-fm-2thirds1 +[CMBC-VAL-CONTAINS-CORR-link]: #cmbc-val-contains-corr1 +[CMBC-VAL-COMMIT-link]: #cmbc-val-commit1 [lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md -[attack-detector]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_001_reviewed.md -[fullnode]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md +[attack-detector]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/light-client/detection/detection_001_reviewed.md +[fullnode]: https://github.com/cometbft/cometbft/tree/v0.34.x/spec/core [ibc-rs]:https://github.com/informalsystems/ibc-rs -[blockchain-validator-set]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/blockchain.md#data-structures -[fullnode-data-structures]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md#data-structures - -[FN-ManifestFaulty-link]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/blockchain/fullnode.md#fn-manifestfaulty [arXiv]: https://arxiv.org/abs/1807.04938 diff --git a/spec/p2p/config.md b/spec/p2p/config.md index b63c04f28d..a087f8e1d5 100644 --- a/spec/p2p/config.md +++ b/spec/p2p/config.md @@ -1,7 +1,7 @@ # P2P Config Here we describe configuration options around the Peer Exchange. -These can be set using flags or via the `$TMHOME/config/config.toml` file. +These can be set using flags or via the `$CMTHOME/config/config.toml` file. ## Seed Mode diff --git a/spec/p2p/connection.md b/spec/p2p/connection.md index 33178f4794..f12f044fbe 100644 --- a/spec/p2p/connection.md +++ b/spec/p2p/connection.md @@ -61,7 +61,7 @@ func (m MConnection) TrySend(chID byte, msg interface{}) bool {} `Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued for the channel with the given id byte `chID`. The message `msg` is serialized -using the `tendermint/go-amino` submodule's `WriteBinary()` reflection routine. +using protobuf marshaling. `TrySend(chID, msg)` is a nonblocking call that queues the message msg in the channel with the given id byte chID if the queue is not full; otherwise it returns false immediately. diff --git a/spec/p2p/messages/README.md b/spec/p2p/messages/README.md index 1b5f5c60dd..4de4f103ef 100644 --- a/spec/p2p/messages/README.md +++ b/spec/p2p/messages/README.md @@ -9,7 +9,7 @@ parent: An implementation of the spec consists of many components. While many parts of these components are implementation specific, the p2p messages are not. In this section we will be covering all the p2p messages of components. -There are two parts to the P2P messages, the message and the channel. The channel is message specific and messages are specific to components of Tendermint. When a node connect to a peer it will tell the other node which channels are available. This notifies the peer what services the connecting node offers. You can read more on channels in [connection.md](../connection.md#mconnection) +There are two parts to the P2P messages, the message and the channel. The channel is message specific and messages are specific to components of CometBFT. When a node connect to a peer it will tell the other node which channels are available. This notifies the peer what services the connecting node offers. You can read more on channels in [connection.md](../connection.md#mconnection) - [Block Sync](./block-sync.md) - [Mempool](./mempool.md) diff --git a/spec/p2p/messages/consensus.md b/spec/p2p/messages/consensus.md index 48055d9da2..ba4e865620 100644 --- a/spec/p2p/messages/consensus.md +++ b/spec/p2p/messages/consensus.md @@ -30,7 +30,7 @@ next block in the blockchain should be. Vote is sent to vote for some block (or to inform others that a process does not vote in the current round). Vote is defined in the -[Blockchain](https://github.com/tendermint/tendermint/blob/v0.34.x/spec/core/data_structures.md#blockidd) +[Blockchain](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/core/data_structures.md#blockidd) section and contains validator's information (validator address and index), height and round for which the vote is sent, vote type, blockID if process vote for some block (`nil` otherwise) and a timestamp when the vote is sent. The @@ -54,7 +54,7 @@ and the block part. ### NewRoundStep NewRoundStep is sent for every step transition during the core consensus algorithm execution. -It is used in the gossip part of the Tendermint protocol to inform peers about a current +It is used in the gossip part of the CometBFT consensus protocol to inform peers about a current height/round/step a process is in. | Name | Type | Description | Field Number | @@ -144,6 +144,6 @@ Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buff | proposal_pol | [ProposalPOL](#proposalpol) | | 4 | | block_part | [BlockPart](#blockpart) | | 5 | | vote | [Vote](#vote) | | 6 | -| received_vote | [ReceivedVote](#ReceivedVote) | | 7 | +| received_vote | [ReceivedVote](#receivedvote) | | 7 | | vote_set_maj23 | [VoteSetMaj23](#votesetmaj23) | | 8 | | vote_set_bits | [VoteSetBits](#votesetbits) | | 9 | diff --git a/spec/p2p/messages/pex.md b/spec/p2p/messages/pex.md index e12a076e56..cab26ac2f4 100644 --- a/spec/p2p/messages/pex.md +++ b/spec/p2p/messages/pex.md @@ -14,7 +14,7 @@ Pex has one channel. The channel identifier is listed below. ## Message Types -The current PEX service has two versions. The first uses IP/port pair but since the p2p stack is moving towards a transport agnostic approach, +The current PEX service has two versions. The first uses IP/port pair but since the p2p stack is moving towards a transport agnostic approach, node endpoints require a `Protocol` and `Path` hence the V2 version uses a [url](https://golang.org/pkg/net/url/#URL) instead. ### PexRequest @@ -29,7 +29,7 @@ PexResponse is an list of net addresses provided to a peer to dial. | Name | Type | Description | Field Number | |-------|------------------------------------|------------------------------------------|--------------| -| addresses | repeated [PexAddress](#PexAddress) | List of peer addresses available to dial | 1 | +| addresses | repeated [PexAddress](#pexaddress) | List of peer addresses available to dial | 1 | ### PexAddress @@ -54,7 +54,7 @@ PexResponse is an list of net addresses provided to a peer to dial. | Name | Type | Description | Field Number | |-------|------------------------------------|------------------------------------------|--------------| -| addresses | repeated [PexAddressV2](#PexAddressV2) | List of peer addresses available to dial | 1 | +| addresses | repeated [PexAddressV2](#pexresponsev2) | List of peer addresses available to dial | 1 | ### PexAddressV2 @@ -70,7 +70,7 @@ Message is a [`oneof` protobuf type](https://developers.google.com/protocol-buff | Name | Type | Description | Field Number | |--------------|---------------------------|------------------------------------------------------|--------------| -| pex_request | [PexRequest](#PexRequest) | Empty request asking for a list of addresses to dial | 1 | -| pex_response | [PexResponse](#PexResponse) | List of addresses to dial | 2 | -| pex_request_v2 | [PexRequestV2](#PexRequestV2) | Empty request asking for a list of addresses to dial | 3 | -| pex_response_v2 | [PexRespinseV2](#PexResponseV2) | List of addresses to dial | 4 | +| pex_request | [PexRequest](#pexrequest) | Empty request asking for a list of addresses to dial | 1 | +| pex_response | [PexResponse](#pexresponse)| List of addresses to dial | 2 | +| pex_request_v2| [PexRequestV2](#pexrequestv2)| Empty request asking for a list of addresses to dial| 3 | +| pex_response_v2| [PexRespinseV2](#pexresponsev2)| List of addresses to dial | 4 | diff --git a/spec/p2p/messages/state-sync.md b/spec/p2p/messages/state-sync.md index 71e3ae71b1..cfc958e08d 100644 --- a/spec/p2p/messages/state-sync.md +++ b/spec/p2p/messages/state-sync.md @@ -91,16 +91,15 @@ if necessary. The light block at the height of the snapshot will be used to veri |---------------|---------------------------------------------------------|--------------------------------------|--------------| | light_block | [LightBlock](../../core/data_structures.md#lightblock) | Light block at the height requested | 1 | -State sync will use [light client verification](../../light-client/verification.README.md) to verify +State sync will use [light client verification](../../../spec/light-client/verification/README.md) to verify the light blocks. - If no state sync is in progress (i.e. during normal operation), any unsolicited response messages are discarded. ### ParamsRequest -In order to build tendermint state, the state provider will request the params at the height of the snapshot and use the header to verify it. +In order to build the state, the state provider will request the params at the height of the snapshot and use the header to verify it. | Name | Type | Description | Field Number | |----------|--------|----------------------------|--------------| diff --git a/spec/p2p/node.md b/spec/p2p/node.md index 45559e97c5..1db0cdb6f8 100644 --- a/spec/p2p/node.md +++ b/spec/p2p/node.md @@ -1,7 +1,7 @@ # Peer Discovery -A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to one another. -This document describes what kind of nodes Tendermint should enable and how they should work. +A CometBFT P2P network has different kinds of nodes with different requirements for connectivity to one another. +This document describes what kind of nodes CometBFT should enable and how they should work. ## Seeds @@ -12,27 +12,25 @@ Seeds should operate full nodes with the PEX reactor in a "crawler" mode that continuously explores to validate the availability of peers. Seeds should only respond with some top percentile of the best peers it knows about. -See [the peer-exchange docs](https://github.com/tendermint/tendermint/blob/v0.34.x/spec/reactors/pex/pex.md) for - details on peer quality. ## New Full Node A new node needs a few things to connect to the network: -- a list of seeds, which can be provided to Tendermint via config file or flags, +- a list of seeds, which can be provided to CometBFT via config file or flags, or hardcoded into the software by in-process apps - a `ChainID`, also called `Network` at the p2p layer - a recent block height, H, and hash, HASH for the blockchain. -The values `H` and `HASH` must be received and corroborated by means external to Tendermint, and specific to the user - ie. via the user's trusted social consensus. +The values `H` and `HASH` must be received and corroborated by means external to CometBFT, and specific to the user - ie. via the user's trusted social consensus. This requirement to validate `H` and `HASH` out-of-band and via social consensus is the essential difference in security models between Proof-of-Work and Proof-of-Stake blockchains. With the above, the node then queries some seeds for peers for its chain, -dials those peers, and runs the Tendermint protocols with those it successfully connects to. +dials those peers, and runs the CometBFT protocols with those it successfully connects to. When the peer catches up to height H, it ensures the block hash matches HASH. -If not, Tendermint will exit, and the user must try again - either they are connected +If not, CometBFT will exit, and the user must try again - either they are connected to bad peers or their social consensus is invalid. ## Restarted Full Node diff --git a/spec/p2p/peer.md b/spec/p2p/peer.md index 39410ce12e..995babaf87 100644 --- a/spec/p2p/peer.md +++ b/spec/p2p/peer.md @@ -1,12 +1,10 @@ # Peers -This document explains how Tendermint Peers are identified and how they connect to one another. - -For details on peer discovery, see the [peer exchange (PEX) reactor doc](https://github.com/tendermint/tendermint/blob/v0.34.x/spec/reactors/pex/pex.md). +This document explains how CometBFT Peers are identified and how they connect to one another. ## Peer Identity -Tendermint peers are expected to maintain long-term persistent identities in the form of a public key. +CometBFT peers are expected to maintain long-term persistent identities in the form of a public key. Each peer has an ID defined as `peer.ID == peer.PubKey.Address()`, where `Address` uses the scheme defined in `crypto` package. A single peer ID can have multiple IP addresses associated with it, but a node @@ -21,12 +19,12 @@ corresponding to ``. This prevents man-in-the-middle attacks on the peer lay All p2p connections use TCP. Upon establishing a successful TCP connection with a peer, -two handshakes are performed: one for authenticated encryption, and one for Tendermint versioning. +two handshakes are performed: one for authenticated encryption, and one for CometBFT versioning. Both handshakes have configurable timeouts (they should complete quickly). ### Authenticated Encryption Handshake -Tendermint implements the Station-to-Station protocol +CometBFT implements the Station-to-Station protocol using X25519 keys for Diffie-Helman key-exchange and chacha20poly1305 for encryption. Previous versions of this protocol (0.32 and below) suffered from malleability attacks whereas an active man @@ -80,9 +78,9 @@ an optional whitelist which can be managed through the ABCI app - if the whitelist is enabled and the peer does not qualify, the connection is terminated. -### Tendermint Version Handshake +### CometBFT Version Handshake -The Tendermint Version Handshake allows the peers to exchange their NodeInfo: +The CometBFT Version Handshake allows the peers to exchange their NodeInfo: ```golang type NodeInfo struct { diff --git a/spec/p2p/readme.md b/spec/p2p/readme.md index 96867aad05..e7f57c3026 100644 --- a/spec/p2p/readme.md +++ b/spec/p2p/readme.md @@ -4,3 +4,10 @@ parent: title: P2P order: 6 --- + +# Peer-to-Peer Communication + +The operation of the p2p adopted in production CometBFT networks is [HERE](./v0.34/). + +> This is part of an ongoing [effort](https://github.com/cometbft/cometbft/issues/19) +> to produce a high-level specification of the operation of the p2p layer. diff --git a/spec/p2p/v0.34/README.md b/spec/p2p/v0.34/README.md new file mode 100644 index 0000000000..4768fe5231 --- /dev/null +++ b/spec/p2p/v0.34/README.md @@ -0,0 +1,70 @@ +# Peer-to-Peer Communication + +This document describes the implementation of the peer-to-peer (p2p) +communication layer in CometBFT. + +It is part of an [effort](https://github.com/cometbft/cometbft/issues/19) +to produce a high-level specification of the operation of the p2p layer adopted +in production CometBFT networks. + +This documentation, therefore, considers the releases `0.34.*` of CometBFT, more +specifically, the branch [`v0.34.x`](https://github.com/cometbft/cometbft/tree/v0.34.x) +of this repository. + +## Overview + +A CometBFT network is composed of multiple CometBFT instances, hereafter +called **nodes**, that interact by exchanging messages. + +CometBFT assumes a partially-connected network model. +This means that a node is not assumed to be directly connected to every other +node in the network. +Instead, each node is directly connected to a subset of other nodes in the +network, hereafter called its **peers**. + +The peer-to-peer (p2p) communication layer is responsible for establishing +connections between nodes in a CometBFT network, +for managing the communication between a node and its peers, +and for intermediating the exchange of messages between peers in CometBFT protocols. + +## Contents + +The documentation follows the organization of the `p2p` package of CometBFT, +which implements the following abstractions: + +- [Transport](./transport.md): establishes secure and authenticated + connections with peers; +- [Switch](./switch.md): responsible for dialing peers and accepting + connections from peers, for managing established connections, and for + routing messages between the reactors and peers, + that is, between local and remote instances of the CometBFT protocols; +- [PEX Reactor](./pex.md): a reactor is the implementation of a protocol which + exchanges messages through the p2p layer. The PEX reactor manages the [Address Book](./addressbook.md) and implements both the [PEX protocol](./pex-protocol.md) and the [Peer Manager](./peer_manager.md) role. + - [Peer Exchange protocol](./pex-protocol.md): enables nodes to exchange peer addresses, thus implementing a peer discovery service; + - [Address Book](./addressbook.md): stores discovered peer addresses and + quality metrics associated to peers with which the node has interacted; + - [Peer Manager](./peer_manager.md): defines when and to which peers a node + should dial, in order to establish outbound connections; +- Finally, [Types](./types.md) and [Configuration](./configuration.md) provide + a list of existing types and configuration parameters used by the p2p layer implementation. + +## Further References + +Existing documentation referring to the p2p layer: + +- : p2p-related + configuration flags; overview of connections, peer instances, and reactors; + overview of peer discovery and node types; peer identity, secure connections + and peer authentication handshake. +- : message + types and channel IDs of Block Sync, Mempool, Evidence, State Sync, PEX, and + Consensus reactors. +- : the p2p layer + configuration and operation is documented in several pages. + This content is not necessarily up-to-date, some settings and concepts may + refer to the release `v0.35`, that was [discontinued][v35postmorten]. +- : + peer types, peer discovery, peer management overview, address book and peer + ranking. This documentation refers to the release `v0.35`, that was [discontinued][v35postmorten]. + +[v35postmorten]: https://interchain-io.medium.com/discontinuing-tendermint-v0-35-a-postmortem-on-the-new-networking-layer-3696c811dabc diff --git a/spec/p2p/v0.34/addressbook.md b/spec/p2p/v0.34/addressbook.md new file mode 100644 index 0000000000..8fd2cc3a2c --- /dev/null +++ b/spec/p2p/v0.34/addressbook.md @@ -0,0 +1,368 @@ +# Address Book + +The address book tracks information about peers, i.e., about other nodes in the network. + +The primary information stored in the address book are peer addresses. +A peer address is composed by a node ID and a network address; a network +address is composed by an IP address or a DNS name plus a port number. +The same node ID can be associated to multiple network addresses. + +There are two sources for the addresses stored in the address book. +The [Peer Exchange protocol](./pex-protocol.md) stores in the address book +the peer addresses it discovers, i.e., it learns from connected peers. +And the [Switch](./switch.md) registers the addresses of peers with which it +has interacted: to which it has dialed or from which it has accepted a +connection. + +The address book also records additional information about peers with which the +node has interacted, from which is possible to rank peers. +The Switch reports [connection attempts](#dial-attempts) to a peer address; too +much failed attempts indicate that a peer address is invalid. +Reactors, in they turn, report a peer as [good](#good-peers) when it behaves as +expected, or as a [bad peer](#bad-peers), when it misbehaves. + +There are two entities that retrieve peer addresses from the address book. +The [Peer Manager](./peer_manager.md) retrieves peer addresses to dial, so to +establish outbound connections. +This selection is random, but has a configurable bias towards peers that have +been marked as good peers. +The [Peer Exchange protocol](./pex-protocol.md) retrieves random samples of +addresses to offer (send) to peers. +This selection is also random but it includes, in particular for nodes that +operate in seed mode, some bias toward peers marked as good ones. + +## Buckets + +Peer addresses are stored in buckets. +There are buckets for new addresses and buckets for old addresses. +The buckets for new addresses store addresses of peers about which the node +does not have much information; the first address registered for a peer ID is +always stored in a bucket for new addresses. +The buckets for old addresses store addresses of peers with which the node has +interacted and that were reported as [good peers](#good-peers) by a reactor. +An old address therefore can be seen as an alias for a good address. + +> Note that new addresses does not mean bad addresses. +> The addresses of peers marked as [bad peers](#bad-peers) are removed from the +> buckets where they are stored, and temporarily kept in a table of banned peers. + +The number of buckets is fixed and there are more buckets for new addresses +(`256`) than buckets for old addresses (`64`), a ratio of 4:1. +Each bucket can store up to `64` addresses. +When a bucket becomes full, the peer address with the lowest ranking is removed +from the bucket. +The first choice is to remove bad addresses, with multiple failed attempts +associated. +In the absence of those, the *oldest* address in the bucket is removed, i.e., +the address with the oldest last attempt to dial. + +When a bucket for old addresses becomes full, the lowest-ranked peer address in +the bucket is moved to a bucket of new addresses. +When a bucket for new addresses becomes full, the lowest-ranked peer address in +the bucket is removed from the address book. +In other words, exceeding old or good addresses are downgraded to new +addresses, while exceeding new addresses are dropped. + +The bucket that stores an `address` is defined by the following two methods, +for new and old addresses: + +- `calcNewBucket(address, source) = hash(key + groupKey(source) + hash(key + groupKey(address) + groupKey(source)) % newBucketsPerGroup) % newBucketCount` +- `calcOldBucket(address) = hash(key + groupKey(address) + hash(key + address) % oldBucketsPerGroup) % oldBucketCount` + +The `key` is a fixed random 96-bit (8-byte) string. +The `groupKey` for an address is a string representing its network group. +The `source` of an address is the address of the peer from which we learn the +address.. +The first (internal) hash is reduced to an integer up to `newBucketsPerGroup = +32`, for new addresses, and `oldBucketsPerGroup = 4`, for old addresses. +The second (external) hash is reduced to bucket indexes, in the interval from 0 +to the number of new (`newBucketCount = 256`) or old (`oldBucketCount = 64`) buckets. + +Notice that new addresses with sources from the same network group are more +likely to end up in the same bucket, therefore to competing for it. +For old address, instead, two addresses are more likely to end up in the same +bucket when they belong to the same network group. + +## Adding addresses + +The `AddAddress` method adds the address of a peer to the address book. + +The added address is associated to a *source* address, which identifies the +node from which the peer address was learned. + +Addresses are added to the address book in the following situations: + +1. When a peer address is learned via PEX protocol, having the sender + of the PEX message as its source +2. When an inbound peer is added, in this case the peer itself is set as the + source of its own address +3. When the switch is instructed to dial addresses via the `DialPeersAsync` + method, in this case the node itself is set as the source + +If the added address contains a node ID that is not registered in the address +book, the address is added to a [bucket](#buckets) of new addresses. +Otherwise, the additional address for an existing node ID is **not added** to +the address book when: + +- The last address added with the same node ID is stored in an old bucket, so + it is considered a "good" address +- There are addresses associated to the same node ID stored in + `maxNewBucketsPerAddress = 4` distinct buckets +- Randomly, with a probability that increases exponentially with the number of + buckets in which there is an address with the same node ID. + So, a new address for a node ID which is already present in one bucket is + added with 50% of probability; if the node ID is present in two buckets, the + probability decreases to 25%; and if it is present in three buckets, the + probability is 12.5%. + +The new address is also added to the `addrLookup` table, which stores +`knownAddress` entries indexed by their node IDs. +If the new address is from an unknown peer, a new entry is added to the +`addrLookup` table; otherwise, the existing entry is updated with the new +address. +Entries of this table contain, among other fields, the list of buckets where +addresses of a peer are stored. +The `addrLookup` table is used by most of the address book methods (e.g., +`HasAddress`, `IsGood`, `MarkGood`, `MarkAttempt`), as it provides fast access +to addresses. + +### Errors + +- if the added address or the associated source address are nil +- if the added address is invalid +- if the added address is the local node's address +- if the added address ID is of a [banned](#bad-peers) peer +- if either the added address or the associated source address IDs are configured as private IDs +- if `routabilityStrict` is set and the address is not routable +- in case of failures computing the bucket for the new address (`calcNewBucket` method) +- if the added address instance, which is a new address, is configured as an + old address (sanity check of `addToNewBucket` method) + +## Need for Addresses + +The `NeedMoreAddrs` method verifies whether the address book needs more addresses. + +It is invoked by the PEX reactor to define whether to request peer addresses +to a new outbound peer or to a randomly selected connected peer. + +The address book needs more addresses when it has less than `1000` addresses +registered, counting all buckets for new and old addresses. + +## Pick address + +The `PickAddress` method returns an address stored in the address book, chosen +at random with a configurable bias towards new addresses. + +It is invoked by the Peer Manager to obtain a peer address to dial, as part of +its `ensurePeers` routine. +The bias starts from 10%, when the peer has no outbound peers, increasing by +10% for each outbound peer the node has, up to 90%, when the node has at least +8 outbound peers. + +The configured bias is a parameter that influences the probability of choosing +an address from a bucket of new addresses or from a bucket of old addresses. +A second parameter influencing this choice is the number of new and old +addresses stored in the address book. +In the absence of bias (i.e., if the configured bias is 50%), the probability +of picking a new address is given by the square root of the number of new +addresses divided by the sum of the square roots of the numbers of new and old +addresses. +By adding a bias toward new addresses (i.e., configured bias larger than 50%), +the portion on the sample occupied by the square root of the number of new +addresses increases, while the corresponding portion for old addresses decreases. +As a result, it becomes more likely to pick a new address at random from this sample. + +> The use of the square roots softens the impact of disproportional numbers of +> new and old addresses in the address book. This is actually the expected +> scenario, as there are 4 times more buckets for new addresses than buckets +> for old addresses. + +Once the type of address, new or old, is defined, a non-empty bucket of this +type is selected at random. +From the selected bucket, an address is chosen at random and returned. +If all buckets of the selected type are empty, no address is returned. + +## Random selection + +The `GetSelection` method returns a selection of addresses stored in the +address book, with no bias toward new or old addresses. + +It is invoked by the PEX protocol to obtain a list of peer addresses with two +purposes: + +- To send to a peer in a PEX response, in the case of outbound peers or of + nodes not operating in seed mode +- To crawl, in the case of nodes operating in seed mode, as part of every + interaction of the `crawlPeersRoutine` + +The selection is a random subset of the peer addresses stored in the +`addrLookup` table, which stores the last address added for each peer ID. +The target size of the selection is `23%` (`getSelectionPercent`) of the +number of addresses stored in the address book, but it should not be lower than +`32` (`minGetSelection`) --- if it is, all addresses in the book are returned +--- nor greater than `250` (`maxGetSelection`). + +> The random selection is produced by: +> +> - Retrieving all entries of the `addrLookup` map, which by definition are +> returned in random order. +> - Randomly shuffling the retrieved list, using the Fisher-Yates algorithm + +## Random selection with bias + +The `GetSelectionWithBias` method returns a selection of addresses stored in +the address book, with bias toward new addresses. + +It is invoked by the PEX protocol to obtain a list of peer addresses to be sent +to a peer in a PEX response. +This method is only invoked by seed nodes, when replying to a PEX request +received from an inbound peer (i.e., a peer that dialed the seed node). +The bias used in this scenario is hard-coded to 30%, meaning that 70% of +the returned addresses are expected to be old addresses. + +The number of addresses that compose the selection is computed in the same way +as for the non-biased random selection. +The bias toward new addresses is implemented by requiring that the configured +bias, interpreted as a percentage, of the select addresses come from buckets of +new addresses, while the remaining come from buckets of old addresses. +Since the number of old addresses is typically lower than the number of new +addresses, it is possible that the address book does not have enough old +addresses to include in the selection. +In this case, additional new addresses are included in the selection. +Thus, the configured bias, in practice, is towards old addresses, not towards +new addresses. + +To randomly select addresses of a type, the address book considers all +addresses present in every bucket of that type. +This list of all addresses of a type is randomly shuffled, and the requested +number of addresses are retrieved from the tail of this list. +The returned selection contains, at its beginning, a random selection of new +addresses in random order, followed by a random selection of old addresses, in +random order. + +## Dial Attempts + +The `MarkAttempt` method records a failed attempt to connect to an address. + +It is invoked by the Peer Manager when it fails dialing a peer, but the failure +is not in the authentication step (`ErrSwitchAuthenticationFailure` error). +In case of authentication errors, the peer is instead marked as a [bad peer](#bad-peers). + +The failed connection attempt is recorded in the address registered for the +peer's ID in the `addrLookup` table, which is the last address added with that ID. +The known address' counter of failed `Attempts` is increased and the failure +time is registered in `LastAttempt`. + +The possible effect of recording multiple failed connect attempts to a peer is +to turn its address into a *bad* address (do not confuse with banned addresses). +A known address becomes bad if it is stored in buckets of new addresses, and +when connection attempts: + +- Have not been made over a week, i.e., `LastAttempt` is older than a week +- Have failed 3 times and never succeeded, i.e., `LastSucess` field is unset +- Have failed 10 times in the last week, i.e., `LastSucess` is older than a week + +Addresses marked as *bad* are the first candidates to be removed from a bucket of +new addresses when the bucket becomes full. + +> Note that failed connection attempts are reported for a peer address, but in +> fact the address book records them for a peer. +> +> More precisely, failed connection attempts are recorded in the entry of the +> `addrLookup` table with reported peer ID, which contains the last address +> added for that node ID, which is not necessarily the reported peer address. + +## Good peers + +The `MarkGood` method marks a peer ID as good. + +It is invoked by the consensus reactor, via switch, when the number of useful +messages received from a peer is a multiple of `10000`. +Vote and block part messages are considered for this number, they must be valid +and not be duplicated messages to be considered useful. + +> The `SwitchReporter` type of `behaviour` package also invokes the `MarkGood` +> method when a "reason" associated with consensus votes and block parts is +> reported. +> No reactor, however, currently provides these "reasons" to the `SwitchReporter`. + +The effect of this action is that the address registered for the peer's ID in the +`addrLookup` table, which is the last address added with that ID, is marked as +good and moved to a bucket of old addresses. +An address marked as good has its failed to connect counter and timestamp reset. +If the destination bucket of old addresses is full, the oldest address in the +bucket is moved (downgraded) to a bucket of new addresses. + +Moving the peer address to a bucket of old addresses has the effect of +upgrading, or increasing the ranking of a peer in the address book. + +## Bad peers + +The `MarkBad` method marks a peer as bad and bans it for a period of time. + +This method is only invoked within the PEX reactor, with a banning time of 24 +hours, for the following reasons: + +- A peer misbehaves in the [PEX protocol](pex-protocol.md#misbehavior) +- When the `maxAttemptsToDial` limit (`16`) is reached for a peer +- If an `ErrSwitchAuthenticationFailure` error is returned when dialing a peer + +The effect of this action is that the address registered for the peer's ID in the +`addrLookup` table, which is the last address added with that ID, is banned for +a period of time. +The banned peer is removed from the `addrLookup` table and from all buckets +where its addresses are stored. + +The information about banned peers, however, is not discarded. +It is maintained in the `badPeers` map, indexed by peer ID. +This allows, in particular, addresses of banned peers to be +[reinstated](#reinstating-addresses), i.e., to be added +back to the address book, when their ban period expires. + +## Reinstating addresses + +The `ReinstateBadPeers` method attempts to re-add banned addresses to the address book. + +It is invoked by the PEX reactor when dialing new peers. +This action is taken before requesting additional addresses to peers, +in the case that the node needs more peer addresses. + +The set of banned peer addresses is retrieved from the `badPeers` map. +Addresses that are not any longer banned, i.e., whose banned period has expired, +are added back to the address book as new addresses, while the corresponding +node IDs are removed from the `badPeers` map. + +## Removing addresses + +The `RemoveAddress` method removes an address from the address book. + +It is invoked by the switch when it dials a peer or accepts a connection from a +peer that ends up being the node itself (`IsSelf` error). +In both cases, the address dialed or accepted is also added to the address book +as a local address, via the `AddOurAddress` method. + +The same logic is also internally used by the address book for removing +addresses of a peer that is [marked as a bad peer](#bad-peers). + +The entry registered with the peer ID of the address in the `addrLookup` table, +which is the last address added with that ID, is removed from all buckets where +it is stored and from the `addrLookup` table. + +> FIXME: is it possible that addresses with the same ID as the removed address, +> but with distinct network addresses, are kept in buckets of the address book? +> While they will not be accessible anymore, as there is no reference to them +> in the `addrLookup`, they will still be there. + +## Persistence + +The `loadFromFile` method, called when the address book is started, reads +address book entries from a file, passed to the address book constructor. +The file, at this point, does not need to exist. + +The `saveRoutine` is started when the address book is started. +It saves the address book to the configured file every `dumpAddressInterval`, +hard-coded to 2 minutes. +It is also possible to save the content of the address book using the `Save` +method. +Saving the address book content to a file acquires the address book lock, also +employed by all other public methods. diff --git a/spec/p2p/v0.34/configuration.md b/spec/p2p/v0.34/configuration.md new file mode 100644 index 0000000000..53ac3183db --- /dev/null +++ b/spec/p2p/v0.34/configuration.md @@ -0,0 +1,51 @@ +# CometBFT p2p configuration + +This document contains configurable parameters a node operator can use to tune the p2p behaviour. + +| Parameter| Default| Description | +| --- | --- | ---| +| ListenAddress | "tcp://0.0.0.0:26656" | Address to listen for incoming connections (0.0.0.0:0 means any interface, any port) | +| ExternalAddress | "" | Address to advertise to peers for them to dial | +| [Seeds](pex-protocol.md#seed-nodes) | empty | Comma separated list of seed nodes to connect to (ID@host:port )| +| [Persistent peers](peer_manager.md#persistent-peers) | empty | Comma separated list of nodes to keep persistent connections to (ID@host:port ) | +| UPNP | false | UPNP port forwarding enabled | +| [AddrBook](addressbook.md) | defaultAddrBookPath | Path do address book | +| AddrBookStrict | true | Set true for strict address routability rules and false for private or local networks | +| [MaxNumInboundPeers](switch.md#accepting-peers) | 40 | Maximum number of inbound peers | +| [MaxNumOutboundPeers](peer_manager.md#ensure-peers) | 10 | Maximum number of outbound peers to connect to, excluding persistent peers | +| [UnconditionalPeers](switch.md#accepting-peers) | empty | These are IDs of the peers which are allowed to be (re)connected as both inbound or outbound regardless of whether the node reached `max_num_inbound_peers` or `max_num_outbound_peers` or not. | +| PersistentPeersMaxDialPeriod| 0 * time.Second | Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) | +| FlushThrottleTimeout |100 * time.Millisecond| Time to wait before flushing messages out on the connection | +| MaxPacketMsgPayloadSize | 1024 | Maximum size of a message packet payload, in bytes | +| SendRate | 5120000 (5 mB/s) | Rate at which packets can be sent, in bytes/second | +| RecvRate | 5120000 (5 mB/s) | Rate at which packets can be received, in bytes/second| +| [PexReactor](pex.md) | true | Set true to enable the peer-exchange reactor | +| SeedMode | false | Seed mode, in which node constantly crawls the network and looks for. Does not work if the peer-exchange reactor is disabled. | +| PrivatePeerIDs | empty | Comma separated list of peer IDsthat we do not add to the address book or gossip to other peers. They stay private to us. | +| AllowDuplicateIP | false | Toggle to disable guard against peers connecting from the same ip.| +| [HandshakeTimeout](transport.md#connection-upgrade) | 20 * time.Second | Timeout for handshake completion between peers | +| [DialTimeout](switch.md#dialing-peers) | 3 * time.Second | Timeout for dialing a peer | + + +These parameters can be set using the `$CMTHOME/config/config.toml` file. A subset of them can also be changed via command line using the following command line flags: + +| Parameter | Flag| Example| +| --- | --- | ---| +| Listen address| `p2p.laddr` | "tcp://0.0.0.0:26656" | +| Seed nodes | `p2p.seeds` | `--p2p.seeds “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:4444”` | +| Persistent peers | `p2p.persistent_peers` | `--p2p.persistent_peers “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:26656”` | +| Unconditional peers | `p2p.unconditional_peer_ids` | `--p2p.unconditional_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | + | UPNP | `p2p.upnp` | `--p2p.upnp` | + | PexReactor | `p2p.pex` | `--p2p.pex` | + | Seed mode | `p2p.seed_mode` | `--p2p.seed_mode` | + | Private peer ids | `p2p.private_peer_ids` | `--p2p.private_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | + + **Note on persistent peers** + + If `persistent_peers_max_dial_period` is set greater than zero, the +pause between each dial to each persistent peer will not exceed `persistent_peers_max_dial_period` +during exponential backoff and we keep trying again without giving up. + +If `seeds` and `persistent_peers` intersect, +the user will be warned that seeds may auto-close connections +and that the node may not be able to keep the connection persistent. diff --git a/spec/p2p/v0.34/img/p2p_state.png b/spec/p2p/v0.34/img/p2p_state.png new file mode 100644 index 0000000000000000000000000000000000000000..c86d01682624fd92ead0739519d906bf65a82ec3 GIT binary patch literal 132059 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 FIXME: The current logic was introduced in [#3762](https://github.com/tendermint/tendermint/pull/3762). +> Although it fix the issue, the delay between receiving an address and dialing +> the peer, it does not impose and limit on how many addresses are dialed in this +> scenario. +> So, all addresses received from a seed node are dialed, regardless of the +> current number of outbound peers, the number of dialing routines, or the +> `MaxNumOutboundPeers` parameter. +> +> Issue [#9548](https://github.com/tendermint/tendermint/issues/9548) was +> created to handle this situation. + +### First round + +When the PEX reactor is started, the `ensurePeersRoutine` is created and it +runs thorough the operation of a node, periodically invoking the `ensurePeers` +method. +However, if when the persistent routine is started the node already has some +peers, either inbound or outbound peers, or is dialing some addresses, the +first invocation of `ensurePeers` is delayed by a random amount of time from 0 +to `ensurePeersPeriod`. + +### Persistent peers + +The node configuration can contain a list of *persistent peers*. +Those peers have preferential treatment compared to regular peers and the node +is always trying to connect to them. +Moreover, these peers are not removed from the address book in the case of +multiple failed dial attempts. + +On startup, the node immediately tries to dial the configured persistent peers +by calling the switch's [`DialPeersAsync`](./switch.md#manual-operation) method. +This is not done in the p2p package, but it is part of the procedure to set up a node. + +> TODO: the handling of persistent peers should be described in more detail. + +### Life cycle + +The picture below is a first attempt of illustrating the life cycle of an outbound peer: + + + +A peer can be in the following states: + +- Candidate peers: peer addresses stored in the address boook, that can be + retrieved via the [`PickAddress`](./addressbook.md#pick-address) method +- [Dialing](switch.md#dialing-peers): peer addresses that are currently being + dialed. This state exists to ensure that a single dialing routine exist per peer. +- [Reconnecting](switch.md#reconnect-to-peer): persistent peers to which a node + is currently reconnecting, as a previous connection attempt has failed. +- Connected peers: peers that a node has successfully dialed, added as outbound peers. +- [Bad peers](addressbook.md#bad-peers): peers marked as bad in the address + book due to exhibited [misbehavior](pex-protocol.md#misbehavior). + Peers can be reinstated after being marked as bad. + +## Pending of documentation + +The `dialSeeds` method of the PEX reactor. + +The `dialPeer` method of the PEX reactor. +This includes `dialAttemptsInfo`, `maxBackoffDurationForPeer` methods. diff --git a/spec/p2p/v0.34/pex-protocol.md b/spec/p2p/v0.34/pex-protocol.md new file mode 100644 index 0000000000..908f7258fc --- /dev/null +++ b/spec/p2p/v0.34/pex-protocol.md @@ -0,0 +1,240 @@ +# Peer Exchange Protocol + +The Peer Exchange (PEX) protocol enables nodes to exchange peer addresses, thus +implementing a peer discovery mechanism. + +The PEX protocol uses two messages: + +- `PexRequest`: sent by a node to [request](#requesting-addresses) peer + addresses to a peer +- `PexAddrs`: a list of peer addresses [provided](#providing-addresses) to a + peer as response to a `PexRequest` message + +While all nodes, with few exceptions, participate on the PEX protocol, +a subset of nodes, configured as [seed nodes](#seed-nodes) have a particular +role in the protocol. +They crawl the network, connecting to random peers, in order to learn as many +peer addresses as possible to provide to other nodes. + +## Requesting Addresses + +A node requests peer addresses by sending a `PexRequest` message to a peer. + +For regular nodes, not operating in seed mode, a PEX request is sent when +the node *needs* peers addresses, a condition checked: + +1. When an *outbound* peer is added, causing the node to request addresses from + the new peer +2. Periodically, by the `ensurePeersRoutine`, causing the node to request peer + addresses to a randomly selected peer + +A node needs more peer addresses when its addresses book has +[less than 1000 records](./addressbook.md#need-for-addresses). +It is thus reasonable to assume that the common case is that a peer needs more +peer addresses, so that PEX requests are sent whenever the above two situations happen. + +A PEX request is sent when a new *outbound* peer is added. +The same does not happen with new inbound peers because the implementation +considers outbound peers, that the node has chosen for dialing, more +trustworthy than inbound peers, that the node has accepted. +Moreover, when a node is short of peer addresses, it dials the configured seed nodes; +since they are added as outbound peers, the node can immediately request peer addresses. + +The `ensurePeersRoutine` periodically checks, by default every 30 seconds (`ensurePeersPeriod`), +whether the node has enough outbound peers. +If it does not have, the node tries dialing some peer addresses stored in the address book. +As part of this procedure, the node selects a peer at random, +from the set of connected peers retrieved from the switch, +and sends a PEX request to the selected peer. + +Sending a PEX request to a peer is implemented by the `RequestAddrs` method of +the PEX reactor. + +### Responses + +After a PEX request is sent to a peer, the node expects to receive, +as a response, a `PexAddrs` message from the peer. +This message encodes a list of peer addresses that are +[added to address book](./addressbook.md#adding-addresses), +having the peer from which the PEX response was received as their source. + +Received PEX responses are handled by the `ReceiveAddrs` method of the PEX reactor. +In the case of a PEX response received from a peer which is configured as +a seed node, the PEX reactor attempts immediately to dial the provided peer +addresses, as detailed [here](./peer_manager.md#fast-dialing). + +### Misbehavior + +Sending multiple PEX requests to a peer, before receiving a reply from it, +is considered a misbehavior. +To prevent it, the node maintains a `requestsSent` set of outstanding +requests, indexed by destination peers. +While a peer ID is present in the `requestsSent` set, the node does not send +further PEX requests to that peer. +A peer ID is removed from the `requestsSent` set when a PEX response is +received from it. + +Sending a PEX response to a peer that has not requested peer addresses +is also considered a misbehavior. +So, if a PEX response is received from a peer that is not registered in +the `requestsSent` set, a `ErrUnsolicitedList` error is produced. +This leads the peer to be disconnected and [marked as a bad peer](addressbook.md#bad-peers). + +## Providing Addresses + +When a node receives a `PexRequest` message from a peer, +it replies with a `PexAddrs` message. + +This message encodes a [random selection of peer addresses](./addressbook.md#random-selection) +retrieved from the address book. + +Sending a PEX response to a peer is implemented by the `SendAddrs` method of +the PEX reactor. + +### Misbehavior + +Requesting peer addresses too often is considered a misbehavior. +Since node are expected to send PEX requests every `ensurePeersPeriod`, +the minimum accepted interval between requests from the same peer is set +to `ensurePeersPeriod / 3`, 10 seconds by default. + +The `receiveRequest` method is responsible for verifying this condition. +The node keeps a `lastReceivedRequests` map with the time of the last PEX +request received from every peer. +If the interval between successive requests is less than the minimum accepted +one, the peer is disconnected and [marked as a bad peer](addressbook.md#bad-peers). +An exception is made for the first two PEX requests received from a peer. + +> The probable reason is that, when a new peer is added, the two conditions for +> a node to request peer addresses can be triggered with an interval lower than +> the minimum accepted interval. +> Since this is a legit behavior, it should not be punished. + +## Seed nodes + +A seed node is a node configured to operate in `SeedMode`. + +### Crawling peers + +Seed nodes crawl the network, connecting to random peers and sending PEX +requests to them, in order to learn as many peer addresses as possible. +More specifically, a node operating in seed mode sends PEX requests in two cases: + +1. When an outbound peer is added, and the seed node needs more peer addresses, + it requests peer addresses to the new peer +2. Periodically, the `crawlPeersRoutine` sends PEX requests to a random set of + peers, whose addresses are registered in the Address Book + +The first case also applies for nodes not operating in seed mode. +The second case replaces the second for regular nodes, as seed nodes do not +run the `ensurePeersRoutine`, as regular nodes, +but run the `crawlPeersRoutine`, which is not run by regular nodes. + +The `crawlPeersRoutine` periodically, every 30 seconds (`crawlPeerPeriod`), +starts a new peer discovery round. +First, the seed node retrieves a random selection of peer addresses from its +Address Book. +This selection is produced in the same way as in the random selection of peer +addresses that are [provided](#providing-addresses) to a requesting peer. +Peers that the seed node has crawled recently, +less than 2 minutes ago (`minTimeBetweenCrawls`), are removed from this selection. +The remaining peer addresses are registered in the `crawlPeerInfos` table. + +The seed node is not necessarily connected to the peer whose address is +selected for each round of crawling. +So, the seed node dials the selected peer addresses. +This is performed in foreground, one peer at a time. +As a result, a round of crawling can take a substantial amount of time. +For each selected peer it succeeds dialing to, this include already connected +peers, the seed node sends a PEX request. + +Dialing a selected peer address can fail for multiple reasons. +The seed node might have attempted to dial the peer too many times. +In this case, the peer address is marked as [bad in the address book](addressbook.md#bad-peers). +The seed node might have attempted to dial the peer recently, without success, +and the exponential `backoffDuration` has not yet passed. +Or the current connection attempt might fail, which is registered in the address book. + +Failures to dial to a peer address produce an information that is important for +a seed node. +They indicate that a peer is unreachable, or is not operating correctly, and +therefore its address should not be provided to other nodes. +This occurs when, due to multiple failed connection attempts or authentication +failures, the peer address ends up being removed from the address book. +As a result, the periodically crawling of selected peers not only enables the +discovery of new peers, but also allows the seed node to stop providing +addresses of bad peers. + +### Offering addresses + +Nodes operating in seed mode handle PEX requests differently than regular +nodes, whose operation is described [here](#providing-addresses). + +This distinction exists because nodes dial a seed node with the main, if not +exclusive goal of retrieving peer addresses. +In other words, nodes do not dial a seed node because they intend to have it as +a peer in the multiple CometBFT protocols, but because they believe that a +seed node is a good source of addresses of nodes to which they can establish +connections and interact in the multiple CometBFT protocols. + +So, when a seed node receives a `PexRequest` message from an inbound peer, +it sends a `PexAddrs` message, containing a selection of peer +addresses, back to the peer and *disconnects* from it. +Seed nodes therefore treat inbound connections from peers as short-term +connections, exclusively intended to retrieve peer addresses. +Once the requested peer addresses are sent, the connection with the peer is closed. + +Moreover, the selection of peer addresses provided to inbound peers by a seed +node, although still essentially random, has a [bias toward old +addresses](./addressbook.md#random-selection-with-bias). +The selection bias is defined by `biasToSelectNewPeers`, hard-coded to `30%`, +meaning that `70%` of the peer addresses provided by a seed node are expected +to be old addresses. +Although this nomenclature is not clear, *old* addresses are the addresses that +survived the most in the address book, that is, are addresses that the seed +node believes being from *good* peers (more details [here](./addressbook.md#good-peers)). + +Another distinction is on the handling of potential [misbehavior](#misbehavior-1) +of peers requesting addresses. +A seed node does not enforce, a priori, a minimal interval between PEX requests +from inbound peers. +Instead, it does not reply to more than one PEX request per peer inbound +connection, and, as above mentioned, it disconnects from incoming peers after +responding to them. +If the same peer dials again to the seed node and requests peer addresses, the +seed node will reply to this peer like it was the first time it has requested +peer addresses. + +> This is more an implementation restriction than a desired behavior. +> The `lastReceivedRequests` map stores the last time a PEX request was +> received from a peer, and the entry relative to a peer is removed from this +> map when the peer is disconnected. +> +> It is debatable whether this approach indeed prevents abuse against seed nodes. + +### Disconnecting from peers + +Seed nodes treat connections with peers as short-term connections, which are +mainly, if not exclusively, intended to exchange peer addresses. + +In the case of inbound peers, that have dialed the seed node, the intent of the +connection is achieved once a PEX response is sent to the peer. +The seed node thus disconnects from an inbound peer after sending a `PexAddrs` +message to it. + +In the case of outbound peers, which the seed node has dialed for crawling peer +addresses, the intent of the connection is essentially achieved when a PEX +response is received from the peer. +The seed node, however, does not disconnect from a peer after receiving a +selection of peer addresses from it. +As a result, after some rounds of crawling, a seed node will have established +connections to a substantial amount of peers. + +To couple with the existence of multiple connections with peers that have no +longer purpose for the seed node, the `crawlPeersRoutine` also invokes, after +each round of crawling, the `attemptDisconnects` method. +This method retrieves the list of connected peers from the switch, and +disconnects from peers that are not persistent peers, and with which a +connection is established for more than `SeedDisconnectWaitPeriod`. +This period is a configuration parameter, set to 28 hours when the PEX reactor +is created by the default node constructor. diff --git a/spec/p2p/v0.34/pex.md b/spec/p2p/v0.34/pex.md new file mode 100644 index 0000000000..8243eaa559 --- /dev/null +++ b/spec/p2p/v0.34/pex.md @@ -0,0 +1,111 @@ +# PEX Reactor + +The PEX reactor is one of the reactors running in a CometBFT node. + +Its implementation is located in the `p2p/pex` package, and it is considered +part of the implementation of the p2p layer. + +This document overviews the implementation of the PEX reactor, describing how +the methods from the `Reactor` interface are implemented. + +The actual operation of the PEX reactor is presented in documents describing +the roles played by the PEX reactor in the p2p layer: + +- [Address Book](./addressbook.md): stores known peer addresses and information + about peers to which the node is connected or has attempted to connect +- [Peer Manager](./peer_manager.md): manages connections established with peers, + defining when a node should dial peers and which peers it should dial +- [Peer Exchange protocol](./pex-protocol.md): enables nodes to exchange peer + addresses, thus implementing a peer discovery service + +## OnStart + +The `OnStart` method implements `BaseService` and starts the PEX reactor. + +The [address book](./addressbook.md), which is a `Service` is started. +This loads the address book content from disk, +and starts a routine that periodically persists the address book content to disk. + +The PEX reactor is configured with the addresses of a number of seed nodes, +the `Seeds` parameter of the `ReactorConfig`. +The addresses of seed nodes are parsed into `NetAddress` instances and resolved +into IP addresses, which is implemented by the `checkSeeds` method. +Valid seed node addresses are stored in the `seedAddrs` field, +and are used by the `dialSeeds` method to contact the configured seed nodes. + +The last action is to start one of the following persistent routines, based on +the `SeedMode` configuration parameter: + +- Regular nodes run the `ensurePeersRoutine` to check whether the node has + enough outbound peers, dialing peers when necessary +- Seed nodes run the `crawlPeersRoutine` to periodically start a new round + of [crawling](./pex-protocol.md#Crawling-peers) to discover as many peer + addresses as possible + +### Errors + +Errors encountered when loading the address book from disk are returned, +and prevent the reactor from being started. +An exception is made for the `service.ErrAlreadyStarted` error, which is ignored. + +Errors encountered when parsing the configured addresses of seed nodes +are returned and cause the reactor startup to fail. +An exception is made for DNS resolution `ErrNetAddressLookup` errors, +which are not deemed fatal and are only logged as invalid addresses. + +If none of the configured seed node addresses is valid, and the loaded address +book is empty, the reactor is not started and an error is returned. + +## OnStop + +The `OnStop` method implements `BaseService` and stops the PEX reactor. + +The address book routine that periodically saves its content to disk is stopped. + +## GetChannels + +The `GetChannels` method, from the `Reactor` interface, returns the descriptor +of the channel used by the PEX protocol. + +The channel ID is `PexChannel` (0), with priority `1`, send queue capacity of +`10`, and maximum message size of `64000` bytes. + +## AddPeer + +The `AddPeer` method, from the `Reactor` interface, +adds a new peer to the PEX protocol. + +If the new peer is an **inbound peer**, i.e., if the peer has dialed the node, +the peer's address is [added to the address book](./addressbook.md#adding-addresses). +Since the peer was authenticated when establishing a secret connection with it, +the source of the peer address is trusted, and its source is set by the peer itself. +In the case of an outbound peer, the node should already have its address in +the address book, as the switch has dialed the peer. + +If the peer is an **outbound peer**, i.e., if the node has dialed the peer, +and the PEX protocol needs more addresses, +the node [sends a PEX request](./pex-protocol.md#Requesting-Addresses) to the peer. +The same is not done when inbound peers are added because they are deemed least +trustworthy than outbound peers. + +## RemovePeer + +The `RemovePeer` method, from the `Reactor` interface, +removes a peer from the PEX protocol. + +The peer's ID is removed from the tables tracking PEX requests +[sent](./pex-protocol.md#misbehavior) but not yet replied +and PEX requests [received](./pex-protocol.md#misbehavior-1). + +## Receive + +The `Receive` method, from the `Reactor` interface, +handles a message received by the PEX protocol. + +A node receives two type of messages as part of the PEX protocol: + +- `PexRequest`: a request for addresses received from a peer, handled as + described [here](./pex-protocol.md#providing-addresses) +- `PexAddrs`: a list of addresses received from a peer, as a reponse to a PEX + request sent by the node, as described [here](./pex-protocol.md#responses) + diff --git a/spec/p2p/v0.34/switch.md b/spec/p2p/v0.34/switch.md new file mode 100644 index 0000000000..1d50108f2b --- /dev/null +++ b/spec/p2p/v0.34/switch.md @@ -0,0 +1,238 @@ +# Switch + +The switch is a core component of the p2p layer. +It manages the procedures for [dialing peers](#dialing-peers) and +[accepting](#accepting-peers) connections from peers, which are actually +implemented by the [transport](./transport.md). +It also manages the reactors, i.e., protocols implemented by the node that +interact with its peers. +Once a connection with a peer is established, the peer is [added](#add-peer) to +the switch and all registered reactors. +Reactors may also instruct the switch to [stop a peer](#stop-peer), namely +disconnect from it. +The switch, in this case, makes sure that the peer is removed from all +registered reactors. + +## Dialing peers + +Dialing a peer is implemented by the `DialPeerWithAddress` method. + +This method is invoked by the [peer manager](./peer_manager.md#ensure-peers) +to dial a peer address and establish a connection with an outbound peer. + +The switch keeps a single dialing routine per peer ID. +This is ensured by keeping a synchronized map `dialing` with the IDs of peers +to which the peer is dialing. +A peer ID is added to `dialing` when the `DialPeerWithAddress` method is called +for that peer, and it is removed when the method returns for whatever reason. +The method returns immediately when invoked for a peer which ID is already in +the `dialing` structure. + +The actual dialing is implemented by the [`Dial`](./transport.md#dial) method +of the transport configured for the switch, in the `addOutboundPeerWithConfig` +method. +If the transport succeeds establishing a connection, the returned `Peer` is +added to the switch using the [`addPeer`](#add-peer) method. +This operation can fail, returning an error. In this case, the switch invokes +the transport's [`Cleanup`](./transport.md#cleanup) method to clean any resources +associated with the peer. + +If the transport fails to establish a connection with the peer that is configured +as a persistent peer, the switch spawns a routine to [reconnect to the peer](#reconnect-to-peer). +If the peer is already in the `reconnecting` state, the spawned routine has no +effect and returns immediately. +This is in fact a likely scenario, as the `reconnectToPeer` routine relies on +this same `DialPeerWithAddress` method for dialing peers. + +### Manual operation + +The `DialPeersAsync` method receives a list of peer addresses (strings) +and dials all of them in parallel. +It is invoked in two situations: + +- In the [setup](https://github.com/cometbft/cometbft/blob/29c5a062d23aaef653f11195db55c45cd9e02715/node/node.go#L985) of a node, to establish connections with every configured + persistent peer +- In the RPC package, to implement two unsafe RPC commands, not used in production: + [`DialSeeds`](https://github.com/cometbft/cometbft/blob/29c5a062d23aaef653f11195db55c45cd9e02715/rpc/core/net.go#L47) and + [`DialPeers`](https://github.com/cometbft/cometbft/blob/29c5a062d23aaef653f11195db55c45cd9e02715/rpc/core/net.go#L87) + +The received list of peer addresses to dial is parsed into `NetAddress` instances. +In case of parsing errors, the method returns. An exception is made for +DNS resolution `ErrNetAddressLookup` errors, which do not interrupt the procedure. + +As the peer addresses provided to this method are typically not known by the node, +contrarily to the addressed dialed using the `DialPeerWithAddress` method, +they are added to the node's address book, which is persisted to disk. + +The switch dials the provided peers in parallel. +The list of peer addresses is randomly shuffled, and for each peer a routine is +spawned. +Each routine sleeps for a random interval, up to 3 seconds, then invokes the +`DialPeerWithAddress` method that actually dials the peer. + +### Reconnect to peer + +The `reconnectToPeer` method is invoked when a connection attempt to a peer fails, +and the peer is configured as a persistent peer. + +The `reconnecting` synchronized map keeps the peer's in this state, identified +by their IDs (string). +This should ensure that a single instance of this method is running at any time. +The peer is kept in this map while this method is running for it: it is set on +the beginning, and removed when the method returns for whatever reason. +If the peer is already in the `reconnecting` state, nothing is done. + +The remaining of the method performs multiple connection attempts to the peer, +via `DialPeerWithAddress` method. +If a connection attempt succeeds, the methods returns and the routine finishes. +The same applies when an `ErrCurrentlyDialingOrExistingAddress` error is +returned by the dialing method, as it indicates that peer is already connected +or that another routine is attempting to (re)connect to it. + +A first set of connection attempts is done at (about) regular intervals. +More precisely, between two attempts, the switch waits for a interval of +`reconnectInterval`, hard-coded to 5 seconds, plus a random jitter up to +`dialRandomizerIntervalMilliseconds`, hard-coded to 3 seconds. +At most `reconnectAttempts`, hard-coded to 20, are made using this +regular-interval approach. + +A second set of connection attempts is done with exponentially increasing +intervals. +The base interval `reconnectBackOffBaseSeconds` is hard-coded to 3 seconds, +which is also the increasing factor. +The exponentially increasing dialing interval is adjusted as well by a random +jitter up to `dialRandomizerIntervalMilliseconds`. +At most `reconnectBackOffAttempts`, hard-coded to 10, are made using this approach. + +> Note: the first sleep interval, to which a random jitter is applied, is 1, +> not `reconnectBackOffBaseSeconds`, as the first exponent is `0`... + +## Accepting peers + +The `acceptRoutine` method is a persistent routine that handles connections +accepted by the transport configured for the switch. + +The [`Accept`](./transport.md#accept) method of the configured transport +returns a `Peer` with which an inbound connection was established. +The switch accepts a new peer if the maximum number of inbound peers was not +reached, or if the peer was configured as an _unconditional peer_. +The maximum number of inbound peers is determined by the `MaxNumInboundPeers` +configuration parameter, whose default value is `40`. + +If accepted, the peer is added to the switch using the [`addPeer`](#add-peer) method. +If the switch does not accept the established incoming connection, or if the +`addPeer` method returns an error, the switch invokes the transport's +[`Cleanup`](./transport.md#cleanup) method to clean any resources associated +with the peer. + +The transport's `Accept` method can also return a number of errors. +Errors of `ErrRejected` or `ErrFilterTimeout` types are ignored, +an `ErrTransportClosed` causes the accepted routine to be interrupted, +while other errors cause the routine to panic. + +> TODO: which errors can cause the routine to panic? + +## Add peer + +The `addPeer` method adds a peer to the switch, +either after dialing (by `addOutboundPeerWithConfig`, called by `DialPeerWithAddress`) +a peer and establishing an outbound connection, +or after accepting (`acceptRoutine`) a peer and establishing an inbound connection. + +The first step is to invoke the `filterPeer` method. +It checks whether the peer is already in the set of connected peers, +and whether any of the configured `peerFilter` methods reject the peer. +If the peer is already present or it is rejected by any filter, the `addPeer` +method fails and returns an error. + +Then, the new peer is started, added to the set of connected peers, and added +to all reactors. +More precisely, first the new peer's information is first provided to every +reactor (`InitPeer` method). +Next, the peer's sending and receiving routines are started, and the peer is +added to set of connected peers. +These two operations can fail, causing `addPeer` to return an error. +Then, in the absence of previous errors, the peer is added to every reactor (`AddPeer` method). + +> Adding the peer to the peer set returns a `ErrSwitchDuplicatePeerID` error +> when a peer with the same ID is already presented. +> +> TODO: Starting a peer could be reduced as starting the MConn with that peer? + +## Stop peer + +There are two methods for stopping a peer, namely disconnecting from it, and +removing it from the table of connected peers. + +The `StopPeerForError` method is invoked to stop a peer due to an external +error, which is provided to method as a generic "reason". + +The `StopPeerGracefully` method stops a peer in the absence of errors or, more +precisely, not providing to the switch any "reason" for that. + +In both cases the `Peer` instance is stopped, the peer is removed from all +registered reactors, and finally from the list of connected peers. + +> Issue is mentioned in +> the internal `stopAndRemovePeer` method explaining why removing the peer from +> the list of connected peers is the last action taken. + +When there is a "reason" for stopping the peer (`StopPeerForError` method) +and the peer is a persistent peer, the method creates a routine to attempt +reconnecting to the peer address, using the `reconnectToPeer` method. +If the peer is an outbound peer, the peer's address is know, since the switch +has dialed the peer. +Otherwise, the peer address is retrieved from the `NodeInfo` instance from the +connection handshake. + +## Add reactor + +The `AddReactor` method registers a `Reactor` to the switch. + +The reactor is associated to the set of channel ids it employs. +Two reactors (in the same node) cannot share the same channel id. + +There is a call back to the reactor, in which the switch passes itself to the +reactor. + +## Remove reactor + +The `RemoveReactor` method unregisters a `Reactor` from the switch. + +The reactor is disassociated from the set of channel ids it employs. + +There is a call back to the reactor, in which the switch passes `nil` to the +reactor. + +## OnStart + +This is a `BaseService` method. + +All registered reactors are started. + +The switch's `acceptRoutine` is started. + +## OnStop + +This is a `BaseService` method. + +All (connected) peers are stopped and removed from the peer's list using the +`stopAndRemovePeer` method. + +All registered reactors are stopped. + +## Broadcast + +This method broadcasts a message on a channel, by sending the message in +parallel to all connected peers. + +The method spawns a thread for each connected peer, invoking the `Send` method +provided by each `Peer` instance with the provided message and channel ID. +The return value (a boolean) of these calls are redirected to a channel that is +returned by the method. + +> TODO: detail where this method is invoked: +> +> - By the consensus protocol, in `broadcastNewRoundStepMessage`, +> `broadcastNewValidBlockMessage`, and `broadcastHasVoteMessage` +> - By the state sync protocol diff --git a/spec/p2p/v0.34/transport.md b/spec/p2p/v0.34/transport.md new file mode 100644 index 0000000000..99aebdd737 --- /dev/null +++ b/spec/p2p/v0.34/transport.md @@ -0,0 +1,222 @@ +# Transport + +The transport establishes secure and authenticated connections with peers. + +The transport [`Dial`](#dial)s peer addresses to establish outbound connections, +and [`Listen`](#listen)s in a configured network address +to [`Accept`](#accept) inbound connections from peers. + +The transport establishes raw TCP connections with peers +and [upgrade](#connection-upgrade) them into authenticated secret connections. +The established secret connection is then wrapped into `Peer` instance, which +is returned to the caller, typically the [switch](./switch.md). + +## Dial + +The `Dial` method is used by the switch to establish an outbound connection with a peer. +It is a synchronous method, which blocks until a connection is established or an error occurs. +The method returns an outbound `Peer` instance wrapping the established connection. + +The transport first dials the provided peer's address to establish a raw TCP connection. +The dialing maximum duration is determined by `dialTimeout`, hard-coded to 1 second. +The established raw connection is then submitted to a set of [filters](#connection-filtering), +which can reject it. +If the connection is not rejected, it is recorded in the table of established connections. + +The established raw TCP connection is then [upgraded](#connection-upgrade) into +an authenticated secret connection. +This procedure should ensure, in particular, that the public key of the remote peer +matches the ID of the dialed peer, which is part of peer address provided to this method. +In the absence of errors, +the established secret connection (`conn.SecretConnection` type) +and the information about the peer (`NodeInfo` record) retrieved and verified +during the version handshake, +are wrapped into an outbound `Peer` instance and returned to the switch. + +## Listen + +The `Listen` method produces a TCP listener instance for the provided network +address, and spawns an `acceptPeers` routine to handle the raw connections +accepted by the listener. +The `NetAddress` method exports the listen address configured for the transport. + +The maximum number of simultaneous incoming connections accepted by the listener +is bound to `MaxNumInboundPeer` plus the configured number of unconditional peers, +using the `MultiplexTransportMaxIncomingConnections` option, +in the node [initialization](https://github.com/cometbft/cometbft/blob/v0.34.x/node/node.go#L563). + +This method is called when a node is [started](https://github.com/cometbft/cometbft/blob/v0.34.x/node/node.go#L972). +In case of errors, the `acceptPeers` routine is not started and the error is returned. + +## Accept + +The `Accept` method returns to the switch inbound connections established with a peer. +It is a synchronous method, which blocks until a connection is accepted or an error occurs. +The method returns an inbound `Peer` instance wrapping the established connection. + +The transport handles incoming connections in the `acceptPeers` persistent routine. +This routine is started by the [`Listen`](#listen) method +and accepts raw connections from a TCP listener. +A new routine is spawned for each accepted connection. +The raw connection is submitted to a set of [filters](#connection-filtering), +which can reject it. +If the connection is not rejected, it is recorded in the table of established connections. + +The established raw TCP connection is then [upgraded](#connection-upgrade) into +an authenticated secret connection. +The established secret connection (`conn.SecretConnection` type), +the information about the peer (`NodeInfo` record) retrieved and verified +during the version handshake, +as well any error returned in this process are added to a queue of accepted connections. +This queue is consumed by the `Accept` method. + +> Handling accepted connection asynchronously was introduced due to this issue: +> + +## Connection Filtering + +The `filterConn` method is invoked for every new raw connection established by the transport. +Its main goal is avoid the transport to maintain duplicated connections with the same peer. +It also runs a set of configured connection filters. + +The transports keeps a table `conns` of established connections. +The table maps the remote address returned by a generic connection to a list of +IP addresses, to which the connection remote address is resolved. +If the remote address of the new connection is already present in the table, +the connection is rejected. +Otherwise, the connection's remote address is resolved into a list of IPs, +which are recorded in the established connections table. + +The connection and the resolved IPs are then passed through a set of connection filters, +configured via the `MultiplexTransportConnFilters` transport option. +The maximum duration for the filters execution, which is performed in parallel, +is determined by `filterTimeout`. +Its default value is 5 seconds, +which can be changed using the `MultiplexTransportFilterTimeout` transport option. + +If the connection and the resolved remote addresses are not filtered out, +the transport registers them into the `conns` table and returns. + +In case of errors, the connection is removed from the table of established +connections and closed. + +### Errors + +If the address of the new connection is already present in the `conns` table, +an `ErrRejected` error with the `isDuplicate` reason is returned. + +If the IP resolution of the connection's remote address fails, +an `AddrError` or `DNSError` error is returned. + +If any of the filters reject the connection, +an `ErrRejected` error with the `isRejected` reason is returned. + +If the filters execution times out, +an `ErrFilterTimeout` error is returned. + +## Connection Upgrade + +The `upgrade` method is invoked for every new raw connection established by the +transport that was not [filtered out](#connection-filtering). +It upgrades an established raw TCP connection into a secret authenticated +connection, and validates the information provided by the peer. + +This is a complex procedure, that can be summarized by the following three +message exchanges between the node and the new peer: + +1. Encryption: the nodes produce ephemeral key pairs and exchange ephemeral + public keys, from which are derived: (i) a pair of secret keys used to + encrypt the data exchanged between the nodes, and (ii) a challenge message. +1. Authentication: the nodes exchange their persistent public keys and a + signature of the challenge message produced with the their persistent + private keys. This allows validating the peer's persistent public key, + which plays the role of node ID. +1. Version handshake: nodes exchange and validate each other `NodeInfo` records. + This records contain, among other fields, their node IDs, the network/chain + ID they are part of, and the list of supported channel IDs. + +Steps (1) and (2) are implemented in the `conn` package. +In case of success, they produce the secret connection that is actually used by +the node to communicate with the peer. +An overview of this procedure, which implements the station-to-station (STS) +[protocol][sts-paper] ([PDF][sts-paper-pdf]), can be found [here][peer-sts]. +The maximum duration for establishing a secret connection with the peer is +defined by `handshakeTimeout`, hard-coded to 3 seconds. + +The established secret connection stores the persistent public key of the peer, +which has been validated via the challenge authentication of step (2). +If the connection being upgraded is an outbound connection, i.e., if the node has +dialed the peer, the dialed peer's ID is compared to the peer's persistent public key: +if they do not match, the connection is rejected. +This verification is not performed in the case of inbound (accepted) connections, +as the node does not know a priori the remote node's ID. + +Step (3), the version handshake, is performed by the transport. +Its maximum duration is also defined by `handshakeTimeout`, hard-coded to 3 seconds. +The version handshake retrieves the `NodeInfo` record of the new peer, +which can be rejected for multiple reasons, listed [here][peer-handshake]. + +If the connection upgrade succeeds, the method returns the established secret +connection, an instance of `conn.SecretConnection` type, +and the `NodeInfo` record of the peer. + +In case of errors, the connection is removed from the table of established +connections and closed. + +### Errors + +The timeouts for steps (1) and (2), and for step (3), are configured as the +deadline for operations on the TCP connection that is being upgraded. +If this deadline it is reached, the connection produces an +`os.ErrDeadlineExceeded` error, returned by the corresponding step. + +Any error produced when establishing a secret connection with the peer (steps 1 and 2) or +during the version handshake (step 3), including timeouts, +is encapsulated into an `ErrRejected` error with reason `isAuthFailure` and returned. + +If the upgraded connection is an outbound connection, and the peer ID learned in step (2) +does not match the dialed peer's ID, +an `ErrRejected` error with reason `isAuthFailure` is returned. + +If the peer's `NodeInfo` record, retrieved in step (3), is invalid, +or if reports a node ID that does not match peer ID learned in step (2), +an `ErrRejected` error with reason `isAuthFailure` is returned. +If it reports a node ID equals to the local node ID, +an `ErrRejected` error with reason `isSelf` is returned. +If it is not compatible with the local `NodeInfo`, +an `ErrRejected` error with reason `isIncompatible` is returned. + +## Close + +The `Close` method closes the TCP listener created by the `Listen` method, +and sends a signal for interrupting the `acceptPeers` routine. + +This method is called when a node is [stopped](https://github.com/cometbft/cometbft/blob/46badfabd9d5491c78283a0ecdeb695e21785508/node/node.go#L1019). + +## Cleanup + +The `Cleanup` method receives a `Peer` instance, +and removes the connection established with a peer from the table of established connections. +It also invokes the `Peer` interface method to close the connection associated with a peer. + +It is invoked when the connection with a peer is closed. + +## Supported channels + +The `AddChannel` method registers a channel in the transport. + +The channel ID is added to the list of supported channel IDs, +stored in the local `NodeInfo` record. + +The `NodeInfo` record is exchanged with peers in the version handshake. +For this reason, this method is not invoked with a started transport. + +> The only call to this method is performed in the `CustomReactors` constructor +> option of a node, i.e., before the node is started. +> Note that the default list of supported channel IDs, including the default reactors, +> is provided to the transport as its original `NodeInfo` record. + +[peer-sts]: https://github.com/cometbft/cometbft/blob/v0.34.x/spec/p2p/peer.md#authenticated-encryption-handshake +[peer-handshake]:https://github.com/cometbft/cometbft/blob/v0.34.x/spec/p2p/peer.md#cometbft-version-handshake +[sts-paper]: https://link.springer.com/article/10.1007/BF00124891 +[sts-paper-pdf]: https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf diff --git a/spec/p2p/v0.34/types.md b/spec/p2p/v0.34/types.md new file mode 100644 index 0000000000..6d71da03fb --- /dev/null +++ b/spec/p2p/v0.34/types.md @@ -0,0 +1,239 @@ +# Types adopted in the p2p implementation + +This document lists the packages and source files, excluding test units, that +implement the p2p layer, and summarizes the main types they implement. +Types play the role of classes in Go. + +The reference version for this documentation is the branch +[`v0.34.x`](https://github.com/cometbft/cometbft/tree/v0.34.x/p2p). + +State of August 2022. + +## Package `p2p` + +Implementation of the p2p layer of CometBFT. + +### `base_reactor.go` + +`Reactor` interface. + +`BaseReactor` implements `Reactor`. + +**Not documented yet**. + +### `conn_set.go` + +`ConnSet` interface, a "lookup table for connections and their ips". + +Internal type `connSet` implements the `ConnSet` interface. + +Used by the [transport](#transportgo) to store connected peers. + +### `errors.go` + +Defines several error types. + +`ErrRejected` enumerates a number of reason for which a peer was rejected. +Mainly produced by the [transport](#transportgo), +but also by the [switch](#switchgo). + +`ErrSwitchDuplicatePeerID` is produced by the `PeerSet` used by the [switch](#switchgo). + +`ErrSwitchConnectToSelf` is handled by the [switch](#switchgo), +but currently is not produced outside tests. + +`ErrSwitchAuthenticationFailure` is handled by the [PEX reactor](#pex_reactorgo), +but currently is not produced outside tests. + +`ErrTransportClosed` is produced by the [transport](#transportgo) +and handled by the [switch](#switchgo). + +`ErrNetAddressNoID`, `ErrNetAddressInvalid`, and `ErrNetAddressLookup` +are parsing a string to create an instance of `NetAddress`. +It can be returned in the setup of the [switch](#switchgo) +and of the [PEX reactor](#pex_reactorgo), +as well when the [transport](#transportgo) validates a `NodeInfo`, as part of +the connection handshake. + +`ErrCurrentlyDialingOrExistingAddress` is produced by the [switch](#switchgo), +and handled by the switch and the [PEX reactor](#pex_reactorgo). + +### `fuzz.go` + +For testing purposes. + +`FuzzedConnection` wraps a `net.Conn` and injects random delays. + +### `key.go` + +`NodeKey` is the persistent key of a node, namely its private key. + +The `ID` of a node is a string representing the node's public key. + +### `metrics.go` + +Prometheus `Metrics` exposed by the p2p layer. + +### `netaddress.go` + +Type `NetAddress` contains the `ID` and the network address (IP and port) of a node. + +The API of the [address book](#addrbookgo) receives and returns `NetAddress` instances. + +This source file was adapted from [`btcd`](https://github.com/btcsuite/btcd), +a Go implementation of Bitcoin. + +### `node_info.go` + +Interface `NodeInfo` stores the basic information about a node exchanged with a +peer during the handshake. + +It is implemented by `DefaultNodeInfo` type. + +The [switch](#switchgo) stores the local `NodeInfo`. + +The `NodeInfo` of connected peers is produced by the +[transport](#transportgo) during the handshake, and stored in [`Peer`](#peergo) instances. + +### `peer.go` + +Interface `Peer` represents a connected peer. + +It is implemented by the internal `peer` type. + +The [transport](#transportgo) API methods return `Peer` instances, +wrapping established secure connection with peers. + +The [switch](#switchgo) API methods receive `Peer` instances. +The switch stores connected peers in a `PeerSet`. + +The [`Reactor`](#base_reactorgo) methods, invoked by the switch, receive `Peer` instances. + +### `peer_set.go` + +Interface `IPeerSet` offers methods to access a table of [`Peer`](#peergo) instances. + +Type `PeerSet` implements a thread-safe table of [`Peer`](#peergo) instances, +used by the [switch](#switchgo). + +The switch provides limited access to this table by returing a `IPeerSet` +instance, used by the [PEX reactor](#pex_reactorgo). + +### `switch.go` + +Documented in [switch](./switch.md). + +The `Switch` implements the [peer manager](./peer_manager.md) role for inbound peers. + +[`Reactor`](#base_reactorgo)s have access to the `Switch` and may invoke its methods. +This includes the [PEX reactor](#pex_reactorgo). + +### `transport.go` + +Documented in [transport](./transport.md). + +The `Transport` interface is implemented by `MultiplexTransport`. + +The [switch](#switchgo) contains a `Transport` and uses it to establish +connections with peers. + +### `types.go` + +Aliases for p2p's `conn` package types. + +## Package `p2p.conn` + +Implements the connection between CometBFT nodes, +which is encrypted, authenticated, and multiplexed. + +### `connection.go` + +Implements the `MConnection` type and the `Channel` abstraction. + +A `MConnection` multiplexes a generic network connection (`net.Conn`) into +multiple independent `Channel`s, used by different [`Reactor`](#base_reactorgo)s. + +A [`Peer`](#peergo) stores the `MConnection` instance used to interact with a +peer, which multiplex a [`SecretConnection`](#secret_connectiongo). + +### `conn_go110.go` + +Support for go 1.10. + +### `secret_connection.go` + +Implements the `SecretConnection` type, which is an encrypted authenticated +connection built atop a raw network (TCP) connection. + +A [`Peer`](#peergo) stores the `SecretConnection` established by the transport, +which is the underlying connection multiplexed by [`MConnection`](#connectiongo). + +As briefly documented in the [transport](./transport.md#Connection-Upgrade), +a `SecretConnection` implements the Station-To-Station (STS) protocol. + +The `SecretConnection` type implements the `net.Conn` interface, +which is a generic network connection. + +## Package `p2p.mock` + +Mock implementations of [`Peer`](#peergo) and [`Reactor`](#base_reactorgo) interfaces. + +## Package `p2p.mocks` + +Code generated by `mockery`. + +## Package `p2p.pex` + +Implementation of the [PEX reactor](./pex.md). + +### `addrbook.go` + +Documented in [address book](./addressbook.md). + +This source file was adapted from [`btcd`](https://github.com/btcsuite/btcd), +a Go implementation of Bitcoin. + +### `errors.go` + +A number of errors produced and handled by the [address book](#addrbookgo). + +`ErrAddrBookNilAddr` is produced by the address book, but handled (logged) by +the [PEX reactor](#pex_reactorgo). + +`ErrUnsolicitedList` is produced and handled by the [PEX protocol](#pex_reactorgo). + +### `file.go` + +Implements the [address book](#addrbookgo) persistence. + +### `known_address.go` + +Type `knownAddress` represents an address stored in the [address book](#addrbookgo). + +### `params.go` + +Constants used by the [address book](#addrbookgo). + +### `pex_reactor.go` + +Implementation of the [PEX reactor](./pex.md), which is a [`Reactor`](#base_reactorgo). + +This includes the implementation of the [PEX protocol](./pex-protocol.md) +and of the [peer manager](./peer_manager.md) role for outbound peers. + +The PEX reactor also manages an [address book](#addrbookgo) instance. + +## Package `p2p.trust` + +Go documentation of `Metric` type: + +> // Metric - keeps track of peer reliability +> // See cometbft/docs/architecture/adr-006-trust-metric.md for details + +Not imported by any other CometBFT source file. + +## Package `p2p.upnp` + +This package implementation was taken from "taipei-torrent". + +It is used by the `probe-upnp` command of the CometBFT binary. diff --git a/spec/rpc/README.md b/spec/rpc/README.md index 7cdf417dca..ff9ce2ce9e 100644 --- a/spec/rpc/README.md +++ b/spec/rpc/README.md @@ -7,18 +7,18 @@ parent: # RPC spec -This file defines the JSON-RPC spec of Tendermint. This is meant to be implemented by all clients. +This file defines the JSON-RPC spec of CometBFT. This is meant to be implemented by all clients. ## Support - | | [Tendermint-Go](https://github.com/tendermint/tendermint/) | [endermint-Rs](https://github.com/informalsystems/tendermint-rs) | + | | [CometBFT](https://github.com/cometbft/cometbft/) | [Tendermint-Rs](https://github.com/informalsystems/tendermint-rs) | |--------------|:----------------------------------------------------------:|:----------------------------------------------------------------:| | JSON-RPC 2.0 | ✅ | ✅ | | HTTP | ✅ | ✅ | | HTTPS | ✅ | ❌ | | WS | ✅ | ✅ | - | Routes | [Tendermint-Go](https://github.com/tendermint/tendermint/) | [Tendermint-Rs](https://github.com/informalsystems/tendermint-rs) | + | Routes | [CometBFT](https://github.com/cometbft/cometbft/) | [Tendermint-Rs](https://github.com/informalsystems/tendermint-rs) | |-----------------------------------------|:----------------------------------------------------------:|:-----------------------------------------------------------------:| | [Health](#health) | ✅ | ✅ | | [Status](#status) | ✅ | ✅ | @@ -44,7 +44,7 @@ This file defines the JSON-RPC spec of Tendermint. This is meant to be implement ## Timestamps -Timestamps in the RPC layer of Tendermint follows RFC3339Nano. The RFC3339Nano format removes trailing zeros from the seconds field. +Timestamps in the RPC layer of CometBFT follows RFC3339Nano. The RFC3339Nano format removes trailing zeros from the seconds field. This means if a block has a timestamp like: `1985-04-12T23:20:50.5200000Z`, the value returned in the RPC will be `1985-04-12T23:20:50.52Z`. @@ -86,7 +86,7 @@ curl -X POST https://localhost:26657 -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\ ### Status -Get Tendermint status including node info, pubkey, latest block hash, app hash, block height and time. +Get CometBFT status including node info, pubkey, latest block hash, app hash, block height and time. #### Parameters