github.com/aakash4dev/cometbft@v0.38.2/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla (about)

     1  -------------------- MODULE TendermintPBT_001_draft ---------------------------
     2  (*
     3   A TLA+ specification of a simplified Tendermint consensus algorithm, with added clocks 
     4   and proposer-based timestamps. This TLA+ specification extends and modifies 
     5   the Tendermint TLA+ specification for fork accountability: 
     6      https://github.com/aakash4dev/cometbft/blob/main/spec/light-client/accountability/TendermintAcc_004_draft.tla
     7   
     8   * Version 1. A preliminary specification.
     9  
    10   Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020.
    11   Ilina Stoilkovska, Josef Widder, Informal Systems, 2021.
    12   *)
    13  
    14  EXTENDS Integers, FiniteSets
    15  
    16  (********************* PROTOCOL PARAMETERS **********************************)
    17  CONSTANTS
    18      Corr,          \* the set of correct processes 
    19      Faulty,        \* the set of Byzantine processes, may be empty
    20      N,             \* the total number of processes: correct, defective, and Byzantine
    21      T,             \* an upper bound on the number of Byzantine processes
    22      ValidValues,   \* the set of valid values, proposed both by correct and faulty
    23      InvalidValues, \* the set of invalid values, never proposed by the correct ones
    24      MaxRound,      \* the maximal round number
    25      MaxTimestamp,  \* the maximal value of the clock tick
    26      Delay,         \* message delay
    27      Precision,     \* clock precision: the maximal difference between two local clocks  
    28      Accuracy,      \* clock accuracy: the maximal difference between a local clock and the real time
    29      Proposer,      \* the proposer function from 0..NRounds to 1..N
    30      ClockDrift     \* is there clock drift between the local clocks and the global clock
    31  
    32  ASSUME(N = Cardinality(Corr \union Faulty))
    33  
    34  (*************************** DEFINITIONS ************************************)
    35  AllProcs == Corr \union Faulty      \* the set of all processes
    36  Rounds == 0..MaxRound               \* the set of potential rounds
    37  Timestamps == 0..MaxTimestamp       \* the set of clock ticks
    38  NilRound == -1   \* a special value to denote a nil round, outside of Rounds
    39  NilTimestamp == -1 \* a special value to denote a nil timestamp, outside of Ticks
    40  RoundsOrNil == Rounds \union {NilRound}
    41  Values == ValidValues \union InvalidValues \* the set of all values
    42  NilValue == "None"  \* a special value for a nil round, outside of Values
    43  Proposals == Values \X Timestamps
    44  NilProposal == <<NilValue, NilTimestamp>>
    45  ValuesOrNil == Values \union {NilValue}
    46  Decisions == Values \X Timestamps \X Rounds
    47  NilDecision == <<NilValue, NilTimestamp, NilRound>>
    48  
    49  
    50  \* a value hash is modeled as identity
    51  Id(v) == v
    52  
    53  \* The validity predicate
    54  IsValid(v) == v \in ValidValues
    55  
    56  \* the two thresholds that are used in the algorithm
    57  THRESHOLD1 == T + 1     \* at least one process is not faulty
    58  THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T
    59  
    60  Min(S) == CHOOSE x \in S : \A y \in S : x <= y
    61  
    62  Max(S) == CHOOSE x \in S : \A y \in S : y <= x
    63  
    64  (********************* TYPE ANNOTATIONS FOR APALACHE **************************)
    65  \* the operator for type annotations
    66  a <: b == a
    67  
    68  \* the type of message records
    69  MT == [type |-> STRING, src |-> STRING, round |-> Int,
    70         proposal |-> <<STRING, Int>>, validRound |-> Int, id |-> <<STRING, Int>>]
    71  
    72  RP == <<STRING, MT>>  
    73  
    74  \* a type annotation for a message
    75  AsMsg(m) == m <: MT
    76  \* a type annotation for a set of messages
    77  SetOfMsgs(S) == S <: {MT}       
    78  \* a type annotation for an empty set of messages
    79  EmptyMsgSet == SetOfMsgs({})
    80  
    81  SetOfRcvProp(S) == S <: {RP}
    82  EmptyRcvProp == SetOfRcvProp({})
    83  
    84  SetOfProc(S) == S <: {STRING}
    85  EmptyProcSet == SetOfProc({})
    86  
    87  (********************* PROTOCOL STATE VARIABLES ******************************)
    88  VARIABLES
    89    round,    \* a process round number: Corr -> Rounds
    90    localClock,   \* a process local clock: Corr -> Ticks
    91    realTime, \* a reference Newtonian real time
    92    step,     \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }
    93    decision, \* process decision: Corr -> ValuesOrNil
    94    lockedValue,  \* a locked value: Corr -> ValuesOrNil
    95    lockedRound,  \* a locked round: Corr -> RoundsOrNil
    96    validValue,   \* a valid value: Corr -> ValuesOrNil
    97    validRound    \* a valid round: Corr -> RoundsOrNil
    98  
    99  \* book-keeping variables
   100  VARIABLES
   101    msgsPropose,   \* PROPOSE messages broadcast in the system, Rounds -> Messages
   102    msgsPrevote,   \* PREVOTE messages broadcast in the system, Rounds -> Messages
   103    msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages
   104    receivedTimelyProposal, \* used to keep track when a process receives a timely PROPOSAL message, {<<Corr, Messages>>}
   105    inspectedProposal, \* used to keep track when a process tries to receive a message, [Rounds -> <<Corr, Messages>>]
   106    evidence, \* the messages that were used by the correct processes to make transitions
   107    action,        \* we use this variable to see which action was taken
   108    beginConsensus, \* the minimum of the local clocks in the initial state, Int
   109    endConsensus, \* the local time when a decision is made, [Corr -> Int]
   110    lastBeginConsensus, \* the maximum of the local clocks in the initial state, Int
   111    proposalTime, \* the real time when a proposer proposes in a round, [Rounds -> Int]
   112    proposalReceivedTime \* the real time when a correct process first receives a proposal message in a round, [Rounds -> Int]
   113  
   114  (* to see a type invariant, check TendermintAccInv3 *)  
   115   
   116  \* a handy definition used in UNCHANGED
   117  vars == <<round, step, decision, lockedValue, lockedRound,
   118            validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
   119            localClock, realTime, receivedTimelyProposal, inspectedProposal, action,
   120            beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   121  
   122  (********************* PROTOCOL INITIALIZATION ******************************)
   123  FaultyProposals(r) ==
   124      SetOfMsgs([type: {"PROPOSAL"}, src: Faulty,
   125                 round: {r}, proposal: Proposals, validRound: RoundsOrNil])
   126  
   127  AllFaultyProposals ==
   128      SetOfMsgs([type: {"PROPOSAL"}, src: Faulty,
   129                 round: Rounds, proposal: Proposals, validRound: RoundsOrNil])
   130  
   131  FaultyPrevotes(r) ==
   132      SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: {r}, id: Proposals])
   133  
   134  AllFaultyPrevotes ==    
   135      SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Proposals])
   136  
   137  FaultyPrecommits(r) ==
   138      SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: {r}, id: Proposals])
   139  
   140  AllFaultyPrecommits ==
   141      SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Proposals])
   142     
   143  AllProposals ==
   144      SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs,
   145                 round: Rounds, proposal: Proposals, validRound: RoundsOrNil])    
   146  
   147  RoundProposals(r) ==
   148      SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs,
   149                 round: {r}, proposal: Proposals, validRound: RoundsOrNil])    
   150  
   151  BenignRoundsInMessages(msgfun) ==
   152    \* the message function never contains a message for a wrong round
   153    \A r \in Rounds:
   154      \A m \in msgfun[r]:
   155        r = m.round
   156  
   157  \* The initial states of the protocol. Some faults can be in the system already.
   158  Init ==
   159      /\ round = [p \in Corr |-> 0]
   160      /\ \/ /\ ~ClockDrift
   161            /\ localClock \in [Corr -> 0..Accuracy]
   162         \/ /\ ClockDrift
   163            /\ localClock = [p \in Corr |-> 0]
   164      /\ realTime = 0
   165      /\ step = [p \in Corr |-> "PROPOSE"]
   166      /\ decision = [p \in Corr |-> NilDecision]
   167      /\ lockedValue = [p \in Corr |-> NilValue]
   168      /\ lockedRound = [p \in Corr |-> NilRound]
   169      /\ validValue = [p \in Corr |-> NilValue]
   170      /\ validRound = [p \in Corr |-> NilRound]
   171      /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals]
   172      /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes]
   173      /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits]
   174      /\ receivedTimelyProposal = EmptyRcvProp
   175      /\ inspectedProposal = [r \in Rounds |-> EmptyProcSet]
   176      /\ BenignRoundsInMessages(msgsPropose)
   177      /\ BenignRoundsInMessages(msgsPrevote)
   178      /\ BenignRoundsInMessages(msgsPrecommit)
   179      /\ evidence = EmptyMsgSet
   180      /\ action' = "Init"
   181      /\ beginConsensus = Min({localClock[p] : p \in Corr})
   182      /\ endConsensus = [p \in Corr |-> NilTimestamp]
   183      /\ lastBeginConsensus = Max({localClock[p] : p \in Corr})
   184      /\ proposalTime = [r \in Rounds |-> NilTimestamp]
   185      /\ proposalReceivedTime = [r \in Rounds |-> NilTimestamp]
   186  
   187  (************************ MESSAGE PASSING ********************************)
   188  BroadcastProposal(pSrc, pRound, pProposal, pValidRound) ==
   189    LET newMsg ==
   190      AsMsg([type |-> "PROPOSAL", src |-> pSrc, round |-> pRound,
   191             proposal |-> pProposal, validRound |-> pValidRound])
   192    IN
   193    msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}]
   194  
   195  BroadcastPrevote(pSrc, pRound, pId) ==
   196    LET newMsg == AsMsg([type |-> "PREVOTE",
   197                         src |-> pSrc, round |-> pRound, id |-> pId])
   198    IN
   199    msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}]
   200  
   201  BroadcastPrecommit(pSrc, pRound, pId) ==
   202    LET newMsg == AsMsg([type |-> "PRECOMMIT",
   203                         src |-> pSrc, round |-> pRound, id |-> pId])
   204    IN
   205    msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}]
   206  
   207  
   208  (***************************** TIME **************************************)
   209  
   210  \* [PBTS-CLOCK-PRECISION.0]
   211  SynchronizedLocalClocks ==
   212      \A p \in Corr : \A q \in Corr : 
   213          p /= q => 
   214              \/ /\ localClock[p] >= localClock[q]
   215                 /\ localClock[p] - localClock[q] < Precision 
   216              \/ /\ localClock[p] < localClock[q]
   217                 /\ localClock[q] - localClock[p] < Precision
   218      
   219  \* [PBTS-PROPOSE.0]
   220  Proposal(v, t) ==
   221      <<v, t>>
   222  
   223  \* [PBTS-DECISION-ROUND.0]
   224  Decision(v, t, r) ==
   225      <<v, t, r>>
   226  
   227  (**************** MESSAGE PROCESSING TRANSITIONS *************************)
   228  \* lines 12-13
   229  StartRound(p, r) ==
   230     /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus
   231     /\ round' = [round EXCEPT ![p] = r]
   232     /\ step' = [step EXCEPT ![p] = "PROPOSE"] 
   233  
   234  \* lines 14-19, a proposal may be sent later
   235  InsertProposal(p) == 
   236    LET r == round[p] IN
   237    /\ p = Proposer[r]
   238    /\ step[p] = "PROPOSE"
   239      \* if the proposer is sending a proposal, then there are no other proposals
   240      \* by the correct processes for the same round
   241    /\ \A m \in msgsPropose[r]: m.src /= p
   242    /\ \E v \in ValidValues: 
   243        LET proposal == IF validValue[p] /= NilValue 
   244                        THEN Proposal(validValue[p], localClock[p])  
   245                        ELSE Proposal(v, localClock[p]) IN
   246                       
   247        /\ BroadcastProposal(p, round[p], proposal, validRound[p])
   248        /\ proposalTime' = [proposalTime EXCEPT ![r] = realTime]
   249    /\ UNCHANGED <<evidence, round, decision, lockedValue, lockedRound,
   250                  validValue, step, validRound, msgsPrevote, msgsPrecommit,
   251                  localClock, realTime, receivedTimelyProposal, inspectedProposal,
   252                  beginConsensus, endConsensus, lastBeginConsensus, proposalReceivedTime>>
   253    /\ action' = "InsertProposal"
   254    
   255  \* a new action used to filter messages that are not on time
   256  \* [PBTS-RECEPTION-STEP.0]
   257  ReceiveProposal(p) ==
   258    \E v \in Values, t \in Timestamps:
   259      /\ LET r == round[p] IN
   260         LET msg ==
   261          AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
   262                 round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN
   263        /\ msg \in msgsPropose[round[p]] 
   264        /\ p \notin inspectedProposal[r]
   265        /\ <<p, msg>> \notin receivedTimelyProposal
   266        /\ inspectedProposal' = [inspectedProposal EXCEPT ![r] = @ \union {p}]
   267        /\ \/ /\ localClock[p] - Precision < t 
   268              /\ t < localClock[p] + Precision + Delay
   269              /\ receivedTimelyProposal' = receivedTimelyProposal \union {<<p, msg>>}
   270              /\ \/ /\ proposalReceivedTime[r] = NilTimestamp
   271                    /\ proposalReceivedTime' = [proposalReceivedTime EXCEPT ![r] = realTime]
   272                 \/ /\ proposalReceivedTime[r] /= NilTimestamp
   273                    /\ UNCHANGED proposalReceivedTime
   274           \/ /\ \/ localClock[p] - Precision >= t
   275                 \/ t >= localClock[p] + Precision + Delay
   276              /\ UNCHANGED <<receivedTimelyProposal, proposalReceivedTime>>
   277        /\ UNCHANGED <<round, step, decision, lockedValue, lockedRound,
   278            validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
   279            localClock, realTime, beginConsensus, endConsensus, lastBeginConsensus, proposalTime>>
   280        /\ action' = "ReceiveProposal"
   281        
   282  \* lines 22-27
   283  UponProposalInPropose(p) ==
   284    \E v \in Values, t \in Timestamps:
   285      /\ step[p] = "PROPOSE" (* line 22 *)
   286      /\ LET msg ==
   287          AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
   288                 round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN
   289        /\ <<p, msg>> \in receivedTimelyProposal \* updated line 22
   290        /\ evidence' = {msg} \union evidence
   291      /\ LET mid == (* line 23 *)
   292           IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v)
   293           THEN Id(Proposal(v, t))
   294           ELSE NilProposal
   295         IN
   296         BroadcastPrevote(p, round[p], mid) \* lines 24-26
   297      /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   298      /\ UNCHANGED <<round, decision, lockedValue, lockedRound,
   299                     validValue, validRound, msgsPropose, msgsPrecommit,
   300                     localClock, realTime, receivedTimelyProposal, inspectedProposal,
   301                     beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   302      /\ action' = "UponProposalInPropose"
   303  
   304  \* lines 28-33        
   305  \* [PBTS-ALG-OLD-PREVOTE.0]
   306  UponProposalInProposeAndPrevote(p) ==
   307    \E v \in Values, t1 \in Timestamps, t2 \in Timestamps, vr \in Rounds:
   308      /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part
   309      /\ LET msg ==
   310           AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
   311                  round |-> round[p], proposal |-> Proposal(v, t1), validRound |-> vr])
   312         IN
   313         /\ <<p, msg>> \in receivedTimelyProposal \* updated line 28
   314         /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(Proposal(v, t2)) } IN
   315            /\ Cardinality(PV) >= THRESHOLD2 \* line 28
   316            /\ evidence' = PV \union {msg} \union evidence
   317      /\ LET mid == (* line 29 *)
   318           IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v)
   319           THEN Id(Proposal(v, t1)) 
   320           ELSE NilProposal 
   321         IN
   322         BroadcastPrevote(p, round[p], mid) \* lines 24-26
   323      /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   324      /\ UNCHANGED <<round, decision, lockedValue, lockedRound,
   325                     validValue, validRound, msgsPropose, msgsPrecommit,
   326                     localClock, realTime, receivedTimelyProposal, inspectedProposal,
   327                     beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   328      /\ action' = "UponProposalInProposeAndPrevote"
   329                       
   330   \* lines 34-35 + lines 61-64 (onTimeoutPrevote)
   331  UponQuorumOfPrevotesAny(p) ==
   332    /\ step[p] = "PREVOTE" \* line 34 and 61
   333    /\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]:
   334        \* find the unique voters in the evidence
   335        LET Voters == { m.src: m \in MyEvidence } IN
   336        \* compare the number of the unique voters against the threshold
   337        /\ Cardinality(Voters) >= THRESHOLD2 \* line 34
   338        /\ evidence' = MyEvidence \union evidence
   339        /\ BroadcastPrecommit(p, round[p], NilProposal)
   340        /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   341        /\ UNCHANGED <<round, decision, lockedValue, lockedRound,
   342                      validValue, validRound, msgsPropose, msgsPrevote,
   343                      localClock, realTime, receivedTimelyProposal, inspectedProposal,
   344                      beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   345        /\ action' = "UponQuorumOfPrevotesAny"
   346                       
   347  \* lines 36-46
   348  \* [PBTS-ALG-NEW-PREVOTE.0]
   349  UponProposalInPrevoteOrCommitAndPrevote(p) ==
   350    \E v \in ValidValues, t \in Timestamps, vr \in RoundsOrNil:
   351      /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36
   352      /\ LET msg ==
   353           AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]],
   354                  round |-> round[p], proposal |-> Proposal(v, t), validRound |-> vr]) IN
   355          /\ <<p, msg>> \in receivedTimelyProposal \* updated line 36
   356          /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(Proposal(v, t)) } IN
   357            /\ Cardinality(PV) >= THRESHOLD2 \* line 36
   358            /\ evidence' = PV \union {msg} \union evidence
   359      /\  IF step[p] = "PREVOTE"
   360          THEN \* lines 38-41:
   361            /\ lockedValue' = [lockedValue EXCEPT ![p] = v]
   362            /\ lockedRound' = [lockedRound EXCEPT ![p] = round[p]]
   363            /\ BroadcastPrecommit(p, round[p], Id(Proposal(v, t)))
   364            /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   365          ELSE
   366            UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>>
   367        \* lines 42-43
   368      /\ validValue' = [validValue EXCEPT ![p] = v]
   369      /\ validRound' = [validRound EXCEPT ![p] = round[p]]
   370      /\ UNCHANGED <<round, decision, msgsPropose, msgsPrevote,
   371                    localClock, realTime, receivedTimelyProposal, inspectedProposal,
   372                    beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   373      /\ action' = "UponProposalInPrevoteOrCommitAndPrevote"
   374  
   375  \* lines 47-48 + 65-67 (onTimeoutPrecommit)
   376  UponQuorumOfPrecommitsAny(p) ==
   377    /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]:
   378        \* find the unique committers in the evidence
   379        LET Committers == { m.src: m \in MyEvidence } IN
   380        \* compare the number of the unique committers against the threshold
   381        /\ Cardinality(Committers) >= THRESHOLD2 \* line 47
   382        /\ evidence' = MyEvidence \union evidence
   383        /\ round[p] + 1 \in Rounds
   384        /\ StartRound(p, round[p] + 1)   
   385        /\ UNCHANGED <<decision, lockedValue, lockedRound, validValue,
   386                      validRound, msgsPropose, msgsPrevote, msgsPrecommit,
   387                      localClock, realTime, receivedTimelyProposal, inspectedProposal,
   388                      beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   389        /\ action' = "UponQuorumOfPrecommitsAny"
   390                       
   391  \* lines 49-54        
   392  \* [PBTS-ALG-DECIDE.0]
   393  UponProposalInPrecommitNoDecision(p) ==
   394    /\ decision[p] = NilDecision \* line 49
   395    /\ \E v \in ValidValues, t \in Timestamps (* line 50*) , r \in Rounds, vr \in RoundsOrNil:
   396      /\ LET msg == AsMsg([type |-> "PROPOSAL", src |-> Proposer[r],
   397                             round |-> r, proposal |-> Proposal(v, t), validRound |-> vr]) IN
   398       /\ msg \in msgsPropose[r] \* line 49
   399       /\ p \in inspectedProposal[r]
   400       /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(Proposal(v, t)) } IN
   401         /\ Cardinality(PV) >= THRESHOLD2 \* line 49
   402         /\ evidence' = PV \union {msg} \union evidence
   403         /\ decision' = [decision EXCEPT ![p] = Decision(v, t, round[p])] \* update the decision, line 51
   404      \* The original algorithm does not have 'DECIDED', but it increments the height.
   405      \* We introduced 'DECIDED' here to prevent the process from changing its decision.
   406         /\ endConsensus' = [endConsensus EXCEPT ![p] = localClock[p]]
   407         /\ step' = [step EXCEPT ![p] = "DECIDED"]
   408         /\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
   409                       validRound, msgsPropose, msgsPrevote, msgsPrecommit,
   410                       localClock, realTime, receivedTimelyProposal, inspectedProposal,
   411                       beginConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   412         /\ action' = "UponProposalInPrecommitNoDecision"
   413                                                            
   414  \* the actions below are not essential for safety, but added for completeness
   415  
   416  \* lines 20-21 + 57-60
   417  OnTimeoutPropose(p) ==
   418    /\ step[p] = "PROPOSE"
   419    /\ p /= Proposer[round[p]]
   420    /\ BroadcastPrevote(p, round[p], NilProposal)
   421    /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   422    /\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
   423                  validRound, decision, evidence, msgsPropose, msgsPrecommit,
   424                  localClock, realTime, receivedTimelyProposal, inspectedProposal,
   425                  beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   426    /\ action' = "OnTimeoutPropose"
   427  
   428  \* lines 44-46
   429  OnQuorumOfNilPrevotes(p) ==
   430    /\ step[p] = "PREVOTE"
   431    /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilProposal) } IN
   432      /\ Cardinality(PV) >= THRESHOLD2 \* line 36
   433      /\ evidence' = PV \union evidence
   434      /\ BroadcastPrecommit(p, round[p], Id(NilProposal))
   435      /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   436      /\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
   437                    validRound, decision, msgsPropose, msgsPrevote,
   438                    localClock, realTime, receivedTimelyProposal, inspectedProposal,
   439                    beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   440      /\ action' = "OnQuorumOfNilPrevotes"
   441  
   442  \* lines 55-56
   443  OnRoundCatchup(p) ==
   444    \E r \in {rr \in Rounds: rr > round[p]}:
   445      LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN
   446      \E MyEvidence \in SUBSET RoundMsgs:
   447          LET Faster == { m.src: m \in MyEvidence } IN
   448          /\ Cardinality(Faster) >= THRESHOLD1
   449          /\ evidence' = MyEvidence \union evidence
   450          /\ StartRound(p, r)
   451          /\ UNCHANGED <<decision, lockedValue, lockedRound, validValue,
   452                        validRound, msgsPropose, msgsPrevote, msgsPrecommit,
   453                        localClock, realTime, receivedTimelyProposal, inspectedProposal,
   454                        beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   455          /\ action' = "OnRoundCatchup"
   456  
   457  
   458  (********************* PROTOCOL TRANSITIONS ******************************)
   459  \* advance the global clock
   460  AdvanceRealTime == 
   461      /\ realTime < MaxTimestamp
   462      /\ realTime' = realTime + 1
   463      /\ \/ /\ ~ClockDrift
   464            /\ localClock' = [p \in Corr |-> localClock[p] + 1]  
   465         \/ /\ ClockDrift
   466            /\ UNCHANGED localClock
   467      /\ UNCHANGED <<round, step, decision, lockedValue, lockedRound,
   468                    validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
   469                    localClock, receivedTimelyProposal, inspectedProposal,
   470                    beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 
   471      /\ action' = "AdvanceRealTime"
   472      
   473  \* advance the local clock of node p
   474  AdvanceLocalClock(p) ==
   475      /\ localClock[p] < MaxTimestamp
   476      /\ localClock' = [localClock EXCEPT ![p] = @ + 1]
   477      /\ UNCHANGED <<round, step, decision, lockedValue, lockedRound,
   478                    validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit,
   479                    realTime, receivedTimelyProposal, inspectedProposal,
   480                    beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>>
   481      /\ action' = "AdvanceLocalClock"
   482  
   483  \* process timely messages
   484  MessageProcessing(p) ==
   485      \* start round
   486      \/ InsertProposal(p)
   487      \* reception step
   488      \/ ReceiveProposal(p)
   489      \* processing step
   490      \/ UponProposalInPropose(p)
   491      \/ UponProposalInProposeAndPrevote(p)
   492      \/ UponQuorumOfPrevotesAny(p)
   493      \/ UponProposalInPrevoteOrCommitAndPrevote(p)
   494      \/ UponQuorumOfPrecommitsAny(p)
   495      \/ UponProposalInPrecommitNoDecision(p)
   496      \* the actions below are not essential for safety, but added for completeness
   497      \/ OnTimeoutPropose(p)
   498      \/ OnQuorumOfNilPrevotes(p)
   499      \/ OnRoundCatchup(p)
   500  
   501  (*
   502   * A system transition. In this specificatiom, the system may eventually deadlock,
   503   * e.g., when all processes decide. This is expected behavior, as we focus on safety.
   504   *)
   505  Next ==
   506    \/ AdvanceRealTime
   507    \/ /\ ClockDrift
   508       /\ \E p \in Corr: AdvanceLocalClock(p)
   509    \/ /\ SynchronizedLocalClocks
   510       /\ \E p \in Corr: MessageProcessing(p)
   511  
   512  -----------------------------------------------------------------------------
   513  
   514  (*************************** INVARIANTS *************************************)
   515  
   516  \* [PBTS-INV-AGREEMENT.0]
   517  AgreementOnValue ==
   518      \A p, q \in Corr:
   519          /\ decision[p] /= NilDecision
   520          /\ decision[q] /= NilDecision
   521          => \E v \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r1 \in Rounds, r2 \in Rounds : 
   522              /\ decision[p] = Decision(v, t1, r1)
   523              /\ decision[q] = Decision(v, t2, r2)
   524  
   525  \* [PBTS-INV-TIME-AGR.0]
   526  AgreementOnTime ==
   527      \A p, q \in Corr: 
   528          \A v1 \in ValidValues, v2 \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r \in Rounds :
   529              /\ decision[p] = Decision(v1, t1, r)
   530              /\ decision[q] = Decision(v2, t2, r)
   531              => t1 = t2
   532  
   533  \* [PBTS-CONSENSUS-TIME-VALID.0]
   534  ConsensusTimeValid ==
   535      \A p \in Corr, t \in Timestamps : 
   536      \* if a process decides on v and t
   537      (\E v \in ValidValues, r \in Rounds : decision[p] = Decision(v, t, r))
   538          \* then 
   539          => /\ beginConsensus - Precision <= t 
   540             /\ t < endConsensus[p] + Precision + Delay
   541  
   542  \* [PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0]
   543  ConsensusSafeValidCorrProp ==
   544      \A v \in ValidValues, t \in Timestamps :
   545          \* if the proposer in the first round is correct
   546         (/\ Proposer[0] \in Corr
   547          \* and there exists a process that decided on v, t 
   548          /\ \E p \in Corr, r \in Rounds : decision[p] = Decision(v, t, r))
   549              \* then t is between the minimal and maximal initial local time
   550              => /\ beginConsensus <= t 
   551                 /\ t <= lastBeginConsensus
   552  
   553  \* [PBTS-CONSENSUS-REALTIME-VALID-CORR.0]
   554  ConsensusRealTimeValidCorr ==
   555      \A t \in Timestamps, r \in Rounds :
   556         (/\ \E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r) 
   557          /\ proposalTime[r] /= NilTimestamp)
   558          => /\ proposalTime[r] - Accuracy < t
   559             /\ t < proposalTime[r] + Accuracy
   560  
   561  \* [PBTS-CONSENSUS-REALTIME-VALID.0]
   562  ConsensusRealTimeValid ==
   563      \A t \in Timestamps, r \in Rounds :
   564         (\E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r)) 
   565          => /\ proposalReceivedTime[r] - Accuracy - Precision < t
   566             /\ t < proposalReceivedTime[r] + Accuracy + Precision + Delay
   567  
   568  \* [PBTS-MSG-FAIR.0]
   569  BoundedDelay ==
   570      \A r \in Rounds : 
   571          (/\ proposalTime[r] /= NilTimestamp
   572           /\ proposalTime[r] + Delay < realTime)
   573              => inspectedProposal[r] = Corr
   574  
   575  \* [PBTS-CONSENSUS-TIME-LIVE.0]
   576  ConsensusTimeLive ==
   577      \A r \in Rounds, p \in Corr : 
   578         (/\ proposalTime[r] /= NilTimestamp
   579          /\ proposalTime[r] + Delay < realTime 
   580          /\ Proposer[r] \in Corr
   581          /\ round[p] <= r)
   582              => \E msg \in RoundProposals(r) : <<p, msg>> \in receivedTimelyProposal
   583  
   584  \* a conjunction of all invariants
   585  Inv ==
   586      /\ AgreementOnValue 
   587      /\ AgreementOnTime
   588      /\ ConsensusTimeValid
   589      /\ ConsensusSafeValidCorrProp
   590      /\ ConsensusRealTimeValid
   591      /\ ConsensusRealTimeValidCorr
   592      /\ BoundedDelay
   593  
   594  Liveness ==
   595      ConsensusTimeLive    
   596  
   597  =============================================================================