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