You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

597 lines
26 KiB

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