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.

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