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.

376 lines
13 KiB

  1. ------------------- MODULE TendermintAccInv_004_draft --------------------------
  2. (*
  3. An inductive invariant for TendermintAcc3, which capture the forked
  4. and non-forked cases.
  5. * Version 3. Modular and parameterized definitions.
  6. * Version 2. Bugfixes in the spec and an inductive invariant.
  7. Igor Konnov, 2020.
  8. *)
  9. EXTENDS TendermintAcc_004_draft
  10. (************************** TYPE INVARIANT ***********************************)
  11. (* first, we define the sets of all potential messages *)
  12. \* @type: Set(PROPMESSAGE);
  13. AllProposals ==
  14. [type: {"PROPOSAL"},
  15. src: AllProcs,
  16. round: Rounds,
  17. proposal: ValuesOrNil,
  18. validRound: RoundsOrNil]
  19. \* @type: Set(PREMESSAGE);
  20. AllPrevotes ==
  21. [type: {"PREVOTE"},
  22. src: AllProcs,
  23. round: Rounds,
  24. id: ValuesOrNil]
  25. \* @type: Set(PREMESSAGE);
  26. AllPrecommits ==
  27. [type: {"PRECOMMIT"},
  28. src: AllProcs,
  29. round: Rounds,
  30. id: ValuesOrNil]
  31. (* the standard type invariant -- importantly, it is inductive *)
  32. TypeOK ==
  33. /\ round \in [Corr -> Rounds]
  34. /\ step \in [Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }]
  35. /\ decision \in [Corr -> ValidValues \union {NilValue}]
  36. /\ lockedValue \in [Corr -> ValidValues \union {NilValue}]
  37. /\ lockedRound \in [Corr -> RoundsOrNil]
  38. /\ validValue \in [Corr -> ValidValues \union {NilValue}]
  39. /\ validRound \in [Corr -> RoundsOrNil]
  40. /\ msgsPropose \in [Rounds -> SUBSET AllProposals]
  41. /\ BenignRoundsInMessages(msgsPropose)
  42. /\ msgsPrevote \in [Rounds -> SUBSET AllPrevotes]
  43. /\ BenignRoundsInMessages(msgsPrevote)
  44. /\ msgsPrecommit \in [Rounds -> SUBSET AllPrecommits]
  45. /\ BenignRoundsInMessages(msgsPrecommit)
  46. /\ evidence \in SUBSET (AllProposals \union AllPrevotes \union AllPrecommits)
  47. /\ action \in {
  48. "Init",
  49. "InsertProposal",
  50. "UponProposalInPropose",
  51. "UponProposalInProposeAndPrevote",
  52. "UponQuorumOfPrevotesAny",
  53. "UponProposalInPrevoteOrCommitAndPrevote",
  54. "UponQuorumOfPrecommitsAny",
  55. "UponProposalInPrecommitNoDecision",
  56. "OnTimeoutPropose",
  57. "OnQuorumOfNilPrevotes",
  58. "OnRoundCatchup"
  59. }
  60. (************************** INDUCTIVE INVARIANT *******************************)
  61. EvidenceContainsMessages ==
  62. \* evidence contains only the messages from:
  63. \* msgsPropose, msgsPrevote, and msgsPrecommit
  64. \A m \in evidence:
  65. LET r == m.round
  66. t == m.type
  67. IN
  68. CASE t = "PROPOSAL" -> m \in msgsPropose[r]
  69. [] t = "PREVOTE" -> m \in msgsPrevote[r]
  70. [] OTHER -> m \in msgsPrecommit[r]
  71. NoFutureMessagesForLargerRounds(p) ==
  72. \* a correct process does not send messages for the future rounds
  73. \A r \in { rr \in Rounds: rr > round[p] }:
  74. /\ \A m \in msgsPropose[r]: m.src /= p
  75. /\ \A m \in msgsPrevote[r]: m.src /= p
  76. /\ \A m \in msgsPrecommit[r]: m.src /= p
  77. NoFutureMessagesForCurrentRound(p) ==
  78. \* a correct process does not send messages in the future
  79. LET r == round[p] IN
  80. /\ Proposer[r] = p \/ \A m \in msgsPropose[r]: m.src /= p
  81. /\ \/ step[p] \in {"PREVOTE", "PRECOMMIT", "DECIDED"}
  82. \/ \A m \in msgsPrevote[r]: m.src /= p
  83. /\ \/ step[p] \in {"PRECOMMIT", "DECIDED"}
  84. \/ \A m \in msgsPrecommit[r]: m.src /= p
  85. \* the correct processes never send future messages
  86. AllNoFutureMessagesSent ==
  87. \A p \in Corr:
  88. /\ NoFutureMessagesForCurrentRound(p)
  89. /\ NoFutureMessagesForLargerRounds(p)
  90. \* a correct process in the PREVOTE state has sent a PREVOTE message
  91. IfInPrevoteThenSentPrevote(p) ==
  92. step[p] = "PREVOTE" =>
  93. \E m \in msgsPrevote[round[p]]:
  94. /\ m.id \in ValidValues \cup { NilValue }
  95. /\ m.src = p
  96. AllIfInPrevoteThenSentPrevote ==
  97. \A p \in Corr: IfInPrevoteThenSentPrevote(p)
  98. \* a correct process in the PRECOMMIT state has sent a PRECOMMIT message
  99. IfInPrecommitThenSentPrecommit(p) ==
  100. step[p] = "PRECOMMIT" =>
  101. \E m \in msgsPrecommit[round[p]]:
  102. /\ m.id \in ValidValues \cup { NilValue }
  103. /\ m.src = p
  104. AllIfInPrecommitThenSentPrecommit ==
  105. \A p \in Corr: IfInPrecommitThenSentPrecommit(p)
  106. \* a process in the PRECOMMIT state has sent a PRECOMMIT message
  107. IfInDecidedThenValidDecision(p) ==
  108. step[p] = "DECIDED" <=> decision[p] \in ValidValues
  109. AllIfInDecidedThenValidDecision ==
  110. \A p \in Corr: IfInDecidedThenValidDecision(p)
  111. \* a decided process should have received a proposal on its decision
  112. IfInDecidedThenReceivedProposal(p) ==
  113. step[p] = "DECIDED" =>
  114. \E r \in Rounds: \* r is not necessarily round[p]
  115. /\ \E m \in msgsPropose[r] \intersect evidence:
  116. /\ m.src = Proposer[r]
  117. /\ m.proposal = decision[p]
  118. \* not inductive: /\ m.src \in Corr => (m.validRound <= r)
  119. AllIfInDecidedThenReceivedProposal ==
  120. \A p \in Corr:
  121. IfInDecidedThenReceivedProposal(p)
  122. \* a decided process has received two-thirds of precommit messages
  123. IfInDecidedThenReceivedTwoThirds(p) ==
  124. step[p] = "DECIDED" =>
  125. \E r \in Rounds:
  126. LET PV ==
  127. { m \in msgsPrecommit[r] \intersect evidence: m.id = decision[p] }
  128. IN
  129. Cardinality(PV) >= THRESHOLD2
  130. AllIfInDecidedThenReceivedTwoThirds ==
  131. \A p \in Corr:
  132. IfInDecidedThenReceivedTwoThirds(p)
  133. \* for a round r, there is proposal by the round proposer for a valid round vr
  134. ProposalInRound(r, proposedVal, vr) ==
  135. \E m \in msgsPropose[r]:
  136. /\ m.src = Proposer[r]
  137. /\ m.proposal = proposedVal
  138. /\ m.validRound = vr
  139. TwoThirdsPrevotes(vr, v) ==
  140. LET PV == { mm \in msgsPrevote[vr] \intersect evidence: mm.id = v } IN
  141. Cardinality(PV) >= THRESHOLD2
  142. \* if a process sends a PREVOTE, then there are three possibilities:
  143. \* 1) the process is faulty, 2) the PREVOTE cotains Nil,
  144. \* 3) there is a proposal in an earlier (valid) round and two thirds of PREVOTES
  145. IfSentPrevoteThenReceivedProposalOrTwoThirds(r) ==
  146. \A mpv \in msgsPrevote[r]:
  147. \/ mpv.src \in Faulty
  148. \* lockedRound and lockedValue is beyond my comprehension
  149. \/ mpv.id = NilValue
  150. \//\ mpv.src \in Corr
  151. /\ mpv.id /= NilValue
  152. /\ \/ ProposalInRound(r, mpv.id, NilRound)
  153. \/ \E vr \in { rr \in Rounds: rr < r }:
  154. /\ ProposalInRound(r, mpv.id, vr)
  155. /\ TwoThirdsPrevotes(vr, mpv.id)
  156. AllIfSentPrevoteThenReceivedProposalOrTwoThirds ==
  157. \A r \in Rounds:
  158. IfSentPrevoteThenReceivedProposalOrTwoThirds(r)
  159. \* if a correct process has sent a PRECOMMIT, then there are two thirds,
  160. \* either on a valid value, or a nil value
  161. IfSentPrecommitThenReceivedTwoThirds ==
  162. \A r \in Rounds:
  163. \A mpc \in msgsPrecommit[r]:
  164. mpc.src \in Corr =>
  165. \/ /\ mpc.id \in ValidValues
  166. /\ LET PV ==
  167. { m \in msgsPrevote[r] \intersect evidence: m.id = mpc.id }
  168. IN
  169. Cardinality(PV) >= THRESHOLD2
  170. \/ /\ mpc.id = NilValue
  171. /\ Cardinality(msgsPrevote[r]) >= THRESHOLD2
  172. \* if a correct process has sent a precommit message in a round, it should
  173. \* have sent a prevote
  174. IfSentPrecommitThenSentPrevote ==
  175. \A r \in Rounds:
  176. \A mpc \in msgsPrecommit[r]:
  177. mpc.src \in Corr =>
  178. \E m \in msgsPrevote[r]:
  179. m.src = mpc.src
  180. \* there is a locked round if a only if there is a locked value
  181. LockedRoundIffLockedValue(p) ==
  182. (lockedRound[p] = NilRound) <=> (lockedValue[p] = NilValue)
  183. AllLockedRoundIffLockedValue ==
  184. \A p \in Corr:
  185. LockedRoundIffLockedValue(p)
  186. \* when a process locked a round, it must have sent a precommit on the locked value.
  187. IfLockedRoundThenSentCommit(p) ==
  188. lockedRound[p] /= NilRound
  189. => \E r \in { rr \in Rounds: rr <= round[p] }:
  190. \E m \in msgsPrecommit[r]:
  191. m.src = p /\ m.id = lockedValue[p]
  192. AllIfLockedRoundThenSentCommit ==
  193. \A p \in Corr:
  194. IfLockedRoundThenSentCommit(p)
  195. \* a process always locks the latest round, for which it has sent a PRECOMMIT
  196. LatestPrecommitHasLockedRound(p) ==
  197. LET pPrecommits ==
  198. {mm \in UNION { msgsPrecommit[r]: r \in Rounds }: mm.src = p /\ mm.id /= NilValue }
  199. IN
  200. pPrecommits /= {}
  201. => LET latest ==
  202. CHOOSE m \in pPrecommits:
  203. \A m2 \in pPrecommits:
  204. m2.round <= m.round
  205. IN
  206. /\ lockedRound[p] = latest.round
  207. /\ lockedValue[p] = latest.id
  208. AllLatestPrecommitHasLockedRound ==
  209. \A p \in Corr:
  210. LatestPrecommitHasLockedRound(p)
  211. \* Every correct process sends only one value or NilValue.
  212. \* This test has quantifier alternation -- a threat to all decision procedures.
  213. \* Luckily, the sets Corr and ValidValues are small.
  214. \* @type: (ROUND, ROUND -> Set(PREMESSAGE)) => Bool;
  215. NoEquivocationByCorrect(r, msgs) ==
  216. \A p \in Corr:
  217. \E v \in ValidValues \union {NilValue}:
  218. \A m \in msgs[r]:
  219. \/ m.src /= p
  220. \/ m.id = v
  221. \* a proposer nevers sends two values
  222. \* @type: (ROUND, ROUND -> Set(PROPMESSAGE)) => Bool;
  223. ProposalsByProposer(r, msgs) ==
  224. \* if the proposer is not faulty, it sends only one value
  225. \E v \in ValidValues:
  226. \A m \in msgs[r]:
  227. \/ m.src \in Faulty
  228. \/ m.src = Proposer[r] /\ m.proposal = v
  229. AllNoEquivocationByCorrect ==
  230. \A r \in Rounds:
  231. /\ ProposalsByProposer(r, msgsPropose)
  232. /\ NoEquivocationByCorrect(r, msgsPrevote)
  233. /\ NoEquivocationByCorrect(r, msgsPrecommit)
  234. \* construct the set of the message senders
  235. \* @type: (Set(MESSAGE)) => Set(PROCESS);
  236. Senders(M) == { m.src: m \in M }
  237. \* The final piece by Josef Widder:
  238. \* if T + 1 processes precommit on the same value in a round,
  239. \* then in the future rounds there are less than 2T + 1 prevotes for another value
  240. PrecommitsLockValue ==
  241. \A r \in Rounds:
  242. \A v \in ValidValues \union {NilValue}:
  243. \/ LET Precommits == {m \in msgsPrecommit[r]: m.id = v}
  244. IN
  245. Cardinality(Senders(Precommits)) < THRESHOLD1
  246. \/ \A fr \in { rr \in Rounds: rr > r }: \* future rounds
  247. \A w \in (ValuesOrNil) \ {v}:
  248. LET Prevotes == {m \in msgsPrevote[fr]: m.id = w}
  249. IN
  250. Cardinality(Senders(Prevotes)) < THRESHOLD2
  251. \* a combination of all lemmas
  252. Inv ==
  253. /\ EvidenceContainsMessages
  254. /\ AllNoFutureMessagesSent
  255. /\ AllIfInPrevoteThenSentPrevote
  256. /\ AllIfInPrecommitThenSentPrecommit
  257. /\ AllIfInDecidedThenReceivedProposal
  258. /\ AllIfInDecidedThenReceivedTwoThirds
  259. /\ AllIfInDecidedThenValidDecision
  260. /\ AllLockedRoundIffLockedValue
  261. /\ AllIfLockedRoundThenSentCommit
  262. /\ AllLatestPrecommitHasLockedRound
  263. /\ AllIfSentPrevoteThenReceivedProposalOrTwoThirds
  264. /\ IfSentPrecommitThenSentPrevote
  265. /\ IfSentPrecommitThenReceivedTwoThirds
  266. /\ AllNoEquivocationByCorrect
  267. /\ PrecommitsLockValue
  268. \* this is the inductive invariant we like to check
  269. TypedInv == TypeOK /\ Inv
  270. \* UNUSED FOR SAFETY
  271. ValidRoundNotSmallerThanLockedRound(p) ==
  272. validRound[p] >= lockedRound[p]
  273. \* UNUSED FOR SAFETY
  274. ValidRoundIffValidValue(p) ==
  275. (validRound[p] = NilRound) <=> (validValue[p] = NilValue)
  276. \* UNUSED FOR SAFETY
  277. AllValidRoundIffValidValue ==
  278. \A p \in Corr: ValidRoundIffValidValue(p)
  279. \* if validRound is defined, then there are two-thirds of PREVOTEs
  280. IfValidRoundThenTwoThirds(p) ==
  281. \/ validRound[p] = NilRound
  282. \/ LET PV == { m \in msgsPrevote[validRound[p]]: m.id = validValue[p] } IN
  283. Cardinality(PV) >= THRESHOLD2
  284. \* UNUSED FOR SAFETY
  285. AllIfValidRoundThenTwoThirds ==
  286. \A p \in Corr: IfValidRoundThenTwoThirds(p)
  287. \* a valid round can be only set to a valid value that was proposed earlier
  288. IfValidRoundThenProposal(p) ==
  289. \/ validRound[p] = NilRound
  290. \/ \E m \in msgsPropose[validRound[p]]:
  291. m.proposal = validValue[p]
  292. \* UNUSED FOR SAFETY
  293. AllIfValidRoundThenProposal ==
  294. \A p \in Corr: IfValidRoundThenProposal(p)
  295. (******************************** THEOREMS ***************************************)
  296. (* Under this condition, the faulty processes can decide alone *)
  297. FaultyQuorum == Cardinality(Faulty) >= THRESHOLD2
  298. (* The standard condition of the Tendermint security model *)
  299. LessThanThirdFaulty == N > 3 * T /\ Cardinality(Faulty) <= T
  300. (*
  301. TypedInv is an inductive invariant, provided that there is no faulty quorum.
  302. We run Apalache to prove this theorem only for fixed instances of 4 to 10 processes.
  303. (We run Apalache manually, as it does not parse theorem statements at the moment.)
  304. To get a parameterized argument, one has to use a theorem prover, e.g., TLAPS.
  305. *)
  306. THEOREM TypedInvIsInductive ==
  307. \/ FaultyQuorum \* if there are 2 * T + 1 faulty processes, we give up
  308. \//\ Init => TypedInv
  309. /\ TypedInv /\ [Next]_vars => TypedInv'
  310. (*
  311. There should be no fork, when there are less than 1/3 faulty processes.
  312. *)
  313. THEOREM AgreementWhenLessThanThirdFaulty ==
  314. LessThanThirdFaulty /\ TypedInv => Agreement
  315. (*
  316. In a more general case, when there are less than 2/3 faulty processes,
  317. there is either Agreement (no fork), or two scenarios exist:
  318. equivocation by Faulty, or amnesia by Faulty.
  319. *)
  320. THEOREM AgreementOrFork ==
  321. ~FaultyQuorum /\ TypedInv => Accountability
  322. =============================================================================