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

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