|
|
- # Light Client Verification
-
- The light client implements a read operation of a
- [header][TMBC-HEADER-link] from the [blockchain][TMBC-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
- 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],
- 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.
-
- The challenge addressed here is that the light client might have a
- block of height *h1* and needs to read the block of height *h2*
- 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].
-
- # Status
-
- This document is thoroughly reviewed, and the protocol has been
- formalized in TLA+ and model checked.
-
- ## Issues that need to be addressed
-
- As it is part of the larger light node, its data structures and
- functions interact with the fork dectection functionality of the light
- client. As a result of the work on
- [Pull Request 479](https://github.com/informalsystems/tendermint-rs/pull/479) we
- established the need for an update in the data structures in [Issue 499](https://github.com/informalsystems/tendermint-rs/issues/499). This
- will not change the verification logic, but it will record information
- about verification that can be used in fork detection (in particular
- in computing more efficiently the proof of fork).
-
- # Outline
-
- - [Part I](#part-i---tendermint-blockchain): Introduction of
- relevant terms of the Tendermint
- 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
- 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):
- Provides a mathematical definition of the problem statement in
- its sequential form, that is, ignoring the distributed aspect of
- the implementation of the blockchain.
-
- - [Part III](#part-iii---light-client-as-distributed-system): Distributed
- aspects of the light client, system assumptions and temporal
- logic specifications.
-
- - [Incentives](#incentives): how faulty full nodes may benefit from
- misbehaving and how correct full nodes benefit from cooperating.
-
- - [Computational Model](#Computational-Model):
- timing and correctness assumptions.
-
- - [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,
- variables used by the protocol, auxiliary functions
-
- - [Core Verification](#core-verification): gives an outline of the solution,
- and details of the functions used (with preconditions,
- postconditions, error conditions).
-
- - [Liveness Scenarios](#liveness-scenarios): when the light
- client makes progress depends heavily on the changes in the
- validator sets of the blockchain. We discuss some typical scenarios.
-
- - [Part V](#part-v---supporting-the-ibc-relayer): The above parts
- focus on a common case where the last verified block has height *h1*
- and the
- requested height *h2* satisfies *h2 > h1*. For IBC, there are
- scenarios where this might not be the case. In this part, we provide
- some preliminaries for supporting this. As not all details of the
- IBC requirements are clear by now, we do not provide a complete
- specification at this point. We mark with "Open Question" points
- that need to be addressed in order to finalize this specification.
- It should be noted that the technically
- most challenging case is the one specified in Part IV.
-
- 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
- - SEQ: for sequential specifications
- - LCV: Lightclient Verification
- - LIVE: liveness
- - SAFE: safety
- - FUNC: function
- - INV: invariant
- - A: assumption
-
- # Part I - Tendermint Blockchain
-
- ## Header Fields necessary for the Light Client
-
- #### **[TMBC-HEADER.1]**
-
- A set of blockchain transactions is stored in a data structure called
- *block*, which contains a field called *header*. (The data structure
- *block* is defined [here][block]). As the header contains hashes to
- 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]**
-
- 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]**
-
- A header contains the following fields:
-
- - `Height`: non-negative integer
- - `Time`: time (integer)
- - `LastBlockID`: Hashvalue
- - `LastCommit` DomainCommit
- - `Validators`: DomainVal
- - `NextValidators`: DomainVal
- - `Data`: DomainTX
- - `AppState`: DomainApp
- - `LastResults`: DomainRes
-
- #### **[TMBC-SEQ.1]**
-
- The Tendermint blockchain is a list *chain* of headers.
-
- #### **[TMBC-VALIDATOR-PAIR.1]**
-
- Given a full node, a
- *validator pair* is a pair *(peerID, voting_power)*, where
-
- - *peerID* is the PeerID (public key) of a full node,
- - *voting_power* is an integer (representing the full node's
- voting power in a certain consensus instance).
-
- > In the Golang implementation the data type for *validator
- pair* is called `Validator`
-
- #### **[TMBC-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]**
-
- A *vote* contains a `prevote` or `precommit` message sent and signed by
- a validator node during the execution of [consensus][arXiv]. Each
- message contains the following fields
-
- - `Type`: prevote or precommit
- - `Height`: positive integer
- - `Round` a positive integer
- - `BlockID` a Hashvalue of a block (not necessarily a block of the chain)
-
- #### **[TMBC-COMMIT.1]**
-
- A commit is a set of `precommit` message.
-
- ## Tendermint Failure Model
-
- #### **[TMBC-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
- 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]**
-
- A Tendermint blockchain has the following configuration parameters:
-
- - *unbondingPeriod*: a time duration.
- - *trustingPeriod*: a time duration smaller than *unbondingPeriod*.
-
- #### **[TMBC-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]**
-
- 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]
- - For every validator pair *(n,p)* in *CorrV*, it holds *correctUntil(n,
- h.Time + trustingPeriod)*; cf. [TMBC-CORRECT.1]
-
- > The definition of correct
- > [**[TMBC-CORRECT.1]**][TMBC-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]**
-
- Every correct full node locally stores a prefix of the
- current list of headers from [**[TMBC-SEQ.1]**][TMBC-SEQ-link].
-
- ## What the Light Client Checks
-
- > From [TMBC-FM-2THIRDS.1] we directly derive the following observation:
-
- #### **[TMBC-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
-
- - *t - trustingPeriod < tb.Time < t*
- - the voting power in tb.NextValidators of nodes in *N* is more
- than 1/3 of *TotalVotingPower(tb.NextValidators)*
-
- > The following describes how a commit for a given block *b* must look
- > like.
-
- #### **[TMBC-SOUND-DISTR-POSS-COMMIT.1]**
-
- For a block *b*, each element *pc* of *PossibleCommit(b)* satisfies:
-
- - *pc* contains only votes (cf. [TMBC-VOTE.1])
- by validators from *b.Validators*
- - the sum of the voting powers in *pc* is greater than 2/3
- *TotalVotingPower(b.Validators)*
- - and there is an *r* such that each vote *v* in *pc* satisfies
- - v.Type = precommit
- - v.Height = b.Height
- - v.Round = r
- - v.blockID = hash(b)
-
- > The following property comes from the validity of the [consensus][arXiv]: A
- > correct validator node only sends `prevote` or `precommit`, if
- > `BlockID` of the new (to-be-decided) block is equal to the hash of
- > the last block.
-
- #### **[TMBC-VAL-COMMIT.1]**
-
- If for a block *b*, a commit *c*
-
- - contains at least one validator pair *(v,p)* such that *v* is a
- **correct** validator node, and
- - is contained in *PossibleCommit(b)*
-
- then the block *b* is on the blockchain.
-
- ## Context of this document
-
- In this document we specify the light client verification component,
- called *Core Verification*. The *Core Verification* communicates with
- a full node. As full nodes may be faulty, it cannot trust the
- received information, but the light client has to check whether the
- 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
- 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*
- contains a correct node using *tb*.
-
- # Part II - Sequential Definition of the Verification Problem
-
- ## Verification Informal Problem statement
-
- 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
- particular, a header that was not generated by the blockchain should
- never be stored.
-
- ## Sequential Problem statement
-
- #### **[LCV-SEQ-LIVE.1]**
-
- The *Verifier* gets as input a height *targetHeight*, and eventually stores the
- header of height *targetHeight* of the blockchain.
-
- #### **[LCV-SEQ-SAFE.1]**
-
- The *Verifier* never stores a header which is not in the blockchain.
-
- # Part III - Light Client as Distributed System
-
- ## Incentives
-
- Faulty full nodes may benefit from lying to the light client, by making the
- light client accept a block that deviates (e.g., contains additional
- transactions) from the one generated by Tendermint consensus.
- Users using the light client might be harmed by accepting a forged header.
-
- The [fork detector][fork-detector] of the light client may help the
- correct full nodes to understand whether their header is a good one.
- Hence, in combination with the light client detector, the correct full
- nodes have the incentive to respond. We can thus base liveness
- arguments on the assumption that correct full nodes reliably talk to
- the light client.
-
- ## Computational Model
-
- #### **[LCV-A-PEER.1]**
-
- The verifier communicates with a full node called *primary*. No assumption is made about the full node (it may be correct or faulty).
-
- #### **[LCV-A-COMM.1]**
-
- Communication between the light client and a correct full node is
- reliable and bounded in time. Reliable communication means that
- messages are not lost, not duplicated, and eventually delivered. There
- is a (known) end-to-end delay *Delta*, such that if a message is sent
- at time *t* then it is received and processes by time *t + Delta*.
- This implies that we need a timeout of at least *2 Delta* for remote
- procedure calls to ensure that the response of a correct peer arrives
- before the timeout expires.
-
- #### **[LCV-A-TFM.1]**
-
- The Tendermint blockchain satisfies the Tendermint failure model [**[TMBC-FM-2THIRDS.1]**][TMBC-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
- blockchain that satisfies the soundness requirements (that is, the
- validation rules in [[block]]).
-
- ## Distributed Problem Statement
-
- ### Two Kinds of Termination
-
- We do not assume that *primary* is correct. Under this assumption no
- protocol can guarantee the combination of the sequential
- properties. Thus, in the (unreliable) distributed setting, we consider
- two kinds of termination (successful and failure) and we will specify
- below under what (favorable) conditions *Core Verification* ensures to
- terminate successfully, and satisfy the requirements of the sequential
- problem statement:
-
- #### **[LCV-DIST-TERM.1]**
-
- *Core Verification* either *terminates
- successfully* or it *terminates with failure*.
-
- ### Design choices
-
- #### **[LCV-DIST-STORE.1]**
-
- *Core Verification* has a local data structure called *LightStore* that
- contains light blocks (that contain a header). For each light block we
- record whether it is verified.
-
- #### **[LCV-DIST-PRIMARY.1]**
-
- *Core Verification* has a local variable *primary* that contains the PeerID of a full node.
-
- #### **[LCV-DIST-INIT.1]**
-
- *LightStore* is initialized with a header *trustedHeader* that was correctly
- generated by the Tendermint consensus. We say *trustedHeader* is verified.
-
- ### Temporal Properties
-
- #### **[LCV-DIST-SAFE.1]**
-
- It is always the case that every verified header in *LightStore* was
- generated by an instance of Tendermint consensus.
-
- #### **[LCV-DIST-LIVE.1]**
-
- From time to time, a new instance of *Core Verification* is called with a
- height *targetHeight* greater than the height of any header in *LightStore*.
- Each instance must eventually terminate.
-
- - If
- - the *primary* is correct (and locally has the block of
- *targetHeight*), and
- - *LightStore* always contains a verified header whose age is less than the
- trusting period,
- then *Core Verification* adds a verified header *hd* with height
- *targetHeight* to *LightStore* and it **terminates successfully**
-
- > 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)
- > 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).
- > Note that for liveness, initially having a *trustedHeader* within
- > the *trustinPeriod* is not sufficient. However, as this
- > specification will leave some freedom with respect to the strategy
- > in which order to download intermediate headers, we do not give a
- > more precise liveness specification here. After giving the
- > specification of the protocol, we will discuss some liveness
- > scenarios [below](#liveness-scenarios).
-
- ### Solving the sequential specification
-
- 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)
-
- 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)
- )
-
- # Part IV - Light Client Verification Protocol
-
- We provide a specification for Light Client Verification. The local
- code for verification is presented by a sequential function
- `VerifyToTarget` to highlight the control flow of this functionality.
- We note that if a different concurrency model is considered for
- an implementation, the sequential flow of the function may be
- implemented with mutexes, etc. However, the light client verification
- is partitioned into three blocks that can be implemented and tested
- independently:
-
- - `FetchLightBlock` is called to download a light block (header) of a
- given height from a peer.
- - `ValidAndVerified` is a local code that checks the header.
- - `Schedule` decides which height to try to verify next. We keep this
- underspecified as different implementations (currently in Goland and
- Rust) may implement different optimizations here. We just provide
- necessary conditions on how the height may evolve.
-
- <!-- > `ValidAndVerified` is the function that is sometimes called "Light -->
- <!-- > Client" in the IBC context. -->
-
- ## Definitions
-
- ### Data Types
-
- The core data structure of the protocol is the LightBlock.
-
- #### **[LCV-DATA-LIGHTBLOCK.1]**
-
- ```go
- type LightBlock struct {
- Header Header
- Commit Commit
- Validators ValidatorSet
- NextValidators ValidatorSet
- Provider PeerID
- }
- ```
-
- #### **[LCV-DATA-LIGHTSTORE.1]**
-
- LightBlocks are stored in a structure which stores all LightBlock from
- initialization or received from peers.
-
- ```go
- type LightStore struct {
- ...
- }
-
- ```
-
- Each LightBlock is in one of the following states:
-
- ```go
- type VerifiedState int
-
- const (
- StateUnverified = iota + 1
- StateVerified
- StateFailed
- StateTrusted
- )
- ```
-
- > Only the detector module sets a lightBlock state to `StateTrusted`
- > and only if it was `StateVerified` before.
-
- The LightStore exposes the following functions to query stored LightBlocks.
-
- ```go
- func (ls LightStore) Get(height Height) (LightBlock, bool)
- ```
-
- - Expected postcondition
- - returns a LightBlock at a given height or false in the second argument if
- the LightStore does not contain the specified LightBlock.
-
- ```go
- func (ls LightStore) LatestVerified() LightBlock
- ```
-
- - Expected postcondition
- - returns the highest light block whose state is `StateVerified`
- or `StateTrusted`
-
- #### **[LCV-FUNC-UPDATE.1]**
-
- ```go
- func (ls LightStore) Update(lightBlock LightBlock, verfiedState VerifiedState)
- ```
-
- - Expected postcondition
- - The state of the LightBlock is set to *verifiedState*.
-
- > The following function is used only in the detector specification
- > listed here for completeness.
-
- ```go
- func (ls LightStore) LatestTrusted() LightBlock
- ```
-
- - Expected postcondition
- - returns the highest light block that has been verified and
- checked by the detector.
-
- ### Inputs
-
- - *lightStore*: stores light blocks that have been downloaded and that
- passed verification. Initially it contains a light block with
- *trustedHeader*.
- - *primary*: peerID
- - *targetHeight*: the height of the needed header
-
- ### 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].
- - *clockDrift*: a time duration. Correction parameter dealing with only approximately synchronized clocks.
-
- ### Variables
-
- - *nextHeight*: initially *targetHeight*
- > *nextHeight* should be thought of the "height of the next header we need
- > to download and verify"
-
- ### Assumptions
-
- #### **[LCV-A-INIT.1]**
-
- - *trustedHeader* is from the blockchain
-
- - *targetHeight > LightStore.LatestVerified.Header.Height*
-
- ### Invariants
-
- #### **[LCV-INV-TP.1]**
-
- It is always the case that *LightStore.LatestTrusted.Header.Time > now - trustingPeriod*.
-
- > If the invariant is violated, the light client does not have a
- > header it can trust. A trusted header must be obtained externally,
- > its trust can only be based on social consensus.
-
- ### Used Remote Functions
-
- We use the functions `commit` and `validators` that are provided
- by the [RPC client for Tendermint][RPC].
-
- ```go
- func Commit(height int64) (SignedHeader, error)
- ```
-
- - Implementation remark
- - RPC to full node *n*
- - JSON sent:
-
- ```javascript
- // POST /commit
- {
- "jsonrpc": "2.0",
- "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request
- "method": "commit",
- "params": {
- "height": 1234
- }
- }
- ```
-
- - Expected precondition
- - header of `height` exists on blockchain
- - Expected postcondition
- - if *n* is correct: Returns the signed header of height `height`
- from the blockchain if communication is timely (no timeout)
- - if *n* is faulty: Returns a signed header with arbitrary content
- - Error condition
- - if *n* is correct: precondition violated or timeout
- - if *n* is faulty: arbitrary error
-
- ----
-
- ```go
- func Validators(height int64) (ValidatorSet, error)
- ```
-
- - Implementation remark
- - RPC to full node *n*
- - JSON sent:
-
- ```javascript
- // POST /validators
- {
- "jsonrpc": "2.0",
- "id": "ccc84631-dfdb-4adc-b88c-5291ea3c2cfb", // UUID v4, unique per request
- "method": "validators",
- "params": {
- "height": 1234
- }
- }
- ```
-
- - Expected precondition
- - header of `height` exists on blockchain
- - Expected postcondition
- - if *n* is correct: Returns the validator set of height `height`
- from the blockchain if communication is timely (no timeout)
- - if *n* is faulty: Returns arbitrary validator set
- - Error condition
- - if *n* is correct: precondition violated or timeout
- - if *n* is faulty: arbitrary error
-
- ----
-
- ### Communicating Function
-
- #### **[LCV-FUNC-FETCH.1]**
-
- ```go
- func FetchLightBlock(peer PeerID, height Height) LightBlock
- ```
-
- - Implementation remark
- - RPC to peer at *PeerID*
- - calls `Commit` for *height* and `Validators` for *height* and *height+1*
- - Expected precondition
- - `height` is less than or equal to height of the peer **[LCV-IO-PRE-HEIGHT.1]**
- - Expected postcondition:
- - if *node* is correct:
- - Returns the LightBlock *lb* of height `height`
- that is consistent with the blockchain
- - *lb.provider = peer* **[LCV-IO-POST-PROVIDER.1]**
- - *lb.Header* is a header consistent with the blockchain
- - *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]
- - Error condition
- - if *n* is correct: precondition violated
- - if *n* is faulty: arbitrary error
- - if *lb.provider != peer*
- - times out after 2 Delta (by assumption *n* is faulty)
-
- ----
-
- ## Core Verification
-
- ### Outline
-
- The `VerifyToTarget` is the main function and uses the following functions.
-
- - `FetchLightBlock` is called to download the next light block. It is
- the only function that communicates with other nodes
- - `ValidAndVerified` checks whether header is valid and checks if a
- new lightBlock should be trusted
- based on a previously verified lightBlock.
- - `Schedule` decides which height to try to verify next
-
- In the following description of `VerifyToTarget` we do not deal with error
- handling. If any of the above function returns an error, VerifyToTarget just
- passes the error on.
-
- #### **[LCV-FUNC-MAIN.1]**
-
- ```go
- func VerifyToTarget(primary PeerID, lightStore LightStore,
- targetHeight Height) (LightStore, Result) {
-
- nextHeight := targetHeight
-
- for lightStore.LatestVerified.height < targetHeight {
-
- // Get next LightBlock for verification
- current, found := lightStore.Get(nextHeight)
- if !found {
- current = FetchLightBlock(primary, nextHeight)
- lightStore.Update(current, StateUnverified)
- }
-
- // Verify
- verdict = ValidAndVerified(lightStore.LatestVerified, current)
-
- // Decide whether/how to continue
- if verdict == OK {
- lightStore.Update(current, StateVerified)
- }
- else if verdict == CANNOT_VERIFY {
- // do nothing
- // the light block current passed validation, but the validator
- // set is too different to verify it. We keep the state of
- // current at StateUnverified. For a later iteration, Schedule
- // might decide to try verification of that light block again.
- }
- else {
- // verdict is some error code
- lightStore.Update(current, StateFailed)
- // possibly remove all LightBlocks from primary
- return (lightStore, ResultFailure)
- }
- nextHeight = Schedule(lightStore, nextHeight, targetHeight)
- }
- return (lightStore, ResultSuccess)
- }
- ```
-
- - Expected precondition
- - *lightStore* contains a LightBlock within the *trustingPeriod* **[LCV-PRE-TP.1]**
- - *targetHeight* is greater than the height of all the LightBlocks in *lightStore*
- - Expected postcondition:
- - returns *lightStore* that contains a LightBlock that corresponds to a block
- of the blockchain of height *targetHeight*
- (that is, the LightBlock has been added to *lightStore*) **[LCV-POST-LS.1]**
- - 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
-
- ### Details of the Functions
-
- #### **[LCV-FUNC-VALID.1]**
-
- ```go
- func ValidAndVerified(trusted LightBlock, untrusted LightBlock) Result
- ```
-
- - Expected precondition:
- - *untrusted* is valid, that is, satisfies the soundness [checks][block]
- - *untrusted* is **well-formed**, that is,
- - *untrusted.Header.Time < now + clockDrift*
- - *untrusted.Validators = hash(untrusted.Header.Validators)*
- - *untrusted.NextValidators = hash(untrusted.Header.NextValidators)*
- - *trusted.Header.Time > now - trustingPeriod*
- - *trusted.Commit* is a commit for the header
- *trusted.Header*, i.e., it contains
- the correct hash of the header, and +2/3 of signatures
- - the `Height` and `Time` of `trusted` are smaller than the Height and
- `Time` of `untrusted`, respectively
- - the *untrusted.Header* is well-formed (passes the tests from
- [[block]]), and in particular
- - if the untrusted header `unstrusted.Header` is the immediate
- successor of `trusted.Header`, then it holds that
- - *trusted.Header.NextValidators =
- untrusted.Header.Validators*, and
- moreover,
- - *untrusted.Header.Commit*
- - contains signatures by more than two-thirds of the validators
- - contains no signature from nodes that are not in *trusted.Header.NextValidators*
- - Expected postcondition:
- - Returns `OK`:
- - if *untrusted* is the immediate successor of *trusted*, or otherwise,
- - if the signatures of a set of validators that have more than
- *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])
- - Returns `CANNOT_VERIFY` 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]
- fails and header is does not violate the soundness
- checks [[block]]).
- - Error condition:
- - if precondition violated
-
- ----
-
- #### **[LCV-FUNC-SCHEDULE.1]**
-
- ```go
- func Schedule(lightStore, nextHeight, targetHeight) Height
- ```
-
- - Implementation remark: If picks the next height to be verified.
- We keep the precise choice of the next header under-specified. It is
- subject to performance optimizations that do not influence the correctness
- - Expected postcondition: **[LCV-SCHEDULE-POST.1]**
- Return *H* s.t.
- 1. if *lightStore.LatestVerified.Height = nextHeight* and
- *lightStore.LatestVerified < targetHeight* then
- *nextHeight < H <= targetHeight*
- 2. if *lightStore.LatestVerified.Height < nextHeight* and
- *lightStore.LatestVerified.Height < targetHeight* then
- *lightStore.LatestVerified.Height < H < nextHeight*
- 3. if *lightStore.LatestVerified.Height = targetHeight* then
- *H = targetHeight*
-
- > Case i. captures the case where the light block at height *nextHeight*
- > has been verified, and we can choose a height closer to the *targetHeight*.
- > As we get the *lightStore* as parameter, the choice of the next height can
- > depend on the *lightStore*, e.g., we can pick a height for which we have
- > already downloaded a light block.
- > In Case ii. the header of *nextHeight* could not be verified, and we need to pick a smaller height.
- > In Case iii. is a special case when we have verified the *targetHeight*.
-
- ### Solving the distributed specification
-
- *trustedStore* is implemented by the light blocks in lightStore that
- have the state *StateVerified*.
-
- #### Argument for [**[LCV-DIST-SAFE.1]**](#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]
- - Only if `ValidAndVerified` returns with `OK`, the state of a light block is
- set to *StateVerified*.
-
- #### Argument for [**[LCV-DIST-LIVE.1]**](#lcv-dist-life)
-
- - If *primary* is correct,
- - `FetchLightBlock` will always return a light block consistent
- with the blockchain
- - `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
- 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
- changes of the validator sets on the blockchain.
- We will give some examples [below](#liveness-scenarios).
- - If *primary* is faulty,
- - it either provides headers that pass all the tests, and we
- return with the header
- - it provides one header that fails a test, core verification
- **terminates with failure**.
- - it times out and core verification
- **terminates with failure**.
-
- ## Liveness Scenarios
-
- The liveness argument above assumes [**[LCV-INV-TP.1]**](#LCV-INV-TP.1)
-
- which requires that there is a header that does not expire before the
- target height is reached. Here we discuss scenarios to ensure this.
-
- Let *startHeader* be *LightStore.LatestVerified* when core
- verification is called (*trustedHeader*) and *startTime* be the time
- core verification is invoked.
-
- In order to ensure liveness, *LightStore* always needs to contain a
- verified (or initially trusted) header whose time is within the
- trusting period. To ensure this, core verification needs to add new
- headers to *LightStore* and verify them, before all headers in
- *LightStore* expire.
-
- #### Many changes in validator set
-
- Let's consider `Schedule` implements
- bisection, that is, it halves the distance.
- Assume the case where the validator set changes completely in each
- block. Then the
- method in this specification needs to
- sequentially verify all headers. That is, for
-
- - *W = log_2 (targetHeight - startHeader.Height)*,
-
- *W* headers need to be downloaded and checked before the
- header of height *startHeader.Height + 1* is added to *LightStore*.
-
- - Let *Comp*
- be the local computation time needed to check headers and signatures
- for one header.
- - Then we need in the worst case *Comp + 2 Delta* to download and
- check one header.
- - Then the first time a verified header could be added to *LightStore* is
- startTime + W * (Comp + 2 Delta)
- - [TP.1] However, it can only be added if we still have a header in
- *LightStore*,
- which is not
- expired, that is only the case if
- - startHeader.Time > startTime + WCG * (Comp + 2 Delta) -
- trustingPeriod,
- - that is, if core verification is started at
- startTime < startHeader.Time + trustingPeriod - WCG * (Comp + 2 Delta)
-
- - one may then do an inductive argument from this point on, depending
- on the implementation of `Schedule`. We may have to account for the
- headers that are already
- downloaded, but they are checked against the new *LightStore.LatestVerified*.
-
- > We observe that
- > the worst case time it needs to verify the header of height
- > *targetHeight* depends mainly on how frequent the validator set on the
- > blockchain changes. That core verification terminates successfully
- > crucially depends on the check [TP.1], that is, that the headers in
- > *LightStore* do not expire in the time needed to download more
- > headers, which depends on the creation time of the headers in
- > *LightStore*. That is, termination of core verification is highly
- > depending on the data stored in the blockchain.
- > The current light client core verification protocol exploits that, in
- > practice, changes in the validator set are rare. For instance,
- > consider the following scenario.
-
- #### No change in validator set
-
- If on the blockchain the validator set of the block at height
- *targetHeight* is equal to *startHeader.NextValidators*:
-
- - there is one round trip in `FetchLightBlock` to download the light
- block
- of height
- *targetHeight*, and *Comp* to check it.
- - as the validator sets are equal, `Verify` returns `OK`, if
- *startHeader.Time > now - trustingPeriod*.
- - that is, if *startTime < startHeader.Header.Time + trustingPeriod -
- 2 Delta - Comp*, then core verification terminates successfully
-
- # 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
- 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
- presentation of the algorithm as initially
- `lightStore.LatestVerified()` is less than *targetHeight*, and in the
- process of verification `lightStore.LatestVerified()` increases until
- *targetHeight* is reached.
-
- For [IBC][ibc-rs] it might be that some "older" header is
- needed, that is, *targetHeight < lightStore.LatestVerified()*. In this section we present a preliminary design, and we mark some
- remaining open questions.
- If *targetHeight < lightStore.LatestVerified()* our design separates
- the following cases:
-
- - A previous instance of `VerifyToTarget` has already downloaded the
- light block of *targetHeight*. There are two cases
- - the light block has been verified
- - the light block has not been verified yet
- - No light block of *targetHeight* had been downloaded before. There
- are two cases:
- - there exists a verified light block of height less than *targetHeight*
- - otherwise. In this case we need to do "backwards verification"
- using the hash of the previous block in the `LastBlockID` field
- of a header.
-
- **Open Question:** what are the security assumptions for backward
- verification. Should we check that the light block we verify from
- (and/or the checked light block) is within the trusting period?
-
- The design just presents the above case
- distinction as a function, and defines some auxiliary functions in the
- same way the protocol was presented in
- [Part IV](#part-iv---light-client-verification-protocol).
-
- ```go
- func (ls LightStore) LatestPrevious(height Height) (LightBlock, bool)
- ```
-
- - Expected postcondition
- - returns a light block *lb* that satisfies:
- - *lb* is in lightStore
- - *lb* is verified and not expired
- - *lb.Header.Height < height*
- - for all *b* in lightStore s.t. *b* is verified and not expired it
- holds *lb.Header.Height >= b.Header.Height*
- - *false* in the second argument if
- the LightStore does not contain such an *lb*.
-
- ```go
- func (ls LightStore) MinVerified() (LightBlock, bool)
- ```
-
- - Expected postcondition
- - returns a light block *lb* that satisfies:
- - *lb* is in lightStore
- - *lb* is verified **Open Question:** replace by trusted?
- - *lb.Header.Height* is minimal in the lightStore
- - **Open Question:** according to this, it might be expired (outside the
- trusting period). This approach appears safe. Are there reasons we
- should not do that?
- - *false* in the second argument if
- the LightStore does not contain such an *lb*.
-
- If a height that is smaller than the smallest height in the lightstore
- is required, we check the hashes backwards. This is done with the
- following function:
-
- #### **[LCV-FUNC-BACKWARDS.1]**
-
- ```go
- func Backwards (primary PeerID, lightStore LightStore, targetHeight Height)
- (LightStore, Result) {
-
- lb,res = lightStore.MinVerified()
- if res = false {
- return (lightStore, ResultFailure)
- }
-
- latest := lb.Header
- for i := lb.Header.height - 1; i >= targetHeight; i-- {
- // here we download height-by-height. We might first download all
- // headers down to targetHeight and then check them.
- current := FetchLightBlock(primary,i)
- if (hash(current) != latest.Header.LastBlockId) {
- return (lightStore, ResultFailure)
- }
- else {
- lightStore.Update(current, StateVerified)
- // **Open Question:** Do we need a new state type for
- // backwards verified light blocks?
- }
- latest = current
- }
- return (lightStore, ResultSuccess)
- }
- ```
-
- The following function just decided based on the required height which
- method should be used.
-
- #### **[LCV-FUNC-IBCMAIN.1]**
-
- ```go
- func Main (primary PeerID, lightStore LightStore, targetHeight Height)
- (LightStore, Result) {
-
- b1, r1 = lightStore.Get(targetHeight)
- if r1 = true and b1.State = StateVerified {
- // block already there
- return (lightStore, ResultSuccess)
- }
-
- if targetHeight > lightStore.LatestVerified.height {
- // case of Part IV
- return VerifyToTarget(primary, lightStore, targetHeight)
- }
- else {
- b2, r2 = lightStore.LatestPrevious(targetHeight);
- if r2 = true {
- // make auxiliary lightStore auxLS to call VerifyToTarget.
- // VerifyToTarget uses LatestVerified of the given lightStore
- // For that we need:
- // auxLS.LatestVerified = lightStore.LatestPrevious(targetHeight)
- auxLS.Init;
- auxLS.Update(b2,StateVerified);
- if r1 = true {
- // we need to verify a previously downloaded light block.
- // we add it to the auxiliary store so that VerifyToTarget
- // does not download it again
- auxLS.Update(b1,b1.State);
- }
- auxLS, res2 = VerifyToTarget(primary, auxLS, targetHeight)
- // move all lightblocks from auxLS to lightStore,
- // maintain state
- // we do that whether VerifyToTarget was successful or not
- for i, s range auxLS {
- lighStore.Update(s,s.State)
- }
- return (lightStore, res2)
- }
- else {
- return Backwards(primary, lightStore, targetHeight)
- }
- }
- }
- ```
- <!-- - Expected postcondition: -->
- <!-- - if targetHeight > lightStore.LatestVerified.height then -->
- <!-- return VerifyToTarget(primary, lightStore, targetHeight) -->
- <!-- - if targetHeight = lightStore.LatestVerified.height then -->
- <!-- return (lightStore, ResultSuccess) -->
- <!-- - if targetHeight < lightStore.LatestVerified.height -->
- <!-- - let b2 be in lightStore -->
- <!-- - that is verified and not expired -->
- <!-- - b2.Header.Height < targetHeight -->
- <!-- - for all b in lightStore s.t. b is verified and not expired it -->
- <!-- holds b2.Header.Height >= b.Header.Height -->
- <!-- - if b2 does not exists -->
- <!-- return Backwards(primary, lightStore, targetHeight) -->
- <!-- - if b2 exists -->
- <!-- - make auxiliary light store auxLS containing only b2 -->
-
- <!-- VerifyToTarget(primary, auxLS, targetHeight) -->
- <!-- - if b2 -->
-
- # References
-
- [[block]] Specification of the block data structure.
-
- [[RPC]] RPC client for Tendermint
-
- [[fork-detector]] The specification of the light client fork detector.
-
- [[fullnode]] Specification of the full node API
-
- [[ibc-rs]] Rust implementation of IBC modules and relayer.
-
- [[lightclient]] The light client ADR [77d2651 on Dec 27, 2019].
-
- [RPC]: https://docs.tendermint.com/master/rpc/
-
- [block]: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/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
-
- [lightclient]: https://github.com/interchainio/tendermint-rs/blob/e2cb9aca0b95430fca2eac154edddc9588038982/docs/architecture/adr-002-lite-client.md
- [fork-detector]: https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_001_reviewed.md
- <!-- [fullnode]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md -->
-
- [ibc-rs]:https://github.com/informalsystems/ibc-rs
-
- <!-- [FN-LuckyCase-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-luckycase -->
-
- [blockchain-validator-set]: https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md#data-structures
- [fullnode-data-structures]: https://github.com/tendermint/spec/blob/master/spec/core/data_structures.md
-
- <!-- [FN-ManifestFaulty-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-manifestfaulty -->
-
- [arXiv]: https://arxiv.org/abs/1807.04938
|