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