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