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

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