github.com/vipernet-xyz/tm@v0.34.24/spec/light-client/verification/Lightclient_A_1.tla (about)

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