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