github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_002_draft.tla (about)

     1  -------------------- MODULE TendermintPBT_002_draft ---------------------------
     2  (*
     3   A TLA+ specification of a simplified Tendermint consensus, 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/tendermint/spec/blob/master/spec/light-client/accountability/TendermintAcc_004_draft.tla
     7   
     8   * Version 2. A preliminary specification.
     9  
    10   Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020.
    11   Ilina Stoilkovska, Josef Widder, Informal Systems, 2021.
    12   Jure Kukovec, Informal Systems, 2022.
    13   *)
    14  
    15  EXTENDS Integers, FiniteSets, Apalache, typedefs
    16  
    17  (********************* PROTOCOL PARAMETERS **********************************)
    18  \* General protocol parameters
    19  CONSTANTS
    20    \* @type: Set(PROCESS);
    21    Corr,          \* the set of correct processes 
    22    \* @type: Set(PROCESS);
    23    Faulty,        \* the set of Byzantine processes, may be empty
    24    \* @type: Int;  
    25    N,             \* the total number of processes: correct, defective, and Byzantine
    26    \* @type: Int;  
    27    T,             \* an upper bound on the number of Byzantine processes
    28    \* @type: Set(VALUE);  
    29    ValidValues,   \* the set of valid values, proposed both by correct and faulty
    30    \* @type: Set(VALUE);  
    31    InvalidValues, \* the set of invalid values, never proposed by the correct ones
    32    \* @type: ROUND;    
    33    MaxRound,      \* the maximal round number
    34    \* @type: ROUND -> PROCESS;
    35    Proposer       \* the proposer function from Rounds to AllProcs
    36  
    37  \* Time-related parameters
    38  CONSTANTS
    39    \* @type: TIME;
    40    MaxTimestamp,  \* the maximal value of the clock tick
    41    \* @type: TIME;
    42    MinTimestamp,  \* the minimal value of the clock tick
    43    \* @type: TIME;
    44    Delay,         \* message delay
    45    \* @type: TIME;
    46    Precision     \* clock precision: the maximal difference between two local clocks  
    47  
    48  ASSUME(N = Cardinality(Corr \union Faulty))
    49  
    50  (*************************** DEFINITIONS ************************************)
    51  \* @type: Set(PROCESS);
    52  AllProcs == Corr \union Faulty      \* the set of all processes
    53  \* @type: Set(ROUND);
    54  Rounds == 0..MaxRound               \* the set of potential rounds
    55  \* @type: Set(TIME);
    56  Timestamps == 0..MaxTimestamp       \* the set of clock ticks
    57  \* @type: ROUND;
    58  NilRound == -1   \* a special value to denote a nil round, outside of Rounds
    59  \* @type: TIME;
    60  NilTimestamp == -1 \* a special value to denote a nil timestamp, outside of Ticks
    61  \* @type: Set(ROUND);
    62  RoundsOrNil == Rounds \union {NilRound}
    63  \* @type: Set(VALUE);
    64  Values == ValidValues \union InvalidValues \* the set of all values
    65  \* @type: VALUE;
    66  NilValue == "None"  \* a special value for a nil round, outside of Values
    67  \* @type: Set(PROPOSAL);
    68  Proposals == Values \X Timestamps \X Rounds
    69  \* @type: PROPOSAL;
    70  NilProposal == <<NilValue, NilTimestamp, NilRound>>
    71  \* @type: Set(VALUE);
    72  ValuesOrNil == Values \union {NilValue}
    73  \* @type: Set(DECISION);
    74  Decisions == Proposals \X Rounds
    75  \* @type: DECISION;
    76  NilDecision == <<NilProposal, NilRound>>
    77  
    78  ValidProposals == ValidValues \X (MinTimestamp..MaxTimestamp) \X Rounds
    79  \* a value hash is modeled as identity
    80  \* @type: (t) => t;
    81  Id(v) == v
    82  
    83  \* The validity predicate
    84  \* @type: (PROPOSAL) => Bool;
    85  IsValid(p) == p \in ValidProposals
    86  
    87  \* Time validity check. If we want MaxTimestamp = \infty, set ValidTime(t) == TRUE
    88  ValidTime(t) == t < MaxTimestamp
    89  
    90  \* @type: (PROPMESSAGE) => VALUE;
    91  MessageValue(msg) == msg.proposal[1]
    92  \* @type: (PROPMESSAGE) => TIME;
    93  MessageTime(msg) == msg.proposal[2]
    94  \* @type: (PROPMESSAGE) => ROUND;
    95  MessageRound(msg) == msg.proposal[3]
    96  
    97  \* @type: (TIME, TIME) => Bool;
    98  IsTimely(processTime, messageTime) ==
    99    /\ processTime >= messageTime - Precision
   100    /\ processTime <= messageTime + Precision + Delay
   101  
   102  \* the two thresholds that are used in the algorithm
   103  \* @type: Int;
   104  THRESHOLD1 == T + 1     \* at least one process is not faulty
   105  \* @type: Int;
   106  THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T
   107  
   108  \* @type: (TIME, TIME) => TIME;
   109  Min2(a,b) == IF a <= b THEN a ELSE b
   110  \* @type: (Set(TIME)) => TIME;
   111  Min(S) == FoldSet( Min2, MaxTimestamp, S )
   112  \* Min(S) == CHOOSE x \in S : \A y \in S : x <= y
   113  
   114  \* @type: (TIME, TIME) => TIME;
   115  Max2(a,b) == IF a >= b THEN a ELSE b
   116  \* @type: (Set(TIME)) => TIME;
   117  Max(S) == FoldSet( Max2, NilTimestamp, S )
   118  \* Max(S) == CHOOSE x \in S : \A y \in S : y <= x
   119  
   120  \* @type: (Set(MESSAGE)) => Int;
   121  Card(S) == 
   122    LET 
   123      \* @type: (Int, MESSAGE) => Int;
   124      PlusOne(i, m) == i + 1
   125    IN FoldSet( PlusOne, 0, S )
   126  
   127  (********************* PROTOCOL STATE VARIABLES ******************************)
   128  VARIABLES
   129    \* @type: PROCESS -> ROUND;
   130    round,    \* a process round number
   131    \* @type: PROCESS -> STEP;
   132    step,     \* a process step
   133    \* @type: PROCESS -> DECISION;
   134    decision, \* process decision
   135    \* @type: PROCESS -> VALUE;
   136    lockedValue,  \* a locked value
   137    \* @type: PROCESS -> ROUND;
   138    lockedRound,  \* a locked round
   139    \* @type: PROCESS -> PROPOSAL;
   140    validValue,   \* a valid value
   141    \* @type: PROCESS -> ROUND;
   142    validRound    \* a valid round
   143  
   144  coreVars == 
   145    <<round, step, decision, lockedValue, 
   146    lockedRound, validValue, validRound>>
   147  
   148  \* time-related variables
   149  VARIABLES  
   150    \* @type: PROCESS -> TIME;
   151    localClock, \* a process local clock: Corr -> Ticks
   152    \* @type: TIME;
   153    realTime   \* a reference Newtonian real time
   154  
   155  temporalVars == <<localClock, realTime>>
   156  
   157  \* book-keeping variables
   158  VARIABLES
   159    \* @type: ROUND -> Set(PROPMESSAGE);
   160    msgsPropose,   \* PROPOSE messages broadcast in the system, Rounds -> Messages
   161    \* @type: ROUND -> Set(PREMESSAGE);
   162    msgsPrevote,   \* PREVOTE messages broadcast in the system, Rounds -> Messages
   163    \* @type: ROUND -> Set(PREMESSAGE);
   164    msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages
   165    \* @type: Set(MESSAGE);
   166    evidence, \* the messages that were used by the correct processes to make transitions
   167    \* @type: ACTION;
   168    action,       \* we use this variable to see which action was taken
   169    \* @type: PROCESS -> Set(PROPMESSAGE);
   170    receivedTimelyProposal, \* used to keep track when a process receives a timely PROPOSAL message
   171    \* @type: <<ROUND,PROCESS>> -> TIME;
   172    inspectedProposal \* used to keep track when a process tries to receive a message
   173    
   174  \* Action is excluded from the tuple, because it always changes
   175  bookkeepingVars == 
   176    <<msgsPropose, msgsPrevote, msgsPrecommit, 
   177    evidence, (*action,*) receivedTimelyProposal, 
   178    inspectedProposal>>
   179  
   180  \* Invariant support
   181  VARIABLES
   182    \* @type: ROUND -> TIME;
   183    beginRound, \* the minimum of the local clocks at the time any process entered a new round
   184    \* @type: PROCESS -> TIME;
   185    endConsensus, \* the local time when a decision is made
   186    \* @type: ROUND -> TIME;
   187    lastBeginRound, \* the maximum of the local clocks in each round
   188    \* @type: ROUND -> TIME;
   189    proposalTime, \* the real time when a proposer proposes in a round
   190    \* @type: ROUND -> TIME;
   191    proposalReceivedTime \* the real time when a correct process first receives a proposal message in a round
   192  
   193  invariantVars == 
   194    <<beginRound, endConsensus, lastBeginRound,
   195    proposalTime, proposalReceivedTime>>
   196  
   197  (* to see a type invariant, check TendermintAccInv3 *)  
   198  
   199  (********************* PROTOCOL INITIALIZATION ******************************)
   200  \* @type: (ROUND) => Set(PROPMESSAGE);
   201  FaultyProposals(r) ==
   202    [
   203      type      : {"PROPOSAL"}, 
   204      src       : Faulty,
   205      round     : {r}, 
   206      proposal  : Proposals, 
   207      validRound: RoundsOrNil
   208    ]
   209  
   210  \* @type: Set(PROPMESSAGE);
   211  AllFaultyProposals ==
   212    [
   213      type      : {"PROPOSAL"}, 
   214      src       : Faulty,
   215      round     : Rounds, 
   216      proposal  : Proposals, 
   217      validRound: RoundsOrNil
   218    ]
   219  
   220  \* @type: (ROUND) => Set(PREMESSAGE);
   221  FaultyPrevotes(r) ==
   222    [
   223      type : {"PREVOTE"}, 
   224      src  : Faulty, 
   225      round: {r}, 
   226      id   : Proposals
   227    ]
   228  
   229  \* @type: Set(PREMESSAGE);
   230  AllFaultyPrevotes ==    
   231    [
   232      type : {"PREVOTE"}, 
   233      src  : Faulty, 
   234      round: Rounds, 
   235      id   : Proposals
   236    ]
   237  
   238  \* @type: (ROUND) => Set(PREMESSAGE);
   239  FaultyPrecommits(r) ==
   240    [
   241      type : {"PRECOMMIT"}, 
   242      src  : Faulty, 
   243      round: {r}, 
   244      id   : Proposals
   245    ]
   246  
   247  \* @type: Set(PREMESSAGE);
   248  AllFaultyPrecommits ==
   249    [
   250      type : {"PRECOMMIT"}, 
   251      src  : Faulty, 
   252      round: Rounds, 
   253      id   : Proposals
   254    ]
   255  
   256  \* @type: Set(PROPMESSAGE);
   257  AllProposals ==
   258    [
   259      type      : {"PROPOSAL"}, 
   260      src       : AllProcs,
   261      round     : Rounds, 
   262      proposal  : Proposals, 
   263      validRound: RoundsOrNil
   264    ]    
   265  
   266  \* @type: (ROUND) => Set(PROPMESSAGE);
   267  RoundProposals(r) ==
   268    [
   269      type      : {"PROPOSAL"}, 
   270      src       : AllProcs,
   271      round     : {r}, 
   272      proposal  : Proposals, 
   273      validRound: RoundsOrNil
   274    ]
   275  
   276  \* @type: (ROUND -> Set(MESSAGE)) => Bool;
   277  BenignRoundsInMessages(msgfun) ==
   278    \* the message function never contains a message for a wrong round
   279    \A r \in Rounds:
   280      \A m \in msgfun[r]:
   281        r = m.round
   282  
   283  \* The initial states of the protocol. Some faults can be in the system already.
   284  Init ==
   285      /\ round = [p \in Corr |-> 0]
   286      /\ localClock \in [Corr -> MinTimestamp..(MinTimestamp + Precision)]
   287      /\ realTime = 0
   288      /\ step = [p \in Corr |-> "PROPOSE"]
   289      /\ decision = [p \in Corr |-> NilDecision]
   290      /\ lockedValue = [p \in Corr |-> NilValue]
   291      /\ lockedRound = [p \in Corr |-> NilRound]
   292      /\ validValue = [p \in Corr |-> NilProposal]
   293      /\ validRound = [p \in Corr |-> NilRound]
   294      /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals]
   295      /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes]
   296      /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits]
   297      /\ receivedTimelyProposal = [p \in Corr |-> {}]
   298      /\ inspectedProposal = [r \in Rounds, p \in Corr |-> NilTimestamp]
   299      /\ BenignRoundsInMessages(msgsPropose)
   300      /\ BenignRoundsInMessages(msgsPrevote)
   301      /\ BenignRoundsInMessages(msgsPrecommit)
   302      /\ evidence = {}
   303      /\ action' = "Init"
   304      /\ beginRound = 
   305        [r \in Rounds |-> 
   306          IF r = 0
   307          THEN Min({localClock[p] : p \in Corr})
   308          ELSE MaxTimestamp
   309        ]
   310      /\ endConsensus = [p \in Corr |-> NilTimestamp]
   311      /\ lastBeginRound = 
   312        [r \in Rounds |-> 
   313          IF r = 0
   314          THEN Max({localClock[p] : p \in Corr})
   315          ELSE NilTimestamp
   316        ]
   317      /\ proposalTime = [r \in Rounds |-> NilTimestamp]
   318      /\ proposalReceivedTime = [r \in Rounds |-> NilTimestamp]
   319  
   320  (************************ MESSAGE PASSING ********************************)
   321  \* @type: (PROCESS, ROUND, PROPOSAL, ROUND) => Bool;
   322  BroadcastProposal(pSrc, pRound, pProposal, pValidRound) ==
   323    LET 
   324      \* @type: PROPMESSAGE;
   325      newMsg ==
   326      [
   327        type       |-> "PROPOSAL", 
   328        src        |-> pSrc, 
   329        round      |-> pRound,
   330        proposal   |-> pProposal, 
   331        validRound |-> pValidRound
   332      ]
   333    IN
   334    /\ msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}]
   335  
   336  \* @type: (PROCESS, ROUND, PROPOSAL) => Bool;
   337  BroadcastPrevote(pSrc, pRound, pId) ==
   338    LET 
   339      \* @type: PREMESSAGE;
   340      newMsg == 
   341      [
   342        type  |-> "PREVOTE",
   343        src   |-> pSrc, 
   344        round |-> pRound, 
   345        id    |-> pId
   346      ]
   347    IN
   348    /\ msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}]
   349  
   350  \* @type: (PROCESS, ROUND, PROPOSAL) => Bool;
   351  BroadcastPrecommit(pSrc, pRound, pId) ==
   352    LET
   353      \* @type: PREMESSAGE; 
   354      newMsg == 
   355      [
   356        type  |-> "PRECOMMIT",
   357        src   |-> pSrc, 
   358        round |-> pRound, 
   359        id    |-> pId
   360      ]
   361    IN
   362    /\ msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}]
   363  
   364  (***************************** TIME **************************************)
   365  
   366  \* [PBTS-CLOCK-PRECISION.0]
   367  \* @type: Bool;
   368  SynchronizedLocalClocks ==
   369      \A p \in Corr : \A q \in Corr : 
   370          p /= q => 
   371              \/ /\ localClock[p] >= localClock[q]
   372                 /\ localClock[p] - localClock[q] < Precision 
   373              \/ /\ localClock[p] < localClock[q]
   374                 /\ localClock[q] - localClock[p] < Precision
   375      
   376  \* [PBTS-PROPOSE.0]
   377  \* @type: (VALUE, TIME, ROUND) => PROPOSAL;
   378  Proposal(v, t, r) ==
   379      <<v, t, r>>
   380  
   381  \* [PBTS-DECISION-ROUND.0]
   382  \* @type: (PROPOSAL, ROUND) => DECISION;
   383  Decision(p, r) ==
   384      <<p, r>>
   385  
   386  (**************** MESSAGE PROCESSING TRANSITIONS *************************)
   387  \* lines 12-13
   388  \* @type: (PROCESS, ROUND) => Bool;
   389  StartRound(p, r) ==
   390     /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus
   391     /\ round' = [round EXCEPT ![p] = r]
   392     /\ step' = [step EXCEPT ![p] = "PROPOSE"]
   393     \* We only need to update (last)beginRound[r] once a process enters round `r`
   394     /\ beginRound' = [beginRound EXCEPT ![r] = Min2(@, localClock[p])]
   395     /\ lastBeginRound' = [lastBeginRound EXCEPT ![r] = Max2(@, localClock[p])]
   396  
   397  \* lines 14-19, a proposal may be sent later
   398  \* @type: (PROCESS) => Bool;
   399  InsertProposal(p) == 
   400    LET r == round[p] IN
   401    /\ p = Proposer[r]
   402    /\ step[p] = "PROPOSE"
   403      \* if the proposer is sending a proposal, then there are no other proposals
   404      \* by the correct processes for the same round
   405    /\ \A m \in msgsPropose[r]: m.src /= p
   406    \* /\ localClock[p] > 
   407    /\ \E v \in ValidValues: 
   408        LET proposal == 
   409          IF validValue[p] /= NilProposal 
   410          THEN validValue[p]
   411          ELSE Proposal(v, localClock[p], r)
   412        IN               
   413        /\ BroadcastProposal(p, r, proposal, validRound[p])
   414        /\ proposalTime' = [proposalTime EXCEPT ![r] = realTime]
   415    /\ UNCHANGED <<temporalVars, coreVars>>
   416    /\ UNCHANGED 
   417      <<(*msgsPropose,*) msgsPrevote, msgsPrecommit, 
   418      evidence, receivedTimelyProposal, inspectedProposal>>
   419    /\ UNCHANGED
   420      <<beginRound, endConsensus, lastBeginRound,
   421      (*proposalTime,*) proposalReceivedTime>>
   422    /\ action' = "InsertProposal"
   423    
   424  \* a new action used to filter messages that are not on time
   425  \* [PBTS-RECEPTION-STEP.0]
   426  \* @type: (PROCESS) => Bool;
   427  ReceiveProposal(p) ==
   428    \E v \in Values, t \in Timestamps:
   429      /\ LET r == round[p] IN
   430         LET 
   431          \* @type: PROPMESSAGE;
   432          msg ==
   433            [
   434              type       |-> "PROPOSAL", 
   435              src        |-> Proposer[round[p]],
   436              round      |-> round[p], 
   437              proposal   |-> Proposal(v, t, r), 
   438              validRound |-> NilRound
   439            ] 
   440         IN
   441        /\ msg \in msgsPropose[round[p]] 
   442        /\ inspectedProposal[r,p] = NilTimestamp
   443        /\ msg \notin receivedTimelyProposal[p]
   444        /\ inspectedProposal' = [inspectedProposal EXCEPT ![r,p] = localClock[p]]
   445        /\ LET 
   446            isTimely == IsTimely(localClock[p], t)
   447           IN
   448            \/ /\ isTimely
   449               /\ receivedTimelyProposal' = [receivedTimelyProposal EXCEPT ![p] = @ \union {msg}]
   450               /\ LET
   451                    isNilTimestamp == proposalReceivedTime[r] = NilTimestamp
   452                  IN
   453                    \/ /\ isNilTimestamp
   454                       /\ proposalReceivedTime' = [proposalReceivedTime EXCEPT ![r] = realTime]
   455                    \/ /\ ~isNilTimestamp
   456                       /\ UNCHANGED proposalReceivedTime
   457            \/ /\ ~isTimely
   458               /\ UNCHANGED <<receivedTimelyProposal, proposalReceivedTime>>
   459        /\ UNCHANGED <<temporalVars, coreVars>>
   460        /\ UNCHANGED
   461          <<msgsPropose, msgsPrevote, msgsPrecommit, 
   462          evidence(*, receivedTimelyProposal, inspectedProposal*)>>
   463        /\ UNCHANGED 
   464          <<beginRound, endConsensus, lastBeginRound,
   465          proposalTime(*, proposalReceivedTime*)>>
   466        /\ action' = "ReceiveProposal"
   467        
   468  \* lines 22-27
   469  \* @type: (PROCESS) => Bool;
   470  UponProposalInPropose(p) ==
   471    \E v \in Values, t \in Timestamps:
   472      LET 
   473        r == round[p]
   474      IN LET
   475        \* @type: PROPOSAL;
   476        prop == Proposal(v,t,r)
   477      IN
   478      /\ step[p] = "PROPOSE" (* line 22 *)
   479      /\ LET 
   480          \* @type: PROPMESSAGE; 
   481          msg ==
   482          [
   483            type       |-> "PROPOSAL", 
   484            src        |-> Proposer[r],
   485            round      |-> r, 
   486            proposal   |-> prop, 
   487            validRound |-> NilRound
   488          ] 
   489         IN
   490        /\ msg \in receivedTimelyProposal[p] \* updated line 22
   491        /\ evidence' = {msg} \union evidence
   492      /\ LET mid == (* line 23 *)
   493           IF IsValid(prop) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v)
   494           THEN Id(prop)
   495           ELSE NilProposal
   496         IN
   497         BroadcastPrevote(p, r, mid) \* lines 24-26
   498      /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   499      /\ UNCHANGED <<temporalVars, invariantVars>>
   500      /\ UNCHANGED
   501        <<round, (*step,*) decision, lockedValue, 
   502        lockedRound, validValue, validRound>>
   503      /\ UNCHANGED 
   504        <<msgsPropose, (*msgsPrevote,*) msgsPrecommit, 
   505        (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   506      /\ action' = "UponProposalInPropose"
   507  
   508  \* lines 28-33        
   509  \* [PBTS-ALG-OLD-PREVOTE.0]
   510  \* @type: (PROCESS) => Bool;
   511  UponProposalInProposeAndPrevote(p) ==
   512    \E v \in Values, t \in Timestamps, vr \in Rounds, pr \in Rounds:
   513      LET 
   514        r == round[p]
   515      IN LET
   516        \* @type: PROPOSAL;
   517        prop == Proposal(v,t,pr)
   518      IN
   519      /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < r \* line 28, the while part
   520      /\ pr <= vr
   521      /\ LET
   522          \* @type: PROPMESSAGE; 
   523          msg ==
   524          [
   525            type       |-> "PROPOSAL", 
   526            src        |-> Proposer[r],
   527            round      |-> r, 
   528            proposal   |-> prop, 
   529            validRound |-> vr
   530          ]
   531         IN
   532         \* Changed from 001: no need to re-check timeliness
   533         /\ msg \in msgsPropose[r] \* line 28
   534         /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(prop) } IN
   535            /\ Cardinality(PV) >= THRESHOLD2 \* line 28
   536            /\ evidence' = PV \union {msg} \union evidence
   537      /\ LET mid == (* line 29 *)
   538           IF IsValid(prop) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v)
   539           THEN Id(prop) 
   540           ELSE NilProposal 
   541         IN
   542         BroadcastPrevote(p, r, mid) \* lines 24-26
   543      /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   544      /\ UNCHANGED <<temporalVars, invariantVars>>
   545      /\ UNCHANGED
   546        <<round, (*step,*) decision, lockedValue, 
   547        lockedRound, validValue, validRound>>
   548      /\ UNCHANGED 
   549        <<msgsPropose, (*msgsPrevote,*) msgsPrecommit, 
   550        (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   551      /\ action' = "UponProposalInProposeAndPrevote"
   552                       
   553  \* lines 34-35 + lines 61-64 (onTimeoutPrevote)
   554  \* @type: (PROCESS) => Bool;
   555  UponQuorumOfPrevotesAny(p) ==
   556    /\ step[p] = "PREVOTE" \* line 34 and 61
   557    /\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]:
   558        \* find the unique voters in the evidence
   559        LET Voters == { m.src: m \in MyEvidence } IN
   560        \* compare the number of the unique voters against the threshold
   561        /\ Cardinality(Voters) >= THRESHOLD2 \* line 34
   562        /\ evidence' = MyEvidence \union evidence
   563        /\ BroadcastPrecommit(p, round[p], NilProposal)
   564        /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   565        /\ UNCHANGED <<temporalVars, invariantVars>>
   566        /\ UNCHANGED
   567          <<round, (*step,*) decision, lockedValue, 
   568          lockedRound, validValue, validRound>>
   569        /\ UNCHANGED 
   570          <<msgsPropose, msgsPrevote, (*msgsPrecommit, *)
   571          (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   572        /\ action' = "UponQuorumOfPrevotesAny"
   573                       
   574  \* lines 36-46
   575  \* [PBTS-ALG-NEW-PREVOTE.0]
   576  \* @type: (PROCESS) => Bool;
   577  UponProposalInPrevoteOrCommitAndPrevote(p) ==
   578    \E v \in ValidValues, t \in Timestamps, vr \in RoundsOrNil:
   579      LET 
   580        r == round[p]
   581      IN LET
   582        \* @type: PROPOSAL;
   583        prop == Proposal(v,t,r)
   584      IN
   585      /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36
   586      /\ LET
   587          \* @type: PROPMESSAGE; 
   588          msg ==
   589          [
   590            type       |-> "PROPOSAL", 
   591            src        |-> Proposer[r],
   592            round      |-> r, 
   593            proposal   |-> prop, 
   594            validRound |-> vr
   595          ] 
   596         IN 
   597          \* Changed from 001: no need to re-check timeliness
   598          /\ msg \in msgsPropose[r] \* line 36
   599          /\ LET PV == { m \in msgsPrevote[r]: m.id = Id(prop) } IN
   600            /\ Cardinality(PV) >= THRESHOLD2 \* line 36
   601            /\ evidence' = PV \union {msg} \union evidence
   602      /\  IF step[p] = "PREVOTE"
   603          THEN \* lines 38-41:
   604            /\ lockedValue' = [lockedValue EXCEPT ![p] = v]
   605            /\ lockedRound' = [lockedRound EXCEPT ![p] = r]
   606            /\ BroadcastPrecommit(p, r, Id(prop))
   607            /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   608          ELSE
   609            UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>>
   610        \* lines 42-43
   611      /\ validValue' = [validValue EXCEPT ![p] = prop]
   612      /\ validRound' = [validRound EXCEPT ![p] = r]
   613      /\ UNCHANGED <<temporalVars, invariantVars>>
   614      /\ UNCHANGED
   615        <<round, (*step,*) decision(*, lockedValue, 
   616        lockedRound, validValue, validRound*)>>
   617      /\ UNCHANGED 
   618        <<msgsPropose, msgsPrevote, (*msgsPrecommit, *)
   619        (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   620      /\ action' = "UponProposalInPrevoteOrCommitAndPrevote"
   621  
   622  \* lines 47-48 + 65-67 (onTimeoutPrecommit)
   623  \* @type: (PROCESS) => Bool;
   624  UponQuorumOfPrecommitsAny(p) ==
   625    /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]:
   626        \* find the unique committers in the evidence
   627        LET Committers == { m.src: m \in MyEvidence } IN
   628        \* compare the number of the unique committers against the threshold
   629        /\ Cardinality(Committers) >= THRESHOLD2 \* line 47
   630        /\ evidence' = MyEvidence \union evidence
   631        /\ round[p] + 1 \in Rounds
   632        /\ StartRound(p, round[p] + 1)
   633        /\ UNCHANGED temporalVars
   634        /\ UNCHANGED
   635          <<(*beginRound,*) endConsensus, (*lastBeginRound,*)
   636          proposalTime, proposalReceivedTime>>
   637        /\ UNCHANGED
   638          <<(*round, step,*) decision, lockedValue, 
   639          lockedRound, validValue, validRound>>
   640        /\ UNCHANGED 
   641          <<msgsPropose, msgsPrevote, msgsPrecommit,
   642          (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   643        /\ action' = "UponQuorumOfPrecommitsAny"
   644                       
   645  \* lines 49-54        
   646  \* [PBTS-ALG-DECIDE.0]
   647  \* @type: (PROCESS) => Bool;
   648  UponProposalInPrecommitNoDecision(p) ==
   649    /\ decision[p] = NilDecision \* line 49
   650    /\ \E v \in ValidValues, t \in Timestamps (* line 50*) , r \in Rounds, pr \in Rounds, vr \in RoundsOrNil:
   651      LET
   652        \* @type: PROPOSAL;
   653        prop == Proposal(v,t,pr)
   654      IN
   655      /\ LET
   656          \* @type: PROPMESSAGE; 
   657          msg == 
   658          [
   659            type       |-> "PROPOSAL", 
   660            src        |-> Proposer[r],
   661            round      |-> r, 
   662            proposal   |-> prop, 
   663            validRound |-> vr
   664          ] 
   665         IN 
   666       /\ msg \in msgsPropose[r] \* line 49
   667       /\ inspectedProposal[r,p] /= NilTimestamp \* Keep?
   668       /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(prop) } IN
   669         /\ Cardinality(PV) >= THRESHOLD2 \* line 49
   670         /\ evidence' = PV \union {msg} \union evidence
   671         /\ decision' = [decision EXCEPT ![p] = Decision(prop, r)] \* update the decision, line 51
   672      \* The original algorithm does not have 'DECIDED', but it increments the height.
   673      \* We introduced 'DECIDED' here to prevent the process from changing its decision.
   674         /\ endConsensus' = [endConsensus EXCEPT ![p] = localClock[p]]
   675         /\ step' = [step EXCEPT ![p] = "DECIDED"]
   676         /\ UNCHANGED temporalVars
   677         /\ UNCHANGED
   678           <<round, (*step, decision,*) lockedValue, 
   679           lockedRound, validValue, validRound>>
   680         /\ UNCHANGED 
   681           <<msgsPropose, msgsPrevote, msgsPrecommit,
   682           (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   683         /\ UNCHANGED
   684           <<beginRound, (*endConsensus,*) lastBeginRound,
   685           proposalTime, proposalReceivedTime>>
   686         /\ action' = "UponProposalInPrecommitNoDecision"
   687                                                            
   688  \* the actions below are not essential for safety, but added for completeness
   689  
   690  \* lines 20-21 + 57-60
   691  \* @type: (PROCESS) => Bool;
   692  OnTimeoutPropose(p) ==
   693    /\ step[p] = "PROPOSE"
   694    /\ p /= Proposer[round[p]]
   695    /\ BroadcastPrevote(p, round[p], NilProposal)
   696    /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   697    /\ UNCHANGED <<temporalVars, invariantVars>>
   698    /\ UNCHANGED
   699      <<round, (*step,*) decision, lockedValue, 
   700      lockedRound, validValue, validRound>>
   701    /\ UNCHANGED 
   702      <<msgsPropose, (*msgsPrevote,*) msgsPrecommit,
   703      evidence, receivedTimelyProposal, inspectedProposal>>
   704    /\ action' = "OnTimeoutPropose"
   705  
   706  \* lines 44-46
   707  \* @type: (PROCESS) => Bool;
   708  OnQuorumOfNilPrevotes(p) ==
   709    /\ step[p] = "PREVOTE"
   710    /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilProposal) } IN
   711      /\ Cardinality(PV) >= THRESHOLD2 \* line 36
   712      /\ evidence' = PV \union evidence
   713      /\ BroadcastPrecommit(p, round[p], Id(NilProposal))
   714      /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   715      /\ UNCHANGED <<temporalVars, invariantVars>>
   716      /\ UNCHANGED
   717        <<round, (*step,*) decision, lockedValue, 
   718        lockedRound, validValue, validRound>>
   719      /\ UNCHANGED 
   720        <<msgsPropose, msgsPrevote, (*msgsPrecommit,*)
   721        (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   722      /\ action' = "OnQuorumOfNilPrevotes"
   723  
   724  \* lines 55-56
   725  \* @type: (PROCESS) => Bool;
   726  OnRoundCatchup(p) ==
   727    \E r \in {rr \in Rounds: rr > round[p]}:
   728      LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN
   729      \E MyEvidence \in SUBSET RoundMsgs:
   730          LET Faster == { m.src: m \in MyEvidence } IN
   731          /\ Cardinality(Faster) >= THRESHOLD1
   732          /\ evidence' = MyEvidence \union evidence
   733          /\ StartRound(p, r)
   734          /\ UNCHANGED temporalVars
   735          /\ UNCHANGED
   736            <<(*beginRound,*) endConsensus, (*lastBeginRound,*)
   737            proposalTime, proposalReceivedTime>>
   738          /\ UNCHANGED
   739            <<(*round, step,*) decision, lockedValue, 
   740            lockedRound, validValue, validRound>>
   741          /\ UNCHANGED 
   742            <<msgsPropose, msgsPrevote, msgsPrecommit,
   743            (*evidence,*) receivedTimelyProposal, inspectedProposal>>
   744          /\ action' = "OnRoundCatchup"
   745  
   746  
   747  (********************* PROTOCOL TRANSITIONS ******************************)
   748  \* advance the global clock
   749  \* @type: Bool;
   750  AdvanceRealTime == 
   751      /\ ValidTime(realTime)
   752      /\ \E t \in Timestamps:
   753        /\ t > realTime
   754        /\ realTime' = t
   755        /\ localClock' = [p \in Corr |-> localClock[p] + (t - realTime)]  
   756      /\ UNCHANGED <<coreVars, bookkeepingVars, invariantVars>>
   757      /\ action' = "AdvanceRealTime"
   758      
   759  \* advance the local clock of node p to some larger time t, not necessarily by 1
   760  \* #type: (PROCESS) => Bool;
   761  \* AdvanceLocalClock(p) ==
   762  \*     /\ ValidTime(localClock[p])
   763  \*     /\ \E t \in Timestamps:
   764  \*       /\ t > localClock[p] 
   765  \*       /\ localClock' = [localClock EXCEPT ![p] = t]
   766  \*     /\ UNCHANGED <<coreVars, bookkeepingVars, invariantVars>>
   767  \*     /\ UNCHANGED realTime
   768  \*     /\ action' = "AdvanceLocalClock"
   769  
   770  \* process timely messages
   771  \* @type: (PROCESS) => Bool;
   772  MessageProcessing(p) ==
   773      \* start round
   774      \/ InsertProposal(p)
   775      \* reception step
   776      \/ ReceiveProposal(p)
   777      \* processing step
   778      \/ UponProposalInPropose(p)
   779      \/ UponProposalInProposeAndPrevote(p)
   780      \/ UponQuorumOfPrevotesAny(p)
   781      \/ UponProposalInPrevoteOrCommitAndPrevote(p)
   782      \/ UponQuorumOfPrecommitsAny(p)
   783      \/ UponProposalInPrecommitNoDecision(p)
   784      \* the actions below are not essential for safety, but added for completeness
   785      \/ OnTimeoutPropose(p)
   786      \/ OnQuorumOfNilPrevotes(p)
   787      \/ OnRoundCatchup(p)
   788  
   789  (*
   790   * A system transition. In this specificatiom, the system may eventually deadlock,
   791   * e.g., when all processes decide. This is expected behavior, as we focus on safety.
   792   *)
   793  Next == 
   794    \/ AdvanceRealTime
   795    \/ /\ SynchronizedLocalClocks
   796       /\ \E p \in Corr: MessageProcessing(p)
   797  
   798  -----------------------------------------------------------------------------
   799  
   800  (*************************** INVARIANTS *************************************)
   801  
   802  \* [PBTS-INV-AGREEMENT.0]
   803  AgreementOnValue ==
   804      \A p, q \in Corr:
   805          /\ decision[p] /= NilDecision
   806          /\ decision[q] /= NilDecision
   807          => \E v \in ValidValues, t \in Timestamps, pr \in Rounds, r1 \in Rounds, r2 \in Rounds : 
   808              LET prop == Proposal(v,t,pr)
   809              IN
   810              /\ decision[p] = Decision(prop, r1)
   811              /\ decision[q] = Decision(prop, r2)
   812  
   813  \* [PBTS-CONSENSUS-TIME-VALID.0]
   814  ConsensusTimeValid ==
   815      \A p \in Corr: 
   816      \* if a process decides on v and t
   817        \E v \in ValidValues, t \in Timestamps, pr \in Rounds, dr \in Rounds : 
   818          decision[p] = Decision(Proposal(v,t,pr), dr)
   819          \* then 
   820          \* TODO: consider tighter bound where beginRound[pr] is replaced
   821          \* w/ MedianOfRound[pr]
   822          => (/\ beginRound[pr] - Precision - Delay <= t 
   823              /\ t <= endConsensus[p] + Precision)
   824  
   825  \* [PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0]
   826  ConsensusSafeValidCorrProp ==
   827      \A v \in ValidValues:
   828        \* and there exists a process that decided on v, t 
   829        /\ \E p \in Corr, t \in Timestamps, pr \in Rounds, dr \in Rounds : 
   830          \* if the proposer in the round is correct
   831          (/\ Proposer[pr] \in Corr
   832          /\ decision[p] = Decision(Proposal(v,t,pr), dr))
   833            \* then t is between the minimal and maximal initial local time
   834            => /\ beginRound[pr] <= t 
   835               /\ t <= lastBeginRound[pr]
   836  
   837  \* [PBTS-CONSENSUS-REALTIME-VALID-CORR.0]
   838  ConsensusRealTimeValidCorr ==
   839    \A r \in Rounds :
   840      \E p \in Corr, v \in ValidValues, t \in Timestamps, pr \in Rounds: 
   841       (/\ decision[p] = Decision(Proposal(v,t,pr), r) 
   842        /\ proposalTime[r] /= NilTimestamp)
   843          => (/\ proposalTime[r] - Precision <= t
   844              /\ t <= proposalTime[r] + Precision)
   845  
   846  \* [PBTS-CONSENSUS-REALTIME-VALID.0]
   847  ConsensusRealTimeValid ==
   848      \A t \in Timestamps, r \in Rounds :
   849         (\E p \in Corr, v \in ValidValues, pr \in Rounds : 
   850          decision[p] = Decision(Proposal(v,t,pr), r)) 
   851          => /\ proposalReceivedTime[r] - Precision < t
   852             /\ t < proposalReceivedTime[r] + Precision + Delay
   853  
   854  DecideAfterMin == TRUE
   855    \* if decide => time > min
   856  
   857  \* [PBTS-MSG-FAIR.0]
   858  BoundedDelay ==
   859      \A r \in Rounds : 
   860          (/\ proposalTime[r] /= NilTimestamp
   861           /\ proposalTime[r] + Delay < realTime)
   862              => \A p \in Corr: inspectedProposal[r,p] /= NilTimestamp
   863  
   864  \* [PBTS-CONSENSUS-TIME-LIVE.0]
   865  ConsensusTimeLive ==
   866      \A r \in Rounds, p \in Corr : 
   867         (/\ proposalTime[r] /= NilTimestamp
   868          /\ proposalTime[r] + Delay < realTime 
   869          /\ Proposer[r] \in Corr
   870          /\ round[p] <= r)
   871              => \E msg \in RoundProposals(r) : msg \in receivedTimelyProposal[p]
   872  
   873  \* a conjunction of all invariants
   874  Inv ==
   875      /\ AgreementOnValue 
   876      /\ ConsensusTimeValid
   877      /\ ConsensusSafeValidCorrProp
   878      \* /\ ConsensusRealTimeValid
   879      \* /\ ConsensusRealTimeValidCorr
   880      \* /\ BoundedDelay
   881  
   882  \* Liveness ==
   883  \*     ConsensusTimeLive    
   884  
   885  =============================================================================