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.

779 lines
29 KiB

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