github.com/aakash4dev/cometbft@v0.38.2/spec/consensus/proposer-based-timestamp/tla/TendermintPBT_001_draft.tla (about) 1 -------------------- MODULE TendermintPBT_001_draft --------------------------- 2 (* 3 A TLA+ specification of a simplified Tendermint consensus algorithm, 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/aakash4dev/cometbft/blob/main/spec/light-client/accountability/TendermintAcc_004_draft.tla 7 8 * Version 1. A preliminary specification. 9 10 Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020. 11 Ilina Stoilkovska, Josef Widder, Informal Systems, 2021. 12 *) 13 14 EXTENDS Integers, FiniteSets 15 16 (********************* PROTOCOL PARAMETERS **********************************) 17 CONSTANTS 18 Corr, \* the set of correct processes 19 Faulty, \* the set of Byzantine processes, may be empty 20 N, \* the total number of processes: correct, defective, and Byzantine 21 T, \* an upper bound on the number of Byzantine processes 22 ValidValues, \* the set of valid values, proposed both by correct and faulty 23 InvalidValues, \* the set of invalid values, never proposed by the correct ones 24 MaxRound, \* the maximal round number 25 MaxTimestamp, \* the maximal value of the clock tick 26 Delay, \* message delay 27 Precision, \* clock precision: the maximal difference between two local clocks 28 Accuracy, \* clock accuracy: the maximal difference between a local clock and the real time 29 Proposer, \* the proposer function from 0..NRounds to 1..N 30 ClockDrift \* is there clock drift between the local clocks and the global clock 31 32 ASSUME(N = Cardinality(Corr \union Faulty)) 33 34 (*************************** DEFINITIONS ************************************) 35 AllProcs == Corr \union Faulty \* the set of all processes 36 Rounds == 0..MaxRound \* the set of potential rounds 37 Timestamps == 0..MaxTimestamp \* the set of clock ticks 38 NilRound == -1 \* a special value to denote a nil round, outside of Rounds 39 NilTimestamp == -1 \* a special value to denote a nil timestamp, outside of Ticks 40 RoundsOrNil == Rounds \union {NilRound} 41 Values == ValidValues \union InvalidValues \* the set of all values 42 NilValue == "None" \* a special value for a nil round, outside of Values 43 Proposals == Values \X Timestamps 44 NilProposal == <<NilValue, NilTimestamp>> 45 ValuesOrNil == Values \union {NilValue} 46 Decisions == Values \X Timestamps \X Rounds 47 NilDecision == <<NilValue, NilTimestamp, NilRound>> 48 49 50 \* a value hash is modeled as identity 51 Id(v) == v 52 53 \* The validity predicate 54 IsValid(v) == v \in ValidValues 55 56 \* the two thresholds that are used in the algorithm 57 THRESHOLD1 == T + 1 \* at least one process is not faulty 58 THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T 59 60 Min(S) == CHOOSE x \in S : \A y \in S : x <= y 61 62 Max(S) == CHOOSE x \in S : \A y \in S : y <= x 63 64 (********************* TYPE ANNOTATIONS FOR APALACHE **************************) 65 \* the operator for type annotations 66 a <: b == a 67 68 \* the type of message records 69 MT == [type |-> STRING, src |-> STRING, round |-> Int, 70 proposal |-> <<STRING, Int>>, validRound |-> Int, id |-> <<STRING, Int>>] 71 72 RP == <<STRING, MT>> 73 74 \* a type annotation for a message 75 AsMsg(m) == m <: MT 76 \* a type annotation for a set of messages 77 SetOfMsgs(S) == S <: {MT} 78 \* a type annotation for an empty set of messages 79 EmptyMsgSet == SetOfMsgs({}) 80 81 SetOfRcvProp(S) == S <: {RP} 82 EmptyRcvProp == SetOfRcvProp({}) 83 84 SetOfProc(S) == S <: {STRING} 85 EmptyProcSet == SetOfProc({}) 86 87 (********************* PROTOCOL STATE VARIABLES ******************************) 88 VARIABLES 89 round, \* a process round number: Corr -> Rounds 90 localClock, \* a process local clock: Corr -> Ticks 91 realTime, \* a reference Newtonian real time 92 step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" } 93 decision, \* process decision: Corr -> ValuesOrNil 94 lockedValue, \* a locked value: Corr -> ValuesOrNil 95 lockedRound, \* a locked round: Corr -> RoundsOrNil 96 validValue, \* a valid value: Corr -> ValuesOrNil 97 validRound \* a valid round: Corr -> RoundsOrNil 98 99 \* book-keeping variables 100 VARIABLES 101 msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages 102 msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages 103 msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages 104 receivedTimelyProposal, \* used to keep track when a process receives a timely PROPOSAL message, {<<Corr, Messages>>} 105 inspectedProposal, \* used to keep track when a process tries to receive a message, [Rounds -> <<Corr, Messages>>] 106 evidence, \* the messages that were used by the correct processes to make transitions 107 action, \* we use this variable to see which action was taken 108 beginConsensus, \* the minimum of the local clocks in the initial state, Int 109 endConsensus, \* the local time when a decision is made, [Corr -> Int] 110 lastBeginConsensus, \* the maximum of the local clocks in the initial state, Int 111 proposalTime, \* the real time when a proposer proposes in a round, [Rounds -> Int] 112 proposalReceivedTime \* the real time when a correct process first receives a proposal message in a round, [Rounds -> Int] 113 114 (* to see a type invariant, check TendermintAccInv3 *) 115 116 \* a handy definition used in UNCHANGED 117 vars == <<round, step, decision, lockedValue, lockedRound, 118 validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit, 119 localClock, realTime, receivedTimelyProposal, inspectedProposal, action, 120 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 121 122 (********************* PROTOCOL INITIALIZATION ******************************) 123 FaultyProposals(r) == 124 SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, 125 round: {r}, proposal: Proposals, validRound: RoundsOrNil]) 126 127 AllFaultyProposals == 128 SetOfMsgs([type: {"PROPOSAL"}, src: Faulty, 129 round: Rounds, proposal: Proposals, validRound: RoundsOrNil]) 130 131 FaultyPrevotes(r) == 132 SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: {r}, id: Proposals]) 133 134 AllFaultyPrevotes == 135 SetOfMsgs([type: {"PREVOTE"}, src: Faulty, round: Rounds, id: Proposals]) 136 137 FaultyPrecommits(r) == 138 SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: {r}, id: Proposals]) 139 140 AllFaultyPrecommits == 141 SetOfMsgs([type: {"PRECOMMIT"}, src: Faulty, round: Rounds, id: Proposals]) 142 143 AllProposals == 144 SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs, 145 round: Rounds, proposal: Proposals, validRound: RoundsOrNil]) 146 147 RoundProposals(r) == 148 SetOfMsgs([type: {"PROPOSAL"}, src: AllProcs, 149 round: {r}, proposal: Proposals, validRound: RoundsOrNil]) 150 151 BenignRoundsInMessages(msgfun) == 152 \* the message function never contains a message for a wrong round 153 \A r \in Rounds: 154 \A m \in msgfun[r]: 155 r = m.round 156 157 \* The initial states of the protocol. Some faults can be in the system already. 158 Init == 159 /\ round = [p \in Corr |-> 0] 160 /\ \/ /\ ~ClockDrift 161 /\ localClock \in [Corr -> 0..Accuracy] 162 \/ /\ ClockDrift 163 /\ localClock = [p \in Corr |-> 0] 164 /\ realTime = 0 165 /\ step = [p \in Corr |-> "PROPOSE"] 166 /\ decision = [p \in Corr |-> NilDecision] 167 /\ lockedValue = [p \in Corr |-> NilValue] 168 /\ lockedRound = [p \in Corr |-> NilRound] 169 /\ validValue = [p \in Corr |-> NilValue] 170 /\ validRound = [p \in Corr |-> NilRound] 171 /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals] 172 /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes] 173 /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits] 174 /\ receivedTimelyProposal = EmptyRcvProp 175 /\ inspectedProposal = [r \in Rounds |-> EmptyProcSet] 176 /\ BenignRoundsInMessages(msgsPropose) 177 /\ BenignRoundsInMessages(msgsPrevote) 178 /\ BenignRoundsInMessages(msgsPrecommit) 179 /\ evidence = EmptyMsgSet 180 /\ action' = "Init" 181 /\ beginConsensus = Min({localClock[p] : p \in Corr}) 182 /\ endConsensus = [p \in Corr |-> NilTimestamp] 183 /\ lastBeginConsensus = Max({localClock[p] : p \in Corr}) 184 /\ proposalTime = [r \in Rounds |-> NilTimestamp] 185 /\ proposalReceivedTime = [r \in Rounds |-> NilTimestamp] 186 187 (************************ MESSAGE PASSING ********************************) 188 BroadcastProposal(pSrc, pRound, pProposal, pValidRound) == 189 LET newMsg == 190 AsMsg([type |-> "PROPOSAL", src |-> pSrc, round |-> pRound, 191 proposal |-> pProposal, validRound |-> pValidRound]) 192 IN 193 msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}] 194 195 BroadcastPrevote(pSrc, pRound, pId) == 196 LET newMsg == AsMsg([type |-> "PREVOTE", 197 src |-> pSrc, round |-> pRound, id |-> pId]) 198 IN 199 msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}] 200 201 BroadcastPrecommit(pSrc, pRound, pId) == 202 LET newMsg == AsMsg([type |-> "PRECOMMIT", 203 src |-> pSrc, round |-> pRound, id |-> pId]) 204 IN 205 msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}] 206 207 208 (***************************** TIME **************************************) 209 210 \* [PBTS-CLOCK-PRECISION.0] 211 SynchronizedLocalClocks == 212 \A p \in Corr : \A q \in Corr : 213 p /= q => 214 \/ /\ localClock[p] >= localClock[q] 215 /\ localClock[p] - localClock[q] < Precision 216 \/ /\ localClock[p] < localClock[q] 217 /\ localClock[q] - localClock[p] < Precision 218 219 \* [PBTS-PROPOSE.0] 220 Proposal(v, t) == 221 <<v, t>> 222 223 \* [PBTS-DECISION-ROUND.0] 224 Decision(v, t, r) == 225 <<v, t, r>> 226 227 (**************** MESSAGE PROCESSING TRANSITIONS *************************) 228 \* lines 12-13 229 StartRound(p, r) == 230 /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus 231 /\ round' = [round EXCEPT ![p] = r] 232 /\ step' = [step EXCEPT ![p] = "PROPOSE"] 233 234 \* lines 14-19, a proposal may be sent later 235 InsertProposal(p) == 236 LET r == round[p] IN 237 /\ p = Proposer[r] 238 /\ step[p] = "PROPOSE" 239 \* if the proposer is sending a proposal, then there are no other proposals 240 \* by the correct processes for the same round 241 /\ \A m \in msgsPropose[r]: m.src /= p 242 /\ \E v \in ValidValues: 243 LET proposal == IF validValue[p] /= NilValue 244 THEN Proposal(validValue[p], localClock[p]) 245 ELSE Proposal(v, localClock[p]) IN 246 247 /\ BroadcastProposal(p, round[p], proposal, validRound[p]) 248 /\ proposalTime' = [proposalTime EXCEPT ![r] = realTime] 249 /\ UNCHANGED <<evidence, round, decision, lockedValue, lockedRound, 250 validValue, step, validRound, msgsPrevote, msgsPrecommit, 251 localClock, realTime, receivedTimelyProposal, inspectedProposal, 252 beginConsensus, endConsensus, lastBeginConsensus, proposalReceivedTime>> 253 /\ action' = "InsertProposal" 254 255 \* a new action used to filter messages that are not on time 256 \* [PBTS-RECEPTION-STEP.0] 257 ReceiveProposal(p) == 258 \E v \in Values, t \in Timestamps: 259 /\ LET r == round[p] IN 260 LET msg == 261 AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], 262 round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN 263 /\ msg \in msgsPropose[round[p]] 264 /\ p \notin inspectedProposal[r] 265 /\ <<p, msg>> \notin receivedTimelyProposal 266 /\ inspectedProposal' = [inspectedProposal EXCEPT ![r] = @ \union {p}] 267 /\ \/ /\ localClock[p] - Precision < t 268 /\ t < localClock[p] + Precision + Delay 269 /\ receivedTimelyProposal' = receivedTimelyProposal \union {<<p, msg>>} 270 /\ \/ /\ proposalReceivedTime[r] = NilTimestamp 271 /\ proposalReceivedTime' = [proposalReceivedTime EXCEPT ![r] = realTime] 272 \/ /\ proposalReceivedTime[r] /= NilTimestamp 273 /\ UNCHANGED proposalReceivedTime 274 \/ /\ \/ localClock[p] - Precision >= t 275 \/ t >= localClock[p] + Precision + Delay 276 /\ UNCHANGED <<receivedTimelyProposal, proposalReceivedTime>> 277 /\ UNCHANGED <<round, step, decision, lockedValue, lockedRound, 278 validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit, 279 localClock, realTime, beginConsensus, endConsensus, lastBeginConsensus, proposalTime>> 280 /\ action' = "ReceiveProposal" 281 282 \* lines 22-27 283 UponProposalInPropose(p) == 284 \E v \in Values, t \in Timestamps: 285 /\ step[p] = "PROPOSE" (* line 22 *) 286 /\ LET msg == 287 AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], 288 round |-> round[p], proposal |-> Proposal(v, t), validRound |-> NilRound]) IN 289 /\ <<p, msg>> \in receivedTimelyProposal \* updated line 22 290 /\ evidence' = {msg} \union evidence 291 /\ LET mid == (* line 23 *) 292 IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v) 293 THEN Id(Proposal(v, t)) 294 ELSE NilProposal 295 IN 296 BroadcastPrevote(p, round[p], mid) \* lines 24-26 297 /\ step' = [step EXCEPT ![p] = "PREVOTE"] 298 /\ UNCHANGED <<round, decision, lockedValue, lockedRound, 299 validValue, validRound, msgsPropose, msgsPrecommit, 300 localClock, realTime, receivedTimelyProposal, inspectedProposal, 301 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 302 /\ action' = "UponProposalInPropose" 303 304 \* lines 28-33 305 \* [PBTS-ALG-OLD-PREVOTE.0] 306 UponProposalInProposeAndPrevote(p) == 307 \E v \in Values, t1 \in Timestamps, t2 \in Timestamps, vr \in Rounds: 308 /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part 309 /\ LET msg == 310 AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], 311 round |-> round[p], proposal |-> Proposal(v, t1), validRound |-> vr]) 312 IN 313 /\ <<p, msg>> \in receivedTimelyProposal \* updated line 28 314 /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(Proposal(v, t2)) } IN 315 /\ Cardinality(PV) >= THRESHOLD2 \* line 28 316 /\ evidence' = PV \union {msg} \union evidence 317 /\ LET mid == (* line 29 *) 318 IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v) 319 THEN Id(Proposal(v, t1)) 320 ELSE NilProposal 321 IN 322 BroadcastPrevote(p, round[p], mid) \* lines 24-26 323 /\ step' = [step EXCEPT ![p] = "PREVOTE"] 324 /\ UNCHANGED <<round, decision, lockedValue, lockedRound, 325 validValue, validRound, msgsPropose, msgsPrecommit, 326 localClock, realTime, receivedTimelyProposal, inspectedProposal, 327 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 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 /\ evidence' = MyEvidence \union evidence 339 /\ BroadcastPrecommit(p, round[p], NilProposal) 340 /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] 341 /\ UNCHANGED <<round, decision, lockedValue, lockedRound, 342 validValue, validRound, msgsPropose, msgsPrevote, 343 localClock, realTime, receivedTimelyProposal, inspectedProposal, 344 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 345 /\ action' = "UponQuorumOfPrevotesAny" 346 347 \* lines 36-46 348 \* [PBTS-ALG-NEW-PREVOTE.0] 349 UponProposalInPrevoteOrCommitAndPrevote(p) == 350 \E v \in ValidValues, t \in Timestamps, vr \in RoundsOrNil: 351 /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36 352 /\ LET msg == 353 AsMsg([type |-> "PROPOSAL", src |-> Proposer[round[p]], 354 round |-> round[p], proposal |-> Proposal(v, t), validRound |-> vr]) IN 355 /\ <<p, msg>> \in receivedTimelyProposal \* updated line 36 356 /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(Proposal(v, t)) } IN 357 /\ Cardinality(PV) >= THRESHOLD2 \* line 36 358 /\ evidence' = PV \union {msg} \union evidence 359 /\ IF step[p] = "PREVOTE" 360 THEN \* lines 38-41: 361 /\ lockedValue' = [lockedValue EXCEPT ![p] = v] 362 /\ lockedRound' = [lockedRound EXCEPT ![p] = round[p]] 363 /\ BroadcastPrecommit(p, round[p], Id(Proposal(v, t))) 364 /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] 365 ELSE 366 UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>> 367 \* lines 42-43 368 /\ validValue' = [validValue EXCEPT ![p] = v] 369 /\ validRound' = [validRound EXCEPT ![p] = round[p]] 370 /\ UNCHANGED <<round, decision, msgsPropose, msgsPrevote, 371 localClock, realTime, receivedTimelyProposal, inspectedProposal, 372 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 373 /\ action' = "UponProposalInPrevoteOrCommitAndPrevote" 374 375 \* lines 47-48 + 65-67 (onTimeoutPrecommit) 376 UponQuorumOfPrecommitsAny(p) == 377 /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]: 378 \* find the unique committers in the evidence 379 LET Committers == { m.src: m \in MyEvidence } IN 380 \* compare the number of the unique committers against the threshold 381 /\ Cardinality(Committers) >= THRESHOLD2 \* line 47 382 /\ evidence' = MyEvidence \union evidence 383 /\ round[p] + 1 \in Rounds 384 /\ StartRound(p, round[p] + 1) 385 /\ UNCHANGED <<decision, lockedValue, lockedRound, validValue, 386 validRound, msgsPropose, msgsPrevote, msgsPrecommit, 387 localClock, realTime, receivedTimelyProposal, inspectedProposal, 388 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 389 /\ action' = "UponQuorumOfPrecommitsAny" 390 391 \* lines 49-54 392 \* [PBTS-ALG-DECIDE.0] 393 UponProposalInPrecommitNoDecision(p) == 394 /\ decision[p] = NilDecision \* line 49 395 /\ \E v \in ValidValues, t \in Timestamps (* line 50*) , r \in Rounds, vr \in RoundsOrNil: 396 /\ LET msg == AsMsg([type |-> "PROPOSAL", src |-> Proposer[r], 397 round |-> r, proposal |-> Proposal(v, t), validRound |-> vr]) IN 398 /\ msg \in msgsPropose[r] \* line 49 399 /\ p \in inspectedProposal[r] 400 /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(Proposal(v, t)) } IN 401 /\ Cardinality(PV) >= THRESHOLD2 \* line 49 402 /\ evidence' = PV \union {msg} \union evidence 403 /\ decision' = [decision EXCEPT ![p] = Decision(v, t, round[p])] \* update the decision, line 51 404 \* The original algorithm does not have 'DECIDED', but it increments the height. 405 \* We introduced 'DECIDED' here to prevent the process from changing its decision. 406 /\ endConsensus' = [endConsensus EXCEPT ![p] = localClock[p]] 407 /\ step' = [step EXCEPT ![p] = "DECIDED"] 408 /\ UNCHANGED <<round, lockedValue, lockedRound, validValue, 409 validRound, msgsPropose, msgsPrevote, msgsPrecommit, 410 localClock, realTime, receivedTimelyProposal, inspectedProposal, 411 beginConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 412 /\ action' = "UponProposalInPrecommitNoDecision" 413 414 \* the actions below are not essential for safety, but added for completeness 415 416 \* lines 20-21 + 57-60 417 OnTimeoutPropose(p) == 418 /\ step[p] = "PROPOSE" 419 /\ p /= Proposer[round[p]] 420 /\ BroadcastPrevote(p, round[p], NilProposal) 421 /\ step' = [step EXCEPT ![p] = "PREVOTE"] 422 /\ UNCHANGED <<round, lockedValue, lockedRound, validValue, 423 validRound, decision, evidence, msgsPropose, msgsPrecommit, 424 localClock, realTime, receivedTimelyProposal, inspectedProposal, 425 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 426 /\ action' = "OnTimeoutPropose" 427 428 \* lines 44-46 429 OnQuorumOfNilPrevotes(p) == 430 /\ step[p] = "PREVOTE" 431 /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilProposal) } IN 432 /\ Cardinality(PV) >= THRESHOLD2 \* line 36 433 /\ evidence' = PV \union evidence 434 /\ BroadcastPrecommit(p, round[p], Id(NilProposal)) 435 /\ step' = [step EXCEPT ![p] = "PRECOMMIT"] 436 /\ UNCHANGED <<round, lockedValue, lockedRound, validValue, 437 validRound, decision, msgsPropose, msgsPrevote, 438 localClock, realTime, receivedTimelyProposal, inspectedProposal, 439 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 440 /\ action' = "OnQuorumOfNilPrevotes" 441 442 \* lines 55-56 443 OnRoundCatchup(p) == 444 \E r \in {rr \in Rounds: rr > round[p]}: 445 LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN 446 \E MyEvidence \in SUBSET RoundMsgs: 447 LET Faster == { m.src: m \in MyEvidence } IN 448 /\ Cardinality(Faster) >= THRESHOLD1 449 /\ evidence' = MyEvidence \union evidence 450 /\ StartRound(p, r) 451 /\ UNCHANGED <<decision, lockedValue, lockedRound, validValue, 452 validRound, msgsPropose, msgsPrevote, msgsPrecommit, 453 localClock, realTime, receivedTimelyProposal, inspectedProposal, 454 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 455 /\ action' = "OnRoundCatchup" 456 457 458 (********************* PROTOCOL TRANSITIONS ******************************) 459 \* advance the global clock 460 AdvanceRealTime == 461 /\ realTime < MaxTimestamp 462 /\ realTime' = realTime + 1 463 /\ \/ /\ ~ClockDrift 464 /\ localClock' = [p \in Corr |-> localClock[p] + 1] 465 \/ /\ ClockDrift 466 /\ UNCHANGED localClock 467 /\ UNCHANGED <<round, step, decision, lockedValue, lockedRound, 468 validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit, 469 localClock, receivedTimelyProposal, inspectedProposal, 470 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 471 /\ action' = "AdvanceRealTime" 472 473 \* advance the local clock of node p 474 AdvanceLocalClock(p) == 475 /\ localClock[p] < MaxTimestamp 476 /\ localClock' = [localClock EXCEPT ![p] = @ + 1] 477 /\ UNCHANGED <<round, step, decision, lockedValue, lockedRound, 478 validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit, 479 realTime, receivedTimelyProposal, inspectedProposal, 480 beginConsensus, endConsensus, lastBeginConsensus, proposalTime, proposalReceivedTime>> 481 /\ action' = "AdvanceLocalClock" 482 483 \* process timely messages 484 MessageProcessing(p) == 485 \* start round 486 \/ InsertProposal(p) 487 \* reception step 488 \/ ReceiveProposal(p) 489 \* processing step 490 \/ UponProposalInPropose(p) 491 \/ UponProposalInProposeAndPrevote(p) 492 \/ UponQuorumOfPrevotesAny(p) 493 \/ UponProposalInPrevoteOrCommitAndPrevote(p) 494 \/ UponQuorumOfPrecommitsAny(p) 495 \/ UponProposalInPrecommitNoDecision(p) 496 \* the actions below are not essential for safety, but added for completeness 497 \/ OnTimeoutPropose(p) 498 \/ OnQuorumOfNilPrevotes(p) 499 \/ OnRoundCatchup(p) 500 501 (* 502 * A system transition. In this specificatiom, the system may eventually deadlock, 503 * e.g., when all processes decide. This is expected behavior, as we focus on safety. 504 *) 505 Next == 506 \/ AdvanceRealTime 507 \/ /\ ClockDrift 508 /\ \E p \in Corr: AdvanceLocalClock(p) 509 \/ /\ SynchronizedLocalClocks 510 /\ \E p \in Corr: MessageProcessing(p) 511 512 ----------------------------------------------------------------------------- 513 514 (*************************** INVARIANTS *************************************) 515 516 \* [PBTS-INV-AGREEMENT.0] 517 AgreementOnValue == 518 \A p, q \in Corr: 519 /\ decision[p] /= NilDecision 520 /\ decision[q] /= NilDecision 521 => \E v \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r1 \in Rounds, r2 \in Rounds : 522 /\ decision[p] = Decision(v, t1, r1) 523 /\ decision[q] = Decision(v, t2, r2) 524 525 \* [PBTS-INV-TIME-AGR.0] 526 AgreementOnTime == 527 \A p, q \in Corr: 528 \A v1 \in ValidValues, v2 \in ValidValues, t1 \in Timestamps, t2 \in Timestamps, r \in Rounds : 529 /\ decision[p] = Decision(v1, t1, r) 530 /\ decision[q] = Decision(v2, t2, r) 531 => t1 = t2 532 533 \* [PBTS-CONSENSUS-TIME-VALID.0] 534 ConsensusTimeValid == 535 \A p \in Corr, t \in Timestamps : 536 \* if a process decides on v and t 537 (\E v \in ValidValues, r \in Rounds : decision[p] = Decision(v, t, r)) 538 \* then 539 => /\ beginConsensus - Precision <= t 540 /\ t < endConsensus[p] + Precision + Delay 541 542 \* [PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0] 543 ConsensusSafeValidCorrProp == 544 \A v \in ValidValues, t \in Timestamps : 545 \* if the proposer in the first round is correct 546 (/\ Proposer[0] \in Corr 547 \* and there exists a process that decided on v, t 548 /\ \E p \in Corr, r \in Rounds : decision[p] = Decision(v, t, r)) 549 \* then t is between the minimal and maximal initial local time 550 => /\ beginConsensus <= t 551 /\ t <= lastBeginConsensus 552 553 \* [PBTS-CONSENSUS-REALTIME-VALID-CORR.0] 554 ConsensusRealTimeValidCorr == 555 \A t \in Timestamps, r \in Rounds : 556 (/\ \E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r) 557 /\ proposalTime[r] /= NilTimestamp) 558 => /\ proposalTime[r] - Accuracy < t 559 /\ t < proposalTime[r] + Accuracy 560 561 \* [PBTS-CONSENSUS-REALTIME-VALID.0] 562 ConsensusRealTimeValid == 563 \A t \in Timestamps, r \in Rounds : 564 (\E p \in Corr, v \in ValidValues : decision[p] = Decision(v, t, r)) 565 => /\ proposalReceivedTime[r] - Accuracy - Precision < t 566 /\ t < proposalReceivedTime[r] + Accuracy + Precision + Delay 567 568 \* [PBTS-MSG-FAIR.0] 569 BoundedDelay == 570 \A r \in Rounds : 571 (/\ proposalTime[r] /= NilTimestamp 572 /\ proposalTime[r] + Delay < realTime) 573 => inspectedProposal[r] = Corr 574 575 \* [PBTS-CONSENSUS-TIME-LIVE.0] 576 ConsensusTimeLive == 577 \A r \in Rounds, p \in Corr : 578 (/\ proposalTime[r] /= NilTimestamp 579 /\ proposalTime[r] + Delay < realTime 580 /\ Proposer[r] \in Corr 581 /\ round[p] <= r) 582 => \E msg \in RoundProposals(r) : <<p, msg>> \in receivedTimelyProposal 583 584 \* a conjunction of all invariants 585 Inv == 586 /\ AgreementOnValue 587 /\ AgreementOnTime 588 /\ ConsensusTimeValid 589 /\ ConsensusSafeValidCorrProp 590 /\ ConsensusRealTimeValid 591 /\ ConsensusRealTimeValidCorr 592 /\ BoundedDelay 593 594 Liveness == 595 ConsensusTimeLive 596 597 =============================================================================