github.com/aakash4dev/cometbft@v0.38.2/spec/light-client/verification/Lightclient_002_draft.tla (about)

     1  -------------------------- MODULE Lightclient_002_draft ----------------------------
     2  (**
     3   * A state-machine specification of the lite client, following the English spec:
     4   *
     5   * https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification.md
     6   *) 
     7  
     8  EXTENDS Integers, FiniteSets
     9  
    10  \* the parameters of Light Client
    11  CONSTANTS
    12    TRUSTED_HEIGHT,
    13      (* an index of the block header that the light client trusts by social consensus *)
    14    TARGET_HEIGHT,
    15      (* an index of the block header that the light client tries to verify *)
    16    TRUSTING_PERIOD,
    17      (* the period within which the validators are trusted *)
    18    IS_PRIMARY_CORRECT
    19      (* is primary correct? *)  
    20  
    21  VARIABLES       (* see TypeOK below for the variable types *)
    22    state,        (* the current state of the light client *)
    23    nextHeight,   (* the next height to explore by the light client *)
    24    nprobes       (* the lite client iteration, or the number of block tests *)
    25    
    26  (* the light store *)
    27  VARIABLES  
    28    fetchedLightBlocks, (* a function from heights to LightBlocks *)
    29    lightBlockStatus,   (* a function from heights to block statuses *)
    30    latestVerified      (* the latest verified block *)
    31  
    32  (* the variables of the lite client *)
    33  lcvars == <<state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified>>
    34  
    35  (* the light client previous state components, used for monitoring *)
    36  VARIABLES
    37    prevVerified,
    38    prevCurrent,
    39    prevNow,
    40    prevVerdict
    41  
    42  InitMonitor(verified, current, now, verdict) ==
    43    /\ prevVerified = verified
    44    /\ prevCurrent = current
    45    /\ prevNow = now
    46    /\ prevVerdict = verdict
    47  
    48  NextMonitor(verified, current, now, verdict) ==
    49    /\ prevVerified' = verified
    50    /\ prevCurrent' = current
    51    /\ prevNow' = now
    52    /\ prevVerdict' = verdict
    53  
    54  
    55  (******************* Blockchain instance ***********************************)
    56  
    57  \* the parameters that are propagated into Blockchain
    58  CONSTANTS
    59    AllNodes
    60      (* a set of all nodes that can act as validators (correct and faulty) *)
    61  
    62  \* the state variables of Blockchain, see Blockchain.tla for the details
    63  VARIABLES now, blockchain, Faulty
    64  
    65  \* All the variables of Blockchain. For some reason, BC!vars does not work
    66  bcvars == <<now, blockchain, Faulty>>
    67  
    68  (* Create an instance of Blockchain.
    69     We could write EXTENDS Blockchain, but then all the constants and state variables
    70     would be hidden inside the Blockchain module.
    71   *) 
    72  ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 
    73   
    74  BC == INSTANCE Blockchain_002_draft WITH
    75    now <- now, blockchain <- blockchain, Faulty <- Faulty
    76  
    77  (************************** Lite client ************************************)
    78  
    79  (* the heights on which the light client is working *)  
    80  HEIGHTS == TRUSTED_HEIGHT..TARGET_HEIGHT
    81  
    82  (* the control states of the lite client *) 
    83  States == { "working", "finishedSuccess", "finishedFailure" }
    84  
    85  (**
    86   Check the precondition of ValidAndVerified.
    87   
    88   [LCV-FUNC-VALID.1::TLA-PRE.1]
    89   *)
    90  ValidAndVerifiedPre(trusted, untrusted) ==
    91    LET thdr == trusted.header
    92        uhdr == untrusted.header
    93    IN
    94    /\ BC!InTrustingPeriod(thdr)
    95    /\ thdr.height < uhdr.height
    96       \* the trusted block has been created earlier (no drift here)
    97    /\ thdr.time < uhdr.time
    98       \* the untrusted block is not from the future
    99    /\ uhdr.time < now
   100    /\ untrusted.Commits \subseteq uhdr.VS
   101    /\ LET TP == Cardinality(uhdr.VS)
   102           SP == Cardinality(untrusted.Commits)
   103       IN
   104       3 * SP > 2 * TP     
   105    /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS
   106    (* As we do not have explicit hashes we ignore these three checks of the English spec:
   107     
   108       1. "trusted.Commit is a commit is for the header trusted.Header,
   109        i.e. it contains the correct hash of the header".
   110       2. untrusted.Validators = hash(untrusted.Header.Validators)
   111       3. untrusted.NextValidators = hash(untrusted.Header.NextValidators)
   112     *)
   113  
   114  (**
   115    * Check that the commits in an untrusted block form 1/3 of the next validators
   116    * in a trusted header.
   117   *)
   118  SignedByOneThirdOfTrusted(trusted, untrusted) ==
   119    LET TP == Cardinality(trusted.header.NextVS)
   120        SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS)
   121    IN
   122    3 * SP > TP     
   123  
   124  (**
   125   Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header.
   126   
   127   [LCV-FUNC-VALID.1::TLA.1]
   128   *)   
   129  ValidAndVerified(trusted, untrusted) ==
   130      IF ~ValidAndVerifiedPre(trusted, untrusted)
   131      THEN "INVALID"
   132      ELSE IF ~BC!InTrustingPeriod(untrusted.header)
   133      (* We leave the following test for the documentation purposes.
   134         The implementation should do this test, as signature verification may be slow.
   135         In the TLA+ specification, ValidAndVerified happens in no time.
   136       *) 
   137      THEN "FAILED_TRUSTING_PERIOD" 
   138      ELSE IF untrusted.header.height = trusted.header.height + 1
   139               \/ SignedByOneThirdOfTrusted(trusted, untrusted)
   140           THEN "SUCCESS"
   141           ELSE "NOT_ENOUGH_TRUST"
   142  
   143  (*
   144   Initial states of the light client.
   145   Initially, only the trusted light block is present.
   146   *)
   147  LCInit ==
   148      /\ state = "working"
   149      /\ nextHeight = TARGET_HEIGHT
   150      /\ nprobes = 0  \* no tests have been done so far
   151      /\ LET trustedBlock == blockchain[TRUSTED_HEIGHT]
   152             trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes]
   153         IN
   154          \* initially, fetchedLightBlocks is a function of one element, i.e., TRUSTED_HEIGHT
   155          /\ fetchedLightBlocks = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock]
   156          \* initially, lightBlockStatus is a function of one element, i.e., TRUSTED_HEIGHT
   157          /\ lightBlockStatus = [h \in {TRUSTED_HEIGHT} |-> "StateVerified"]
   158          \* the latest verified block the the trusted block
   159          /\ latestVerified = trustedLightBlock
   160          /\ InitMonitor(trustedLightBlock, trustedLightBlock, now, "SUCCESS")
   161  
   162  \* block should contain a copy of the block from the reference chain, with a matching commit
   163  CopyLightBlockFromChain(block, height) ==
   164      LET ref == blockchain[height]
   165          lastCommit ==
   166            IF height < ULTIMATE_HEIGHT
   167            THEN blockchain[height + 1].lastCommit
   168              \* for the ultimate block, which we never use, as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1
   169            ELSE blockchain[height].VS 
   170      IN
   171      block = [header |-> ref, Commits |-> lastCommit]      
   172  
   173  \* Either the primary is correct and the block comes from the reference chain,
   174  \* or the block is produced by a faulty primary.
   175  \*
   176  \* [LCV-FUNC-FETCH.1::TLA.1]
   177  FetchLightBlockInto(block, height) ==
   178      IF IS_PRIMARY_CORRECT
   179      THEN CopyLightBlockFromChain(block, height)
   180      ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block)
   181  
   182  \* add a block into the light store    
   183  \*
   184  \* [LCV-FUNC-UPDATE.1::TLA.1]
   185  LightStoreUpdateBlocks(lightBlocks, block) ==
   186      LET ht == block.header.height IN    
   187      [h \in DOMAIN lightBlocks \union {ht} |->
   188          IF h = ht THEN block ELSE lightBlocks[h]]
   189  
   190  \* update the state of a light block      
   191  \*
   192  \* [LCV-FUNC-UPDATE.1::TLA.1]
   193  LightStoreUpdateStates(statuses, ht, blockState) ==
   194      [h \in DOMAIN statuses \union {ht} |->
   195          IF h = ht THEN blockState ELSE statuses[h]]      
   196  
   197  \* Check, whether newHeight is a possible next height for the light client.
   198  \*
   199  \* [LCV-FUNC-SCHEDULE.1::TLA.1]
   200  CanScheduleTo(newHeight, pLatestVerified, pNextHeight, pTargetHeight) ==
   201      LET ht == pLatestVerified.header.height IN
   202      \/ /\ ht = pNextHeight
   203         /\ ht < pTargetHeight
   204         /\ pNextHeight < newHeight
   205         /\ newHeight <= pTargetHeight
   206      \/ /\ ht < pNextHeight
   207         /\ ht < pTargetHeight
   208         /\ ht < newHeight
   209         /\ newHeight < pNextHeight
   210      \/ /\ ht = pTargetHeight
   211         /\ newHeight = pTargetHeight
   212  
   213  \* The loop of VerifyToTarget.
   214  \*
   215  \* [LCV-FUNC-MAIN.1::TLA-LOOP.1]
   216  VerifyToTargetLoop ==
   217        \* the loop condition is true
   218      /\ latestVerified.header.height < TARGET_HEIGHT
   219        \* pick a light block, which will be constrained later
   220      /\ \E current \in BC!LightBlocks:
   221          \* Get next LightBlock for verification
   222          /\ IF nextHeight \in DOMAIN fetchedLightBlocks
   223             THEN \* copy the block from the light store
   224                  /\ current = fetchedLightBlocks[nextHeight]
   225                  /\ UNCHANGED fetchedLightBlocks
   226             ELSE \* retrieve a light block and save it in the light store
   227                  /\ FetchLightBlockInto(current, nextHeight)
   228                  /\ fetchedLightBlocks' = LightStoreUpdateBlocks(fetchedLightBlocks, current)
   229          \* Record that one more probe has been done (for complexity and model checking)
   230          /\ nprobes' = nprobes + 1
   231          \* Verify the current block
   232          /\ LET verdict == ValidAndVerified(latestVerified, current) IN
   233             NextMonitor(latestVerified, current, now, verdict) /\
   234             \* Decide whether/how to continue
   235             CASE verdict = "SUCCESS" ->
   236                /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateVerified")
   237                /\ latestVerified' = current
   238                /\ state' =
   239                      IF latestVerified'.header.height < TARGET_HEIGHT
   240                      THEN "working"
   241                      ELSE "finishedSuccess"
   242                /\ \E newHeight \in HEIGHTS:
   243                   /\ CanScheduleTo(newHeight, current, nextHeight, TARGET_HEIGHT)
   244                   /\ nextHeight' = newHeight
   245                    
   246             [] verdict = "NOT_ENOUGH_TRUST" ->
   247                (*
   248                  do nothing: the light block current passed validation, but the validator
   249                  set is too different to verify it. We keep the state of
   250                  current at StateUnverified. For a later iteration, Schedule
   251                  might decide to try verification of that light block again.
   252                  *)
   253                /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateUnverified")
   254                /\ \E newHeight \in HEIGHTS:
   255                   /\ CanScheduleTo(newHeight, latestVerified, nextHeight, TARGET_HEIGHT)
   256                   /\ nextHeight' = newHeight 
   257                /\ UNCHANGED <<latestVerified, state>>
   258                
   259             [] OTHER ->
   260                \* verdict is some error code
   261                /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateFailed")
   262                /\ state' = "finishedFailure"
   263                /\ UNCHANGED <<latestVerified, nextHeight>>
   264  
   265  \* The terminating condition of VerifyToTarget.
   266  \*
   267  \* [LCV-FUNC-MAIN.1::TLA-LOOPCOND.1]
   268  VerifyToTargetDone ==
   269      /\ latestVerified.header.height >= TARGET_HEIGHT
   270      /\ state' = "finishedSuccess"
   271      /\ UNCHANGED <<nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified>>
   272      /\ UNCHANGED <<prevVerified, prevCurrent, prevNow, prevVerdict>>
   273              
   274  (********************* Lite client + Blockchain *******************)
   275  Init ==
   276      \* the blockchain is initialized immediately to the ULTIMATE_HEIGHT
   277      /\ BC!InitToHeight
   278      \* the light client starts
   279      /\ LCInit
   280  
   281  (*
   282    The system step is very simple.
   283    The light client is either executing VerifyToTarget, or it has terminated.
   284    (In the latter case, a model checker reports a deadlock.)
   285    Simultaneously, the global clock may advance.
   286   *)
   287  Next ==
   288      /\ state = "working"
   289      /\ VerifyToTargetLoop \/ VerifyToTargetDone 
   290      /\ BC!AdvanceTime \* the global clock is advanced by zero or more time units
   291  
   292  (************************* Types ******************************************)
   293  TypeOK ==
   294      /\ state \in States
   295      /\ nextHeight \in HEIGHTS
   296      /\ latestVerified \in BC!LightBlocks
   297      /\ \E HS \in SUBSET HEIGHTS:
   298          /\ fetchedLightBlocks \in [HS -> BC!LightBlocks]
   299          /\ lightBlockStatus
   300               \in [HS -> {"StateVerified", "StateUnverified", "StateFailed"}]
   301  
   302  (************************* Properties ******************************************)
   303  
   304  (* The properties to check *)
   305  \* this invariant candidate is false    
   306  NeverFinish ==
   307      state = "working"
   308  
   309  \* this invariant candidate is false    
   310  NeverFinishNegative ==
   311      state /= "finishedFailure"
   312  
   313  \* This invariant holds true, when the primary is correct. 
   314  \* This invariant candidate is false when the primary is faulty.    
   315  NeverFinishNegativeWhenTrusted ==
   316      (*(minTrustedHeight <= TRUSTED_HEIGHT)*)
   317      BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT])
   318        => state /= "finishedFailure"     
   319  
   320  \* this invariant candidate is false    
   321  NeverFinishPositive ==
   322      state /= "finishedSuccess"
   323  
   324  (**
   325    Correctness states that all the obtained headers are exactly like in the blockchain.
   326   
   327    It is always the case that every verified header in LightStore was generated by
   328    an instance of Tendermint consensus.
   329    
   330    [LCV-DIST-SAFE.1::CORRECTNESS-INV.1]
   331   *)  
   332  CorrectnessInv ==
   333      \A h \in DOMAIN fetchedLightBlocks:
   334          lightBlockStatus[h] = "StateVerified" =>
   335              fetchedLightBlocks[h].header = blockchain[h]
   336  
   337  (**
   338   Check that the sequence of the headers in storedLightBlocks satisfies ValidAndVerified = "SUCCESS" pairwise
   339   This property is easily violated, whenever a header cannot be trusted anymore.
   340   *)
   341  StoredHeadersAreVerifiedInv ==
   342      state = "finishedSuccess"
   343          =>
   344          \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers
   345              \/ lh >= rh
   346                 \* either there is a header between them
   347              \/ \E mh \in DOMAIN fetchedLightBlocks:
   348                  lh < mh /\ mh < rh
   349                 \* or we can verify the right one using the left one
   350              \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh])
   351  
   352  \* An improved version of StoredHeadersAreSound, assuming that a header may be not trusted.
   353  \* This invariant candidate is also violated,
   354  \* as there may be some unverified blocks left in the middle.
   355  StoredHeadersAreVerifiedOrNotTrustedInv ==
   356      state = "finishedSuccess"
   357          =>
   358          \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers
   359              \/ lh >= rh
   360                 \* either there is a header between them
   361              \/ \E mh \in DOMAIN fetchedLightBlocks:
   362                  lh < mh /\ mh < rh
   363                 \* or we can verify the right one using the left one
   364              \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh])
   365                 \* or the left header is outside the trusting period, so no guarantees
   366              \/ ~BC!InTrustingPeriod(fetchedLightBlocks[lh].header) 
   367  
   368  (**
   369   * An improved version of StoredHeadersAreSoundOrNotTrusted,
   370   * checking the property only for the verified headers.
   371   * This invariant holds true.
   372   *)
   373  ProofOfChainOfTrustInv ==
   374      state = "finishedSuccess"
   375          =>
   376          \A lh, rh \in DOMAIN fetchedLightBlocks:
   377                  \* for every pair of stored headers that have been verified
   378              \/ lh >= rh
   379              \/ lightBlockStatus[lh] = "StateUnverified"
   380              \/ lightBlockStatus[rh] = "StateUnverified"
   381                 \* either there is a header between them
   382              \/ \E mh \in DOMAIN fetchedLightBlocks:
   383                  lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified"
   384                 \* or the left header is outside the trusting period, so no guarantees
   385              \/ ~(BC!InTrustingPeriod(fetchedLightBlocks[lh].header))
   386                 \* or we can verify the right one using the left one
   387              \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh])
   388  
   389  (**
   390   * When the light client terminates, there are no failed blocks. (Otherwise, someone lied to us.) 
   391   *)            
   392  NoFailedBlocksOnSuccessInv ==
   393      state = "finishedSuccess" =>
   394          \A h \in DOMAIN fetchedLightBlocks:
   395              lightBlockStatus[h] /= "StateFailed"            
   396  
   397  \* This property states that whenever the light client finishes with a positive outcome,
   398  \* the trusted header is still within the trusting period.
   399  \* We expect this property to be violated. And Apalache shows us a counterexample.
   400  PositiveBeforeTrustedHeaderExpires ==
   401      (state = "finishedSuccess") => BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT])
   402      
   403  \* If the primary is correct and the initial trusted block has not expired,
   404  \* then whenever the algorithm terminates, it reports "success"    
   405  CorrectPrimaryAndTimeliness ==
   406    (BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT])
   407      /\ state /= "working" /\ IS_PRIMARY_CORRECT) =>
   408        state = "finishedSuccess"     
   409  
   410  (**
   411    If the primary is correct and there is a trusted block that has not expired,
   412    then whenever the algorithm terminates, it reports "success".
   413  
   414    [LCV-DIST-LIVE.1::SUCCESS-CORR-PRIMARY-CHAIN-OF-TRUST.1]
   415   *)
   416  SuccessOnCorrectPrimaryAndChainOfTrust ==
   417    (\E h \in DOMAIN fetchedLightBlocks:
   418          lightBlockStatus[h] = "StateVerified" /\ BC!InTrustingPeriod(blockchain[h])
   419      /\ state /= "working" /\ IS_PRIMARY_CORRECT) =>
   420        state = "finishedSuccess"     
   421  
   422  \* Lite Client Completeness: If header h was correctly generated by an instance
   423  \* of Tendermint consensus (and its age is less than the trusting period),
   424  \* then the lite client should eventually set trust(h) to true.
   425  \*
   426  \* Note that Completeness assumes that the lite client communicates with a correct full node.
   427  \*
   428  \* We decompose completeness into Termination (liveness) and Precision (safety).
   429  \* Once again, Precision is an inverse version of the safety property in Completeness,
   430  \* as A => B is logically equivalent to ~B => ~A. 
   431  PrecisionInv ==
   432      (state = "finishedFailure")
   433        => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period
   434           \/ \E h \in DOMAIN fetchedLightBlocks:
   435              LET lightBlock == fetchedLightBlocks[h] IN
   436                   \* the full node lied to the lite client about the block header
   437                \/ lightBlock.header /= blockchain[h]
   438                   \* the full node lied to the lite client about the commits
   439                \/ lightBlock.Commits /= lightBlock.header.VS
   440  
   441  \* the old invariant that was found to be buggy by TLC
   442  PrecisionBuggyInv ==
   443      (state = "finishedFailure")
   444        => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period
   445           \/ \E h \in DOMAIN fetchedLightBlocks:
   446              LET lightBlock == fetchedLightBlocks[h] IN
   447              \* the full node lied to the lite client about the block header
   448              lightBlock.header /= blockchain[h]
   449  
   450  \* the worst complexity
   451  Complexity ==
   452      LET N == TARGET_HEIGHT - TRUSTED_HEIGHT + 1 IN
   453      state /= "working" =>
   454          (2 * nprobes <= N * (N - 1)) 
   455  
   456  (*
   457   We omit termination, as the algorithm deadlocks in the end.
   458   So termination can be demonstrated by finding a deadlock.
   459   Of course, one has to analyze the deadlocked state and see that
   460   the algorithm has indeed terminated there.
   461  *)
   462  =============================================================================
   463  \* Modification History
   464  \* Last modified Fri Jun 26 12:08:28 CEST 2020 by igor
   465  \* Created Wed Oct 02 16:39:42 CEST 2019 by igor