github.com/vipernet-xyz/tm@v0.34.24/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