github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/spec/light-client/accountability/TendermintAcc_004_draft.tla (about)

     1  -------------------- MODULE TendermintAcc_004_draft ---------------------------
     2  (*
     3   A TLA+ specification of a simplified Tendermint consensus, tuned for
     4   fork accountability. The simplifications are as follows:
     5  
     6   - the protocol runs for one height, that is, it is one-shot consensus
     7  
     8   - this specification focuses on safety, so timeouts are modelled
     9     with non-determinism
    10  
    11   - the proposer function is non-determinstic, no fairness is assumed
    12  
    13   - the messages by the faulty processes are injected right in the initial states
    14  
    15   - every process has the voting power of 1
    16  
    17   - hashes are modelled as identity
    18  
    19   Having the above assumptions in mind, the specification follows the pseudo-code
    20   of the Tendermint paper: https://arxiv.org/abs/1807.04938
    21  
    22   Byzantine processes can demonstrate arbitrary behavior, including
    23   no communication. We show that if agreement is violated, then the Byzantine
    24   processes demonstrate one of the two behaviours:
    25  
    26     - Equivocation: a Byzantine process may send two different values
    27       in the same round.
    28  
    29     - Amnesia: a Byzantine process may lock a value without unlocking
    30       the previous value that it has locked in the past.
    31  
    32   * Version 4. Remove defective processes, fix bugs, collect global evidence.
    33   * Version 3. Modular and parameterized definitions.
    34   * Version 2. Bugfixes in the spec and an inductive invariant.
    35   * Version 1. A preliminary specification.
    36  
    37   Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020.
    38   *)
    39  
    40  EXTENDS Integers, FiniteSets, typedefs
    41  
    42  (********************* PROTOCOL PARAMETERS **********************************)
    43  CONSTANTS
    44    \* @type: Set(PROCESS);
    45      Corr,          \* the set of correct processes 
    46    \* @type: Set(PROCESS);
    47      Faulty,        \* the set of Byzantine processes, may be empty
    48    \* @type: Int;  
    49      N,             \* the total number of processes: correct, defective, and Byzantine
    50    \* @type: Int;  
    51      T,             \* an upper bound on the number of Byzantine processes
    52    \* @type: Set(VALUE);  
    53      ValidValues,   \* the set of valid values, proposed both by correct and faulty
    54    \* @type: Set(VALUE);  
    55      InvalidValues, \* the set of invalid values, never proposed by the correct ones
    56    \* @type: ROUND;    
    57      MaxRound,      \* the maximal round number
    58    \* @type: ROUND -> PROCESS;
    59      Proposer       \* the proposer function from Rounds to AllProcs
    60  
    61  ASSUME(N = Cardinality(Corr \union Faulty))
    62  
    63  (*************************** DEFINITIONS ************************************)
    64  AllProcs == Corr \union Faulty      \* the set of all processes
    65  \* @type: Set(ROUND);
    66  Rounds == 0..MaxRound               \* the set of potential rounds
    67  \* @type: ROUND;
    68  NilRound == -1   \* a special value to denote a nil round, outside of Rounds
    69  RoundsOrNil == Rounds \union {NilRound}
    70  Values == ValidValues \union InvalidValues \* the set of all values
    71  \* @type: VALUE;
    72  NilValue == "None"  \* a special value for a nil round, outside of Values
    73  ValuesOrNil == Values \union {NilValue}
    74  
    75  \* a value hash is modeled as identity
    76  \* @type: (t) => t;
    77  Id(v) == v
    78  
    79  \* The validity predicate
    80  IsValid(v) == v \in ValidValues
    81  
    82  \* the two thresholds that are used in the algorithm
    83  THRESHOLD1 == T + 1     \* at least one process is not faulty
    84  THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T
    85  
    86  (********************* TYPE ANNOTATIONS FOR APALACHE **************************)
    87  
    88  \* An empty set of messages
    89  \* @type: Set(MESSAGE);
    90  EmptyMsgSet == {}
    91  
    92  (********************* PROTOCOL STATE VARIABLES ******************************)
    93  VARIABLES
    94    \* @type: PROCESS -> ROUND;
    95    round,    \* a process round number: Corr -> Rounds
    96    \* @type: PROCESS -> STEP;
    97    step,     \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }
    98    \* @type: PROCESS -> VALUE;
    99    decision, \* process decision: Corr -> ValuesOrNil
   100    \* @type: PROCESS -> VALUE;
   101    lockedValue,  \* a locked value: Corr -> ValuesOrNil
   102    \* @type: PROCESS -> ROUND;
   103    lockedRound,  \* a locked round: Corr -> RoundsOrNil
   104    \* @type: PROCESS -> VALUE;
   105    validValue,   \* a valid value: Corr -> ValuesOrNil
   106    \* @type: PROCESS -> ROUND;
   107    validRound    \* a valid round: Corr -> RoundsOrNil
   108  
   109  \* book-keeping variables
   110  VARIABLES
   111    \* @type: ROUND -> Set(PROPMESSAGE);
   112    msgsPropose,   \* PROPOSE messages broadcast in the system, Rounds -> Messages
   113    \* @type: ROUND -> Set(PREMESSAGE);
   114    msgsPrevote,   \* PREVOTE messages broadcast in the system, Rounds -> Messages
   115    \* @type: ROUND -> Set(PREMESSAGE);
   116    msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages
   117    \* @type: Set(MESSAGE);
   118    evidence, \* the messages that were used by the correct processes to make transitions
   119    \* @type: ACTION;
   120    action        \* we use this variable to see which action was taken
   121  
   122  (* to see a type invariant, check TendermintAccInv3 *)  
   123   
   124  \* a handy definition used in UNCHANGED
   125  vars == <<round, step, decision, lockedValue, lockedRound,
   126            validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit>>
   127  
   128  (********************* PROTOCOL INITIALIZATION ******************************)
   129  \* @type: (ROUND) => Set(PROPMESSAGE);
   130  FaultyProposals(r) ==
   131    [
   132      type      : {"PROPOSAL"}, 
   133      src       : Faulty,
   134      round     : {r}, 
   135      proposal  : Values, 
   136      validRound: RoundsOrNil
   137    ]
   138  
   139  \* @type: Set(PROPMESSAGE);
   140  AllFaultyProposals ==
   141    [
   142      type      : {"PROPOSAL"}, 
   143      src       : Faulty,
   144      round     : Rounds, 
   145      proposal  : Values, 
   146      validRound: RoundsOrNil
   147    ]
   148  
   149  \* @type: (ROUND) => Set(PREMESSAGE);
   150  FaultyPrevotes(r) ==
   151    [
   152      type : {"PREVOTE"}, 
   153      src  : Faulty, 
   154      round: {r}, 
   155      id   : Values
   156    ]
   157  
   158  \* @type: Set(PREMESSAGE);
   159  AllFaultyPrevotes ==    
   160    [
   161      type : {"PREVOTE"}, 
   162      src  : Faulty, 
   163      round: Rounds, 
   164      id   : Values
   165    ]
   166  
   167  \* @type: (ROUND) => Set(PREMESSAGE);
   168  FaultyPrecommits(r) ==
   169    [
   170      type : {"PRECOMMIT"}, 
   171      src  : Faulty, 
   172      round: {r}, 
   173      id   : Values
   174    ]
   175  
   176  \* @type: Set(PREMESSAGE);
   177  AllFaultyPrecommits ==
   178    [
   179      type : {"PRECOMMIT"}, 
   180      src  : Faulty, 
   181      round: Rounds, 
   182      id   : Values
   183    ]
   184  
   185  \* @type: (ROUND -> Set(MESSAGE)) => Bool;
   186  BenignRoundsInMessages(msgfun) ==
   187    \* the message function never contains a message for a wrong round
   188    \A r \in Rounds:
   189      \A m \in msgfun[r]:
   190        r = m.round
   191  
   192  \* The initial states of the protocol. Some faults can be in the system already.
   193  Init ==
   194      /\ round = [p \in Corr |-> 0]
   195      /\ step = [p \in Corr |-> "PROPOSE"]
   196      /\ decision = [p \in Corr |-> NilValue]
   197      /\ lockedValue = [p \in Corr |-> NilValue]
   198      /\ lockedRound = [p \in Corr |-> NilRound]
   199      /\ validValue = [p \in Corr |-> NilValue]
   200      /\ validRound = [p \in Corr |-> NilRound]
   201      /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals]
   202      /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes]
   203      /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits]
   204      /\ BenignRoundsInMessages(msgsPropose)
   205      /\ BenignRoundsInMessages(msgsPrevote)
   206      /\ BenignRoundsInMessages(msgsPrecommit)
   207      /\ evidence = EmptyMsgSet
   208      /\ action = "Init"
   209  
   210  (************************ MESSAGE PASSING ********************************)
   211  \* @type: (PROCESS, ROUND, VALUE, ROUND) => Bool;
   212  BroadcastProposal(pSrc, pRound, pProposal, pValidRound) ==
   213    LET
   214      \* @type: PROPMESSAGE;
   215      newMsg ==
   216      [
   217        type       |-> "PROPOSAL", 
   218        src        |-> pSrc, 
   219        round      |-> pRound,
   220        proposal   |-> pProposal, 
   221        validRound |-> pValidRound
   222      ]
   223    IN
   224    msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}]
   225  
   226  \* @type: (PROCESS, ROUND, VALUE) => Bool;
   227  BroadcastPrevote(pSrc, pRound, pId) ==
   228    LET 
   229      \* @type: PREMESSAGE;
   230      newMsg == 
   231      [
   232        type  |-> "PREVOTE",
   233        src   |-> pSrc, 
   234        round |-> pRound, 
   235        id    |-> pId
   236      ]
   237    IN
   238    msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}]
   239  
   240  \* @type: (PROCESS, ROUND, VALUE) => Bool;
   241  BroadcastPrecommit(pSrc, pRound, pId) ==
   242    LET
   243      \* @type: PREMESSAGE; 
   244      newMsg == 
   245      [
   246        type  |-> "PRECOMMIT",
   247        src   |-> pSrc, 
   248        round |-> pRound, 
   249        id    |-> pId
   250      ]
   251    IN
   252    msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}]
   253  
   254  
   255  (********************* PROTOCOL TRANSITIONS ******************************)
   256  \* lines 12-13
   257  StartRound(p, r) ==
   258     /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus
   259     /\ round' = [round EXCEPT ![p] = r]
   260     /\ step' = [step EXCEPT ![p] = "PROPOSE"] 
   261  
   262  \* lines 14-19, a proposal may be sent later
   263  \* @type: (PROCESS) => Bool;
   264  InsertProposal(p) == 
   265    LET r == round[p] IN
   266    /\ p = Proposer[r]
   267    /\ step[p] = "PROPOSE"
   268      \* if the proposer is sending a proposal, then there are no other proposals
   269      \* by the correct processes for the same round
   270    /\ \A m \in msgsPropose[r]: m.src /= p
   271    /\ \E v \in ValidValues: 
   272        LET 
   273          \* @type: VALUE;
   274          proposal == 
   275            IF validValue[p] /= NilValue 
   276            THEN validValue[p] 
   277            ELSE v 
   278        IN BroadcastProposal(p, round[p], proposal, validRound[p])
   279    /\ UNCHANGED <<evidence, round, decision, lockedValue, lockedRound,
   280                  validValue, step, validRound, msgsPrevote, msgsPrecommit>>
   281    /\ action' = "InsertProposal"
   282  
   283  \* lines 22-27
   284  UponProposalInPropose(p) ==
   285    \E v \in Values:
   286      /\ step[p] = "PROPOSE" (* line 22 *)
   287      /\ LET
   288          \* @type: PROPMESSAGE; 
   289          msg ==
   290          [
   291            type       |-> "PROPOSAL", 
   292            src        |-> Proposer[round[p]],
   293            round      |-> round[p], 
   294            proposal   |-> v, 
   295            validRound |-> NilRound
   296          ] 
   297         IN
   298        /\ msg \in msgsPropose[round[p]] \* line 22
   299        /\ evidence' = {msg} \union evidence
   300      /\ LET mid == (* line 23 *)
   301           IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v)
   302           THEN Id(v)
   303           ELSE NilValue
   304         IN
   305         BroadcastPrevote(p, round[p], mid) \* lines 24-26
   306      /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   307      /\ UNCHANGED <<round, decision, lockedValue, lockedRound,
   308                     validValue, validRound, msgsPropose, msgsPrecommit>>
   309      /\ action' = "UponProposalInPropose"
   310  
   311  \* lines 28-33        
   312  UponProposalInProposeAndPrevote(p) ==
   313    \E v \in Values, vr \in Rounds:
   314      /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part
   315      /\ LET
   316          \* @type: PROPMESSAGE; 
   317          msg ==
   318          [
   319            type       |-> "PROPOSAL", 
   320            src        |-> Proposer[round[p]],
   321            round      |-> round[p], 
   322            proposal   |-> v, 
   323            validRound |-> vr
   324          ]
   325         IN
   326         /\ msg \in msgsPropose[round[p]] \* line 28
   327         /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(v) } IN
   328            /\ Cardinality(PV) >= THRESHOLD2 \* line 28
   329            /\ evidence' = PV \union {msg} \union evidence
   330      /\ LET mid == (* line 29 *)
   331           IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v)
   332           THEN Id(v)
   333           ELSE NilValue
   334         IN
   335         BroadcastPrevote(p, round[p], mid) \* lines 24-26
   336      /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   337      /\ UNCHANGED <<round, decision, lockedValue, lockedRound,
   338                     validValue, validRound, msgsPropose, msgsPrecommit>>
   339      /\ action' = "UponProposalInProposeAndPrevote"
   340                       
   341   \* lines 34-35 + lines 61-64 (onTimeoutPrevote)
   342  UponQuorumOfPrevotesAny(p) ==
   343    /\ step[p] = "PREVOTE" \* line 34 and 61
   344    /\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]:
   345        \* find the unique voters in the evidence
   346        LET Voters == { m.src: m \in MyEvidence } IN
   347        \* compare the number of the unique voters against the threshold
   348        /\ Cardinality(Voters) >= THRESHOLD2 \* line 34
   349        /\ evidence' = MyEvidence \union evidence
   350        /\ BroadcastPrecommit(p, round[p], NilValue)
   351        /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   352        /\ UNCHANGED <<round, decision, lockedValue, lockedRound,
   353                      validValue, validRound, msgsPropose, msgsPrevote>>
   354        /\ action' = "UponQuorumOfPrevotesAny"
   355                       
   356  \* lines 36-46
   357  UponProposalInPrevoteOrCommitAndPrevote(p) ==
   358    \E v \in ValidValues, vr \in RoundsOrNil:
   359      /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36
   360      /\ LET
   361          \* @type: PROPMESSAGE; 
   362          msg ==
   363          [
   364            type       |-> "PROPOSAL", 
   365            src        |-> Proposer[round[p]],
   366            round      |-> round[p], 
   367            proposal   |-> v, 
   368            validRound |-> vr
   369          ] 
   370         IN
   371          /\ msg \in msgsPropose[round[p]] \* line 36
   372          /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(v) } IN
   373            /\ Cardinality(PV) >= THRESHOLD2 \* line 36
   374            /\ evidence' = PV \union {msg} \union evidence
   375      /\  IF step[p] = "PREVOTE"
   376          THEN \* lines 38-41:
   377            /\ lockedValue' = [lockedValue EXCEPT ![p] = v]
   378            /\ lockedRound' = [lockedRound EXCEPT ![p] = round[p]]
   379            /\ BroadcastPrecommit(p, round[p], Id(v))
   380            /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   381          ELSE
   382            UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>>
   383        \* lines 42-43
   384      /\ validValue' = [validValue EXCEPT ![p] = v]
   385      /\ validRound' = [validRound EXCEPT ![p] = round[p]]
   386      /\ UNCHANGED <<round, decision, msgsPropose, msgsPrevote>>
   387      /\ action' = "UponProposalInPrevoteOrCommitAndPrevote"
   388  
   389  \* lines 47-48 + 65-67 (onTimeoutPrecommit)
   390  UponQuorumOfPrecommitsAny(p) ==
   391    /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]:
   392        \* find the unique committers in the evidence
   393        LET Committers == { m.src: m \in MyEvidence } IN
   394        \* compare the number of the unique committers against the threshold
   395        /\ Cardinality(Committers) >= THRESHOLD2 \* line 47
   396        /\ evidence' = MyEvidence \union evidence
   397        /\ round[p] + 1 \in Rounds
   398        /\ StartRound(p, round[p] + 1)   
   399        /\ UNCHANGED <<decision, lockedValue, lockedRound, validValue,
   400                      validRound, msgsPropose, msgsPrevote, msgsPrecommit>>
   401        /\ action' = "UponQuorumOfPrecommitsAny"
   402                       
   403  \* lines 49-54        
   404  UponProposalInPrecommitNoDecision(p) ==
   405    /\ decision[p] = NilValue \* line 49
   406    /\ \E v \in ValidValues (* line 50*) , r \in Rounds, vr \in RoundsOrNil:
   407      /\ LET
   408          \* @type: PROPMESSAGE; 
   409          msg == 
   410          [
   411            type       |-> "PROPOSAL", 
   412            src        |-> Proposer[r],
   413            round      |-> r, 
   414            proposal   |-> v, 
   415            validRound |-> vr
   416          ] 
   417         IN
   418       /\ msg \in msgsPropose[r] \* line 49
   419       /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(v) } IN
   420         /\ Cardinality(PV) >= THRESHOLD2 \* line 49
   421         /\ evidence' = PV \union {msg} \union evidence
   422         /\ decision' = [decision EXCEPT ![p] = v] \* update the decision, line 51
   423      \* The original algorithm does not have 'DECIDED', but it increments the height.
   424      \* We introduced 'DECIDED' here to prevent the process from changing its decision.
   425         /\ step' = [step EXCEPT ![p] = "DECIDED"]
   426         /\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
   427                       validRound, msgsPropose, msgsPrevote, msgsPrecommit>>
   428         /\ action' = "UponProposalInPrecommitNoDecision"
   429                                                            
   430  \* the actions below are not essential for safety, but added for completeness
   431  
   432  \* lines 20-21 + 57-60
   433  OnTimeoutPropose(p) ==
   434    /\ step[p] = "PROPOSE"
   435    /\ p /= Proposer[round[p]]
   436    /\ BroadcastPrevote(p, round[p], NilValue)
   437    /\ step' = [step EXCEPT ![p] = "PREVOTE"]
   438    /\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
   439                  validRound, decision, evidence, msgsPropose, msgsPrecommit>>
   440    /\ action' = "OnTimeoutPropose"
   441  
   442  \* lines 44-46
   443  OnQuorumOfNilPrevotes(p) ==
   444    /\ step[p] = "PREVOTE"
   445    /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilValue) } IN
   446      /\ Cardinality(PV) >= THRESHOLD2 \* line 36
   447      /\ evidence' = PV \union evidence
   448      /\ BroadcastPrecommit(p, round[p], Id(NilValue))
   449      /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
   450      /\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
   451                    validRound, decision, msgsPropose, msgsPrevote>>
   452      /\ action' = "OnQuorumOfNilPrevotes"
   453  
   454  \* lines 55-56
   455  OnRoundCatchup(p) ==
   456    \E r \in {rr \in Rounds: rr > round[p]}:
   457      LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN
   458      \E MyEvidence \in SUBSET RoundMsgs:
   459          LET Faster == { m.src: m \in MyEvidence } IN
   460          /\ Cardinality(Faster) >= THRESHOLD1
   461          /\ evidence' = MyEvidence \union evidence
   462          /\ StartRound(p, r)
   463          /\ UNCHANGED <<decision, lockedValue, lockedRound, validValue,
   464                        validRound, msgsPropose, msgsPrevote, msgsPrecommit>>
   465          /\ action' = "OnRoundCatchup"
   466  
   467  (*
   468   * A system transition. In this specificatiom, the system may eventually deadlock,
   469   * e.g., when all processes decide. This is expected behavior, as we focus on safety.
   470   *)
   471  Next ==
   472    \E p \in Corr:
   473      \/ InsertProposal(p)
   474      \/ UponProposalInPropose(p)
   475      \/ UponProposalInProposeAndPrevote(p)
   476      \/ UponQuorumOfPrevotesAny(p)
   477      \/ UponProposalInPrevoteOrCommitAndPrevote(p)
   478      \/ UponQuorumOfPrecommitsAny(p)
   479      \/ UponProposalInPrecommitNoDecision(p)
   480      \* the actions below are not essential for safety, but added for completeness
   481      \/ OnTimeoutPropose(p)
   482      \/ OnQuorumOfNilPrevotes(p)
   483      \/ OnRoundCatchup(p)
   484  
   485    
   486  (**************************** FORK SCENARIOS  ***************************)
   487  
   488  \* equivocation by a process p
   489  EquivocationBy(p) ==
   490     \E m1, m2 \in evidence:
   491      /\ m1 /= m2
   492      /\ m1.src = p
   493      /\ m2.src = p
   494      /\ m1.round = m2.round
   495      /\ m1.type = m2.type
   496  
   497  \* amnesic behavior by a process p
   498  AmnesiaBy(p) ==
   499      \E r1, r2 \in Rounds:
   500        /\ r1 < r2
   501        /\ \E v1, v2 \in ValidValues:
   502          /\ v1 /= v2
   503          /\ [
   504              type  |-> "PRECOMMIT", 
   505              src   |-> p,
   506              round |-> r1, 
   507              id    |-> Id(v1)
   508             ] \in evidence
   509          /\ [
   510              type  |-> "PREVOTE", 
   511              src   |-> p,
   512              round |-> r2, 
   513              id    |-> Id(v2)
   514             ] \in evidence
   515          /\ \A r \in { rnd \in Rounds: r1 <= rnd /\ rnd < r2 }:
   516              LET prevotes ==
   517                  { m \in evidence:
   518                      m.type = "PREVOTE" /\ m.round = r /\ m.id = Id(v2) }
   519              IN
   520              Cardinality(prevotes) < THRESHOLD2
   521  
   522  (******************************** PROPERTIES  ***************************************)
   523  
   524  \* the safety property -- agreement
   525  Agreement ==
   526    \A p, q \in Corr:
   527      \/ decision[p] = NilValue
   528      \/ decision[q] = NilValue
   529      \/ decision[p] = decision[q]
   530  
   531  \* the protocol validity
   532  Validity ==
   533      \A p \in Corr: decision[p] \in ValidValues \union {NilValue}
   534  
   535  (*
   536    The protocol safety. Two cases are possible:
   537       1. There is no fork, that is, Agreement holds true.
   538       2. A subset of faulty processes demonstrates equivocation or amnesia.
   539   *)
   540  Accountability ==
   541      \/ Agreement
   542      \/ \E Detectable \in SUBSET Faulty:
   543          /\ Cardinality(Detectable) >= THRESHOLD1
   544          /\ \A p \in Detectable:
   545              EquivocationBy(p) \/ AmnesiaBy(p)
   546  
   547  (****************** FALSE INVARIANTS TO PRODUCE EXAMPLES ***********************)
   548   
   549  \* This property is violated. You can check it to see how amnesic behavior
   550  \* appears in the evidence variable.
   551  NoAmnesia ==
   552      \A p \in Faulty: ~AmnesiaBy(p)
   553  
   554  \* This property is violated. You can check it to see an example of equivocation.
   555  NoEquivocation ==
   556      \A p \in Faulty: ~EquivocationBy(p)
   557  
   558  \* This property is violated. You can check it to see an example of agreement.
   559  \* It is not exactly ~Agreement, as we do not want to see the states where
   560  \* decision[p] = NilValue
   561  NoAgreement ==
   562    \A p, q \in Corr:
   563      (p /= q /\ decision[p] /= NilValue /\ decision[q] /= NilValue)
   564          => decision[p] /= decision[q]
   565   
   566  \* Either agreement holds, or the faulty processes indeed demonstrate amnesia.
   567  \* This property is violated. A counterexample should demonstrate equivocation.
   568  AgreementOrAmnesia ==
   569      Agreement \/ (\A p \in Faulty: AmnesiaBy(p))
   570   
   571  \* We expect this property to be violated. It shows us a protocol run,
   572  \* where one faulty process demonstrates amnesia without equivocation.
   573  \* However, the absence of amnesia
   574  \* is a tough constraint for Apalache. It has not reported a counterexample
   575  \* for n=4,f=2, length <= 5.
   576  ShowMeAmnesiaWithoutEquivocation ==
   577      (~Agreement /\ \E p \in Faulty: ~EquivocationBy(p))
   578          => \A p \in Faulty: ~AmnesiaBy(p)
   579  
   580  \* This property is violated on n=4,f=2, length=4 in less than 10 min.
   581  \* Two faulty processes may demonstrate amnesia without equivocation.
   582  AmnesiaImpliesEquivocation ==
   583      (\E p \in Faulty: AmnesiaBy(p)) => (\E q \in Faulty: EquivocationBy(q))
   584  
   585  (*
   586    This property is violated. You can check it to see that all correct processes
   587    may reach MaxRound without making a decision. 
   588   *)
   589  NeverUndecidedInMaxRound ==
   590      LET AllInMax == \A p \in Corr: round[p] = MaxRound
   591          AllDecided == \A p \in Corr: decision[p] /= NilValue
   592      IN
   593      AllInMax => AllDecided
   594  
   595  =============================================================================    
   596