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.

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