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