github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/spec/light-client/supervisor/supervisor_001_draft.md (about)

     1  <!-- markdown-link-check-disable -->
     2  
     3  # Draft of Light Client Supervisor for discussion
     4  
     5  ## TODOs
     6  
     7  This specification in done in parallel with updates on the
     8  verification specification. So some hyperlinks have to be placed to
     9  the correct files eventually.
    10  
    11  # Light Client Sequential Supervisor
    12  
    13  The light client implements a read operation of a
    14  [header](TMBC-HEADER-link) from the [blockchain](TMBC-SEQ-link), by
    15  communicating with full nodes, a so-called primary and several
    16  so-called witnesses. As some full nodes may be faulty, this
    17  functionality must be implemented in a fault-tolerant way.
    18  
    19  In the Tendermint blockchain, the validator set may change with every
    20  new block.  The staking and unbonding mechanism induces a [security
    21  model](TMBC-FM-2THIRDS-link): starting at time *Time* of the
    22  [header](TMBC-HEADER-link),
    23  more than two-thirds of the next validators of a new block are correct
    24  for the duration of *TrustedPeriod*.
    25  
    26  [Light Client Verification](https://informal.systems) implements the fault-tolerant read
    27  operation designed for this security model. That is, it is safe if the
    28  model assumptions are satisfied and makes progress if it communicates
    29  to a correct primary.
    30  
    31  However, if the [security model](TMBC-FM-2THIRDS-link) is violated,
    32  faulty peers (that have been validators at some point in the past) may
    33  launch attacks on the Tendermint network, and on the light
    34  client. These attacks as well as an axiomatization of blocks in
    35  general are defined in [a document that contains the definitions that
    36  are currently in detection.md](https://informal.systems).
    37  
    38  If there is a light client attack (but no
    39  successful attack on the network), the safety of the verification step
    40  may be violated (as we operate outside its basic assumption).
    41  The light client also
    42  contains a defense mechanism against light clients attacks, called detection.
    43  
    44  [Light Client Detection](https://informal.systems) implements a cross check of the result
    45  of the verification step. If there is a light client attack, and the
    46  light client is connected to a correct peer, the light client as a
    47  whole is safe, that is, it will not operate on invalid
    48  blocks. However, in this case it cannot successfully read, as
    49  inconsistent blocks are in the system. However, in this case the
    50  detection performs a distributed computation that results in so-called
    51  evidence. Evidence can be used to prove
    52  to a correct full node that there has been a
    53  light client attack.
    54  
    55  [Light Client Evidence Accountability](https://informal.systems) is a protocol run on a
    56  full node to check whether submitted evidence indeed proves the
    57  existence of a light client attack. Further, from the evidence and its
    58  own knowledge about the blockchain, the full node computes a set of
    59  bonded full nodes (that at some point had more than one third of the
    60  voting power) that participated in the attack that will be reported
    61  via ABCI to the application.
    62  
    63  In this document we specify
    64  
    65  - Initialization of the Light Client
    66  - The interaction of [verification](https://informal.systems) and [detection](https://informal.systems)
    67  
    68  The details of these two protocols are captured in their own
    69  documents, as is the [accountability](https://informal.systems) protocol.
    70  
    71  > Another related line is IBC attack detection and submission at the
    72  > relayer, as well as attack verification at the IBC handler. This
    73  > will call for yet another spec.
    74  
    75  # Status
    76  
    77  This document is work in progress. In order to develop the
    78  specification step-by-step,
    79  it assumes certain details of [verification](https://informal.systems) and
    80  [detection](https://informal.systems) that are not specified in the respective current
    81  versions yet. This inconsistencies will be addresses over several
    82  upcoming PRs.
    83  
    84  # Part I - Tendermint Blockchain
    85  
    86  See [verification spec](addLinksWhenDone)
    87  
    88  # Part II - Sequential Problem Definition
    89  
    90  #### **[LC-SEQ-INIT-LIVE.1]**
    91  
    92  Upon initialization, the light client gets as input a header of the
    93  blockchain, or the genesis file of the blockchain, and eventually
    94  stores a header of the blockchain.
    95  
    96  #### **[LC-SEQ-LIVE.1]**
    97  
    98  The light client gets a sequence of heights as inputs. For each input
    99  height *targetHeight*, it eventually stores the header of height
   100  *targetHeight*.
   101  
   102  #### **[LC-SEQ-SAFE.1]**
   103  
   104  The light client never stores a header which is not in the blockchain.
   105  
   106  # Part III - Light Client as Distributed System
   107  
   108  ## Computational Model
   109  
   110  The light client communicates with remote processes only via the
   111  [verification](TODO) and the [detection](TODO) protocols. The
   112  respective assumptions are given there.
   113  
   114  ## Distributed Problem Statement
   115  
   116  ### Two Kinds of Liveness
   117  
   118  In case of light client attacks, the sequential problem statement
   119  cannot always be satisfied. The lightclient cannot decide which block
   120  is from the chain and which is not. As a result, the light client just
   121  creates evidence, submits it, and terminates.
   122  For the liveness property, we thus add the
   123  possibility that instead of adding a lightblock, we also might terminate
   124  in case there is an attack.
   125  
   126  #### **[LC-DIST-TERM.1]**
   127  
   128  The light client either runs forever or it *terminates on attack*.
   129  
   130  ### Design choices
   131  
   132  #### [LC-DIST-STORE.1]
   133  
   134  The light client has a local data structure called LightStore
   135  that contains light blocks (that contain a header).
   136  
   137  > The light store exposes functions to query and update it. They are
   138  > specified [here](TODO:onceVerificationIsMerged).
   139  
   140  **TODO:** reference light store invariant [LCV-INV-LS-ROOT.2] once
   141  verification is merged
   142  
   143  #### **[LC-DIST-SAFE.1]**
   144  
   145  It is always the case that every header in *LightStore* was
   146  generated by an instance of Tendermint consensus.
   147  
   148  #### **[LC-DIST-LIVE.1]**
   149  
   150  Whenever the light client gets a new height *h* as input,
   151  
   152  - and there is
   153  no light client attack up to height *h*, then the lightclient
   154  eventually puts the lightblock of height *h* in the lightstore and
   155  wait for another input.
   156  - otherwise, that is, if there
   157  is a light client attack on height *h*, then the light client
   158  must perform one of the following:
   159      - it terminates on attack.
   160      - it eventually puts the lightblock of height *h* in the lightstore and
   161  wait for another input.
   162  
   163  > Observe that the "existence of a lightclient attack" just means that some node has generated a conflicting block. It does not necessarily mean that a (faulty) peer sends such a block to "our" lightclient. Thus, even if there is an attack somewhere in the system, our lightclient might still continue to operate normally.
   164  
   165  ### Solving the sequential specification
   166  
   167  [LC-DIST-SAFE.1] is guaranteed by the detector; in particular it
   168  follows from
   169  [[LCD-DIST-INV-STORE.1]](TODO)
   170  [[LCD-DIST-LIVE.1]](TODO)
   171  
   172  # Part IV - Light Client Supervisor Protocol
   173  
   174  We provide a specification for a sequential Light Client Supervisor.
   175  The local code for verification is presented by a sequential function
   176  `Sequential-Supervisor` to highlight the control flow of this
   177  functionality. Each lightblock is first verified with a primary, and then
   178  cross-checked with secondaries, and if all goes well, the lightblock
   179  is
   180  added (with the attribute "trusted") to the
   181  lightstore. Intermiate lightblocks that were used to verify the target
   182  block but were not cross-checked are stored as "verified"
   183  
   184  > We note that if a different concurrency model is considered
   185  > for an implementation, the semantics of the lightstore might change:
   186  > In a concurrent implementation, we might do verification for some
   187  > height *h*, add the
   188  > lightblock to the lightstore, and start concurrent threads that
   189  >
   190  > - do verification for the next height *h' != h*
   191  > - do cross-checking for height *h*. If we find an attack, we remove
   192  >   *h* from the lightstore.
   193  > - the user might already start to use *h*
   194  >
   195  > Thus, this concurrency model changes the semantics of the
   196  > lightstore (not all lightblocks that are read by the user are
   197  > trusted; they may be removed if
   198  > we find a problem). Whether this is desirable, and whether the gain in
   199  > performance is worth it, we keep for future versions/discussion of
   200  > lightclient protocols.
   201  
   202  ## Definitions
   203  
   204  ### Peers
   205  
   206  #### **[LC-DATA-PEERS.1]:**
   207  
   208  A fixed set of full nodes is provided in the configuration upon
   209  initialization. Initially this set is partitioned into
   210  
   211  - one full node that is the *primary* (singleton set),
   212  - a set *Secondaries* (of fixed size, e.g., 3),
   213  - a set *FullNodes*; it excludes *primary* and *Secondaries* nodes.
   214  - A set *FaultyNodes* of nodes that the light client suspects of
   215      being faulty; it is initially empty
   216  
   217  #### **[LC-INV-NODES.1]:**
   218  
   219  The detector shall maintain the following invariants:
   220  
   221  - *FullNodes \intersect Secondaries = {}*
   222  - *FullNodes \intersect FaultyNodes = {}*
   223  - *Secondaries \intersect FaultyNodes = {}*
   224  
   225  and the following transition invariant
   226  
   227  - *FullNodes' \union Secondaries' \union FaultyNodes' = FullNodes
   228     \union Secondaries \union FaultyNodes*
   229  
   230  #### **[LC-FUNC-REPLACE-PRIMARY.1]:**
   231  
   232  ```go
   233  Replace_Primary(root-of-trust LightBlock)
   234  ```
   235  
   236  - Implementation remark
   237      - the primary is replaced by a secondary
   238      - to maintain a constant size of secondaries, need to
   239          - pick a new secondary *nsec* while ensuring [LC-INV-ROOT-AGREED.1]
   240          - that is, we need to ensure that root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height)
   241  - Expected precondition
   242      - *FullNodes* is nonempty
   243  - Expected postcondition
   244      - *primary* is moved to *FaultyNodes*
   245      - a secondary *s* is moved from *Secondaries* to primary
   246  - Error condition
   247      - if precondition is violated
   248  
   249  #### **[LC-FUNC-REPLACE-SECONDARY.1]:**
   250  
   251  ```go
   252  Replace_Secondary(addr Address, root-of-trust LightBlock)
   253  ```
   254  
   255  - Implementation remark
   256      - maintain [LC-INV-ROOT-AGREED.1], that is,
   257      ensure root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height)
   258  - Expected precondition
   259      - *FullNodes* is nonempty
   260  - Expected postcondition
   261      - addr is moved from *Secondaries* to *FaultyNodes*
   262      - an address *nsec* is moved from *FullNodes* to *Secondaries*
   263  - Error condition
   264      - if precondition is violated
   265  
   266  ### Data Types
   267  
   268  The core data structure of the protocol is the LightBlock.
   269  
   270  #### **[LC-DATA-LIGHTBLOCK.1]**
   271  
   272  ```go
   273  type LightBlock struct {
   274                  Header          Header
   275                  Commit          Commit
   276                  Validators      ValidatorSet
   277                  NextValidators  ValidatorSet
   278                  Provider        PeerID
   279  }
   280  ```
   281  
   282  #### **[LC-DATA-LIGHTSTORE.1]**
   283  
   284  LightBlocks are stored in a structure which stores all LightBlock from
   285  initialization or received from peers.
   286  
   287  ```go
   288  type LightStore struct {
   289          ...
   290  }
   291  
   292  ```
   293  
   294  We use the functions that the LightStore exposes, which
   295  are defined in the [verification specification](TODO).
   296  
   297  ### Inputs
   298  
   299  The lightclient is initialized with LCInitData
   300  
   301  #### **[LC-DATA-INIT.1]**
   302  
   303  ```go
   304  type LCInitData struct {
   305      lightBlock     LightBlock
   306      genesisDoc     GenesisDoc
   307  }
   308  ```
   309  
   310  where only one of the components must be provided. `GenesisDoc` is
   311  defined in the [Tendermint
   312  Types](https://github.com/tendermint/tendermint/blob/master/types/genesis.go).
   313  
   314  #### **[LC-DATA-GENESIS.1]**
   315  
   316  ```go
   317  type GenesisDoc struct {
   318      GenesisTime     time.Time                `json:"genesis_time"`
   319      ChainID         string                   `json:"chain_id"`
   320      InitialHeight   int64                    `json:"initial_height"`
   321      ConsensusParams *tmproto.ConsensusParams `json:"consensus_params,omitempty"`
   322      Validators      []GenesisValidator       `json:"validators,omitempty"`
   323      AppHash         tmbytes.HexBytes         `json:"app_hash"`
   324      AppState        json.RawMessage          `json:"app_state,omitempty"`
   325  }
   326  ```
   327  
   328  We use the following function
   329  `makeblock` so that we create a lightblock from the genesis
   330  file in order to do verification based on the data from the genesis
   331  file using the same verification function we use in normal operation.
   332  
   333  #### **[LC-FUNC-MAKEBLOCK.1]**
   334  
   335  ```go
   336  func makeblock (genesisDoc GenesisDoc) (lightBlock LightBlock))
   337  ```
   338  
   339  - Implementation remark
   340      - none
   341  - Expected precondition
   342      - none
   343  - Expected postcondition
   344      - lightBlock.Header.Height =  genesisDoc.InitialHeight
   345      - lightBlock.Header.Time = genesisDoc.GenesisTime
   346      - lightBlock.Header.LastBlockID = nil
   347      - lightBlock.Header.LastCommit = nil
   348      - lightBlock.Header.Validators = genesisDoc.Validators
   349      - lightBlock.Header.NextValidators = genesisDoc.Validators
   350      - lightBlock.Header.Data = nil
   351      - lightBlock.Header.AppState =  genesisDoc.AppState
   352      - lightBlock.Header.LastResult = nil
   353      - lightBlock.Commit = nil
   354      - lightBlock.Validators = genesisDoc.Validators
   355      - lightBlock.NextValidators = genesisDoc.Validators
   356      - lightBlock.Provider = nil
   357  - Error condition
   358      - none
   359  
   360  ----
   361  
   362  ### Configuration Parameters
   363  
   364  #### **[LC-INV-ROOT-AGREED.1]**
   365  
   366  In the Sequential-Supervisor, it is always the case that the primary
   367  and all secondaries agree on lightStore.Latest().
   368  
   369  ### Assumptions
   370  
   371  We have to assume that the initialization data (the lightblock or the
   372  genesis file) are consistent with the blockchain. This is subjective
   373  initialization and it cannot be checked locally.
   374  
   375  ### Invariants
   376  
   377  #### **[LC-INV-PEERLIST.1]:**
   378  
   379  The peer list contains a primary and a secondary.
   380  
   381  > If the invariant is violated, the light client does not have enough
   382  > peers to download headers from. As a result, the light client
   383  > needs to terminate in case this invariant is violated.
   384  
   385  ## Supervisor
   386  
   387  ### Outline
   388  
   389  The supervisor implements the functionality of the lightclient. It is
   390  initialized with a genesis file or with a lightblock the user
   391  trusts. This initialization is subjective, that is, the security of
   392  the lightclient is based on the validity of the input. If the genesis
   393  file or the lightblock deviate from the actual ones on the blockchain,
   394  the lightclient provides no guarantees.
   395  
   396  After initialization, the supervisor awaits an input, that is, the
   397  height of the next lightblock that should be obtained. Then it
   398  downloads, verifies, and cross-checks a lightblock, and if all tests
   399  go through, the light block (and possibly other lightblocks) are added
   400  to the lightstore, which is returned in an output event to the user.
   401  
   402  The following main loop does the interaction with the user (input,
   403  output) and calls the following two functions:
   404  
   405  - `InitLightClient`: it initializes the lightstore either with the
   406    provided lightblock or with the lightblock that corresponds to the
   407    first block generated by the blockchain (by the validators defined
   408    by the genesis file)
   409  - `VerifyAndDetect`: takes as input a lightstore and a height and
   410    returns the updated lightstore.
   411  
   412  #### **[LC-FUNC-SUPERVISOR.1]:**
   413  
   414  ```go
   415  func Sequential-Supervisor (initdata LCInitData) (Error) {
   416  
   417      lightStore,result := InitLightClient(initData);
   418      if result != OK {
   419          return result;
   420      }
   421  
   422      loop {
   423          // get the next height
   424          nextHeight := input();
   425    
   426          lightStore,result := VerifyAndDetect(lightStore, nextHeight);
   427    
   428          if result == OK {
   429              output(LightStore.Get(targetHeight));
   430     // we only output a trusted lightblock
   431          }
   432          else {
   433              return result
   434          }
   435          // QUESTION: is it OK to generate output event in normal case,
   436          // and terminate with failure in the (light client) attack case?
   437      }
   438  }
   439  ```
   440  
   441  - Implementation remark
   442      - infinite loop unless a light client attack is detected
   443      - In typical implementations (e.g., the one in Rust),
   444     there are mutliple input actions:
   445        `VerifytoLatest`, `LatestTrusted`, and `GetStatus`. The
   446        information can be easily obtained from the lightstore, so that
   447        we do not treat these requests explicitly here but just consider
   448     the request for a block of a given height which requires more
   449     involved computation and communication.
   450  - Expected precondition
   451      - *LCInitData* contains a genesis file or a lightblock.
   452  - Expected postcondition
   453      - if a light client attack is detected: it stops and submits
   454        evidence (in `InitLightClient` or `VerifyAndDetect`)
   455      - otherwise: non. It runs forever.
   456  - Invariant: *lightStore* contains trusted lightblocks only.
   457  - Error condition
   458      - if `InitLightClient` or `VerifyAndDetect` fails (if a attack is
   459   detected, or if [LCV-INV-TP.1] is violated)
   460  
   461  ----
   462  
   463  ### Details of the Functions
   464  
   465  #### Initialization
   466  
   467  The light client is based on subjective initialization. It has to
   468  trust the initial data given to it by the user. It cannot do any
   469  detection of attack. So either upon initialization we obtain a
   470  lightblock and just initialize the lightstore with it. Or in case of a
   471  genesis file, we download, verify, and cross-check the first block, to
   472  initialize the lightstore with this first block. The reason is that
   473  we want to maintain [LCV-INV-TP.1] from the beginning.
   474  
   475  > If the lightclient is initialized with a lightblock, one might think
   476  > it may increase trust, when one cross-checks the initial light
   477  > block. However, if a peer provides a conflicting
   478  > lightblock, the question is to distinguish the case of a
   479  > [bogus](https://informal.systems) block (upon which operation should proceed) from a
   480  > [light client attack](https://informal.systems) (upon which operation should stop). In
   481  > case of a bogus block, the lightclient might be forced to do
   482  > backwards verification until the blocks are out of the trusting
   483  > period, to make sure no previous validator set could have generated
   484  > the bogus block, which effectively opens up a DoS attack on the lightclient
   485  > without adding effective robustness.
   486  
   487  #### **[LC-FUNC-INIT.1]:**
   488  
   489  ```go
   490  func InitLightClient (initData LCInitData) (LightStore, Error) {
   491  
   492      if LCInitData.LightBlock != nil {
   493          // we trust the provided initial block.
   494          newblock := LCInitData.LightBlock
   495      }
   496      else {
   497          genesisBlock := makeblock(initData.genesisDoc);
   498  
   499          result := NoResult;
   500          while result != ResultSuccess {
   501              current = FetchLightBlock(PeerList.primary(), genesisBlock.Header.Height + 1)
   502              // QUESTION: is the height with "+1" OK?
   503  
   504              if CANNOT_VERIFY = ValidAndVerify(genesisBlock, current) {
   505                  Replace_Primary();
   506              }
   507              else {
   508                  result = ResultSuccess
   509              }
   510          }
   511    
   512          // cross-check
   513    auxLS := new LightStore
   514    auxLS.Add(current)
   515          Evidences := AttackDetector(genesisBlock, auxLS)
   516          if Evidences.Empty {
   517              newBlock := current
   518          }
   519          else {
   520              // [LC-SUMBIT-EVIDENCE.1]
   521              submitEvidence(Evidences);
   522              return(nil, ErrorAttack);
   523          }
   524      }
   525  
   526      lightStore := new LightStore;
   527      lightStore.Add(newBlock);
   528      return (lightStore, OK);
   529  }
   530  
   531  ```
   532  
   533  - Implementation remark
   534      - none
   535  - Expected precondition
   536      - *LCInitData* contains either a genesis file of a lightblock
   537      - if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems)
   538  - Expected postcondition
   539      - *lightStore* initialized with trusted lightblock. It has either been
   540        cross-checked (from genesis) or it has initial trust from the
   541        user.
   542  - Error condition
   543      - if precondition is violated
   544      - empty peerList
   545  
   546  ----
   547  
   548  #### Main verification and detection logic
   549  
   550  #### **[LC-FUNC-MAIN-VERIF-DETECT.1]:**
   551  
   552  ```go
   553  func VerifyAndDetect (lightStore LightStore, targetHeight Height)
   554                       (LightStore, Result) {
   555  
   556      b1, r1 = lightStore.Get(targetHeight)
   557      if r1 == true {
   558          if b1.State == StateTrusted {
   559              // block already there and trusted
   560              return (lightStore, ResultSuccess)
   561    }
   562    else {
   563              // We have a lightblock in the store, but it has not been 
   564              // cross-checked by now. We do that now.
   565              root_of_trust, auxLS := lightstore.TraceTo(b1);
   566     
   567              // Cross-check
   568              Evidences := AttackDetector(root_of_trust, auxLS);
   569              if Evidences.Empty {
   570                  // no attack detected, we trust the new lightblock
   571                  lightStore.Update(auxLS.Latest(), 
   572                                    StateTrusted, 
   573                                    verfiedLS.Latest().verification-root);
   574                  return (lightStore, OK);
   575              }
   576              else {
   577                  // there is an attack, we exit
   578    submitEvidence(Evidences);
   579                  return(lightStore, ErrorAttack);
   580              }
   581          }
   582      }
   583  
   584      // get the lightblock with maximum height smaller than targetHeight
   585      // would typically be the heighest, if we always move forward
   586      root_of_trust, r2 = lightStore.LatestPrevious(targetHeight);
   587  
   588      if r2 = false {
   589          // there is no lightblock from which we can do forward
   590          // (skipping) verification. Thus we have to go backwards.
   591          // No cross-check needed. We trust hashes. Therefore, we
   592          // directly return the result
   593          return Backwards(primary, lightStore.Lowest(), targetHeight)
   594      }
   595      else {
   596          // Forward verification + detection
   597          result := NoResult;
   598          while result != ResultSuccess {
   599              verifiedLS,result := VerifyToTarget(primary,
   600                                                  root_of_trust,
   601                                                  nextHeight);
   602              if result == ResultFailure {
   603                  // pick new primary (promote a secondary to primary)
   604                  Replace_Primary(root_of_trust);
   605              }
   606              else if result == ResultExpired {
   607                  return (lightStore, result)
   608              }
   609          }
   610  
   611          // Cross-check
   612          Evidences := AttackDetector(root_of_trust, verifiedLS);
   613          if Evidences.Empty {
   614              // no attack detected, we trust the new lightblock
   615              verifiedLS.Update(verfiedLS.Latest(), 
   616                                StateTrusted, 
   617                                verfiedLS.Latest().verification-root);
   618              lightStore.store_chain(verifidLS);
   619              return (lightStore, OK);
   620          }
   621          else {
   622              // there is an attack, we exit
   623              return(lightStore, ErrorAttack);
   624          }
   625      }
   626  }
   627  ```
   628  
   629  - Implementation remark
   630      - none
   631  - Expected precondition
   632      - none
   633  - Expected postcondition
   634      - lightblock of height *targetHeight* (and possibly additional blocks) added to *lightStore*
   635  - Error condition
   636      - an attack is detected
   637      - [LC-DATA-PEERLIST-INV.1] is violated
   638  
   639  ----