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