|
|
- ------------------- MODULE TendermintAccInv_004_draft --------------------------
- (*
- An inductive invariant for TendermintAcc3, which capture the forked
- and non-forked cases.
-
- * Version 3. Modular and parameterized definitions.
- * Version 2. Bugfixes in the spec and an inductive invariant.
-
- Igor Konnov, 2020.
- *)
-
- EXTENDS TendermintAcc_004_draft
-
- (************************** TYPE INVARIANT ***********************************)
- (* first, we define the sets of all potential messages *)
- \* @type: Set(PROPMESSAGE);
- AllProposals ==
- [type: {"PROPOSAL"},
- src: AllProcs,
- round: Rounds,
- proposal: ValuesOrNil,
- validRound: RoundsOrNil]
-
- \* @type: Set(PREMESSAGE);
- AllPrevotes ==
- [type: {"PREVOTE"},
- src: AllProcs,
- round: Rounds,
- id: ValuesOrNil]
-
- \* @type: Set(PREMESSAGE);
- AllPrecommits ==
- [type: {"PRECOMMIT"},
- src: AllProcs,
- round: Rounds,
- id: ValuesOrNil]
-
- (* the standard type invariant -- importantly, it is inductive *)
- TypeOK ==
- /\ round \in [Corr -> Rounds]
- /\ step \in [Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }]
- /\ decision \in [Corr -> ValidValues \union {NilValue}]
- /\ lockedValue \in [Corr -> ValidValues \union {NilValue}]
- /\ lockedRound \in [Corr -> RoundsOrNil]
- /\ validValue \in [Corr -> ValidValues \union {NilValue}]
- /\ validRound \in [Corr -> RoundsOrNil]
- /\ msgsPropose \in [Rounds -> SUBSET AllProposals]
- /\ BenignRoundsInMessages(msgsPropose)
- /\ msgsPrevote \in [Rounds -> SUBSET AllPrevotes]
- /\ BenignRoundsInMessages(msgsPrevote)
- /\ msgsPrecommit \in [Rounds -> SUBSET AllPrecommits]
- /\ BenignRoundsInMessages(msgsPrecommit)
- /\ evidence \in SUBSET (AllProposals \union AllPrevotes \union AllPrecommits)
- /\ action \in {
- "Init",
- "InsertProposal",
- "UponProposalInPropose",
- "UponProposalInProposeAndPrevote",
- "UponQuorumOfPrevotesAny",
- "UponProposalInPrevoteOrCommitAndPrevote",
- "UponQuorumOfPrecommitsAny",
- "UponProposalInPrecommitNoDecision",
- "OnTimeoutPropose",
- "OnQuorumOfNilPrevotes",
- "OnRoundCatchup"
- }
-
- (************************** INDUCTIVE INVARIANT *******************************)
- EvidenceContainsMessages ==
- \* evidence contains only the messages from:
- \* msgsPropose, msgsPrevote, and msgsPrecommit
- \A m \in evidence:
- LET r == m.round
- t == m.type
- IN
- CASE t = "PROPOSAL" -> m \in msgsPropose[r]
- [] t = "PREVOTE" -> m \in msgsPrevote[r]
- [] OTHER -> m \in msgsPrecommit[r]
-
- NoFutureMessagesForLargerRounds(p) ==
- \* a correct process does not send messages for the future rounds
- \A r \in { rr \in Rounds: rr > round[p] }:
- /\ \A m \in msgsPropose[r]: m.src /= p
- /\ \A m \in msgsPrevote[r]: m.src /= p
- /\ \A m \in msgsPrecommit[r]: m.src /= p
-
- NoFutureMessagesForCurrentRound(p) ==
- \* a correct process does not send messages in the future
- LET r == round[p] IN
- /\ Proposer[r] = p \/ \A m \in msgsPropose[r]: m.src /= p
- /\ \/ step[p] \in {"PREVOTE", "PRECOMMIT", "DECIDED"}
- \/ \A m \in msgsPrevote[r]: m.src /= p
- /\ \/ step[p] \in {"PRECOMMIT", "DECIDED"}
- \/ \A m \in msgsPrecommit[r]: m.src /= p
-
- \* the correct processes never send future messages
- AllNoFutureMessagesSent ==
- \A p \in Corr:
- /\ NoFutureMessagesForCurrentRound(p)
- /\ NoFutureMessagesForLargerRounds(p)
-
- \* a correct process in the PREVOTE state has sent a PREVOTE message
- IfInPrevoteThenSentPrevote(p) ==
- step[p] = "PREVOTE" =>
- \E m \in msgsPrevote[round[p]]:
- /\ m.id \in ValidValues \cup { NilValue }
- /\ m.src = p
-
- AllIfInPrevoteThenSentPrevote ==
- \A p \in Corr: IfInPrevoteThenSentPrevote(p)
-
- \* a correct process in the PRECOMMIT state has sent a PRECOMMIT message
- IfInPrecommitThenSentPrecommit(p) ==
- step[p] = "PRECOMMIT" =>
- \E m \in msgsPrecommit[round[p]]:
- /\ m.id \in ValidValues \cup { NilValue }
- /\ m.src = p
-
- AllIfInPrecommitThenSentPrecommit ==
- \A p \in Corr: IfInPrecommitThenSentPrecommit(p)
-
- \* a process in the PRECOMMIT state has sent a PRECOMMIT message
- IfInDecidedThenValidDecision(p) ==
- step[p] = "DECIDED" <=> decision[p] \in ValidValues
-
- AllIfInDecidedThenValidDecision ==
- \A p \in Corr: IfInDecidedThenValidDecision(p)
-
- \* a decided process should have received a proposal on its decision
- IfInDecidedThenReceivedProposal(p) ==
- step[p] = "DECIDED" =>
- \E r \in Rounds: \* r is not necessarily round[p]
- /\ \E m \in msgsPropose[r] \intersect evidence:
- /\ m.src = Proposer[r]
- /\ m.proposal = decision[p]
- \* not inductive: /\ m.src \in Corr => (m.validRound <= r)
-
- AllIfInDecidedThenReceivedProposal ==
- \A p \in Corr:
- IfInDecidedThenReceivedProposal(p)
-
- \* a decided process has received two-thirds of precommit messages
- IfInDecidedThenReceivedTwoThirds(p) ==
- step[p] = "DECIDED" =>
- \E r \in Rounds:
- LET PV ==
- { m \in msgsPrecommit[r] \intersect evidence: m.id = decision[p] }
- IN
- Cardinality(PV) >= THRESHOLD2
-
- AllIfInDecidedThenReceivedTwoThirds ==
- \A p \in Corr:
- IfInDecidedThenReceivedTwoThirds(p)
-
- \* for a round r, there is proposal by the round proposer for a valid round vr
- ProposalInRound(r, proposedVal, vr) ==
- \E m \in msgsPropose[r]:
- /\ m.src = Proposer[r]
- /\ m.proposal = proposedVal
- /\ m.validRound = vr
-
- TwoThirdsPrevotes(vr, v) ==
- LET PV == { mm \in msgsPrevote[vr] \intersect evidence: mm.id = v } IN
- Cardinality(PV) >= THRESHOLD2
-
- \* if a process sends a PREVOTE, then there are three possibilities:
- \* 1) the process is faulty, 2) the PREVOTE cotains Nil,
- \* 3) there is a proposal in an earlier (valid) round and two thirds of PREVOTES
- IfSentPrevoteThenReceivedProposalOrTwoThirds(r) ==
- \A mpv \in msgsPrevote[r]:
- \/ mpv.src \in Faulty
- \* lockedRound and lockedValue is beyond my comprehension
- \/ mpv.id = NilValue
- \//\ mpv.src \in Corr
- /\ mpv.id /= NilValue
- /\ \/ ProposalInRound(r, mpv.id, NilRound)
- \/ \E vr \in { rr \in Rounds: rr < r }:
- /\ ProposalInRound(r, mpv.id, vr)
- /\ TwoThirdsPrevotes(vr, mpv.id)
-
- AllIfSentPrevoteThenReceivedProposalOrTwoThirds ==
- \A r \in Rounds:
- IfSentPrevoteThenReceivedProposalOrTwoThirds(r)
-
- \* if a correct process has sent a PRECOMMIT, then there are two thirds,
- \* either on a valid value, or a nil value
- IfSentPrecommitThenReceivedTwoThirds ==
- \A r \in Rounds:
- \A mpc \in msgsPrecommit[r]:
- mpc.src \in Corr =>
- \/ /\ mpc.id \in ValidValues
- /\ LET PV ==
- { m \in msgsPrevote[r] \intersect evidence: m.id = mpc.id }
- IN
- Cardinality(PV) >= THRESHOLD2
- \/ /\ mpc.id = NilValue
- /\ Cardinality(msgsPrevote[r]) >= THRESHOLD2
-
- \* if a correct process has sent a precommit message in a round, it should
- \* have sent a prevote
- IfSentPrecommitThenSentPrevote ==
- \A r \in Rounds:
- \A mpc \in msgsPrecommit[r]:
- mpc.src \in Corr =>
- \E m \in msgsPrevote[r]:
- m.src = mpc.src
-
- \* there is a locked round if a only if there is a locked value
- LockedRoundIffLockedValue(p) ==
- (lockedRound[p] = NilRound) <=> (lockedValue[p] = NilValue)
-
- AllLockedRoundIffLockedValue ==
- \A p \in Corr:
- LockedRoundIffLockedValue(p)
-
- \* when a process locked a round, it must have sent a precommit on the locked value.
- IfLockedRoundThenSentCommit(p) ==
- lockedRound[p] /= NilRound
- => \E r \in { rr \in Rounds: rr <= round[p] }:
- \E m \in msgsPrecommit[r]:
- m.src = p /\ m.id = lockedValue[p]
-
- AllIfLockedRoundThenSentCommit ==
- \A p \in Corr:
- IfLockedRoundThenSentCommit(p)
-
- \* a process always locks the latest round, for which it has sent a PRECOMMIT
- LatestPrecommitHasLockedRound(p) ==
- LET pPrecommits ==
- {mm \in UNION { msgsPrecommit[r]: r \in Rounds }: mm.src = p /\ mm.id /= NilValue }
- IN
- pPrecommits /= {}
- => LET latest ==
- CHOOSE m \in pPrecommits:
- \A m2 \in pPrecommits:
- m2.round <= m.round
- IN
- /\ lockedRound[p] = latest.round
- /\ lockedValue[p] = latest.id
-
- AllLatestPrecommitHasLockedRound ==
- \A p \in Corr:
- LatestPrecommitHasLockedRound(p)
-
- \* Every correct process sends only one value or NilValue.
- \* This test has quantifier alternation -- a threat to all decision procedures.
- \* Luckily, the sets Corr and ValidValues are small.
- \* @type: (ROUND, ROUND -> Set(PREMESSAGE)) => Bool;
- NoEquivocationByCorrect(r, msgs) ==
- \A p \in Corr:
- \E v \in ValidValues \union {NilValue}:
- \A m \in msgs[r]:
- \/ m.src /= p
- \/ m.id = v
-
- \* a proposer nevers sends two values
- \* @type: (ROUND, ROUND -> Set(PROPMESSAGE)) => Bool;
- ProposalsByProposer(r, msgs) ==
- \* if the proposer is not faulty, it sends only one value
- \E v \in ValidValues:
- \A m \in msgs[r]:
- \/ m.src \in Faulty
- \/ m.src = Proposer[r] /\ m.proposal = v
-
- AllNoEquivocationByCorrect ==
- \A r \in Rounds:
- /\ ProposalsByProposer(r, msgsPropose)
- /\ NoEquivocationByCorrect(r, msgsPrevote)
- /\ NoEquivocationByCorrect(r, msgsPrecommit)
-
- \* construct the set of the message senders
- \* @type: (Set(MESSAGE)) => Set(PROCESS);
- Senders(M) == { m.src: m \in M }
-
- \* The final piece by Josef Widder:
- \* if T + 1 processes precommit on the same value in a round,
- \* then in the future rounds there are less than 2T + 1 prevotes for another value
- PrecommitsLockValue ==
- \A r \in Rounds:
- \A v \in ValidValues \union {NilValue}:
- \/ LET Precommits == {m \in msgsPrecommit[r]: m.id = v}
- IN
- Cardinality(Senders(Precommits)) < THRESHOLD1
- \/ \A fr \in { rr \in Rounds: rr > r }: \* future rounds
- \A w \in (ValuesOrNil) \ {v}:
- LET Prevotes == {m \in msgsPrevote[fr]: m.id = w}
- IN
- Cardinality(Senders(Prevotes)) < THRESHOLD2
-
- \* a combination of all lemmas
- Inv ==
- /\ EvidenceContainsMessages
- /\ AllNoFutureMessagesSent
- /\ AllIfInPrevoteThenSentPrevote
- /\ AllIfInPrecommitThenSentPrecommit
- /\ AllIfInDecidedThenReceivedProposal
- /\ AllIfInDecidedThenReceivedTwoThirds
- /\ AllIfInDecidedThenValidDecision
- /\ AllLockedRoundIffLockedValue
- /\ AllIfLockedRoundThenSentCommit
- /\ AllLatestPrecommitHasLockedRound
- /\ AllIfSentPrevoteThenReceivedProposalOrTwoThirds
- /\ IfSentPrecommitThenSentPrevote
- /\ IfSentPrecommitThenReceivedTwoThirds
- /\ AllNoEquivocationByCorrect
- /\ PrecommitsLockValue
-
- \* this is the inductive invariant we like to check
- TypedInv == TypeOK /\ Inv
-
- \* UNUSED FOR SAFETY
- ValidRoundNotSmallerThanLockedRound(p) ==
- validRound[p] >= lockedRound[p]
-
- \* UNUSED FOR SAFETY
- ValidRoundIffValidValue(p) ==
- (validRound[p] = NilRound) <=> (validValue[p] = NilValue)
-
- \* UNUSED FOR SAFETY
- AllValidRoundIffValidValue ==
- \A p \in Corr: ValidRoundIffValidValue(p)
-
- \* if validRound is defined, then there are two-thirds of PREVOTEs
- IfValidRoundThenTwoThirds(p) ==
- \/ validRound[p] = NilRound
- \/ LET PV == { m \in msgsPrevote[validRound[p]]: m.id = validValue[p] } IN
- Cardinality(PV) >= THRESHOLD2
-
- \* UNUSED FOR SAFETY
- AllIfValidRoundThenTwoThirds ==
- \A p \in Corr: IfValidRoundThenTwoThirds(p)
-
- \* a valid round can be only set to a valid value that was proposed earlier
- IfValidRoundThenProposal(p) ==
- \/ validRound[p] = NilRound
- \/ \E m \in msgsPropose[validRound[p]]:
- m.proposal = validValue[p]
-
- \* UNUSED FOR SAFETY
- AllIfValidRoundThenProposal ==
- \A p \in Corr: IfValidRoundThenProposal(p)
-
- (******************************** THEOREMS ***************************************)
- (* Under this condition, the faulty processes can decide alone *)
- FaultyQuorum == Cardinality(Faulty) >= THRESHOLD2
-
- (* The standard condition of the Tendermint security model *)
- LessThanThirdFaulty == N > 3 * T /\ Cardinality(Faulty) <= T
-
- (*
- TypedInv is an inductive invariant, provided that there is no faulty quorum.
- We run Apalache to prove this theorem only for fixed instances of 4 to 10 processes.
- (We run Apalache manually, as it does not parse theorem statements at the moment.)
- To get a parameterized argument, one has to use a theorem prover, e.g., TLAPS.
- *)
- THEOREM TypedInvIsInductive ==
- \/ FaultyQuorum \* if there are 2 * T + 1 faulty processes, we give up
- \//\ Init => TypedInv
- /\ TypedInv /\ [Next]_vars => TypedInv'
-
- (*
- There should be no fork, when there are less than 1/3 faulty processes.
- *)
- THEOREM AgreementWhenLessThanThirdFaulty ==
- LessThanThirdFaulty /\ TypedInv => Agreement
-
- (*
- In a more general case, when there are less than 2/3 faulty processes,
- there is either Agreement (no fork), or two scenarios exist:
- equivocation by Faulty, or amnesia by Faulty.
- *)
- THEOREM AgreementOrFork ==
- ~FaultyQuorum /\ TypedInv => Accountability
-
- =============================================================================
-
|