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

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