github.com/aakash4dev/cometbft@v0.38.2/spec/ivy-proofs/accountable_safety_1.ivy (about)

     1  #lang ivy1.7
     2  # ---
     3  # layout: page
     4  # title: Proof of Classic Safety
     5  # ---
     6  
     7  include tendermint
     8  include abstract_tendermint
     9  
    10  # Here we prove the first accountability property: if two well-behaved nodes
    11  # disagree, then there are two quorums Q1 and Q2 such that all members of the
    12  # intersection of Q1 and Q2 have violated the accountability properties.
    13  
    14  # The proof is done in two steps: first we prove the abstract specification
    15  # satisfies the property, and then we show by refinement that this property
    16  # also holds in the concrete specification.
    17  
    18  # To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_1 accountable_safety_1.ivy`
    19  # To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_1 accountable_safety_1.ivy`
    20  # To check the whole proof, use `ivy_check accountable_safety_1.ivy`.
    21  
    22  
    23  # Proof of the accountability property in the abstract specification
    24  # ==================================================================
    25  
    26  # We prove with tactics (see `lemma_1` and `lemma_2`) that, if some basic
    27  # invariants hold (see `invs` below), then the accountability property holds.
    28  
    29  isolate abstract_accountable_safety = {
    30  
    31      instantiate abstract_tendermint
    32  
    33  # The main property
    34  # -----------------
    35  
    36  # If there is disagreement, then there is evidence that a third of the nodes
    37  # have violated the protocol:
    38      invariant [accountability] agreement | accountability_violation
    39      proof {
    40          apply lemma_1.thm # this reduces to goal to three subgoals: p1, p2, and p3 (see their definition below)
    41          proof [p1] {
    42              assume invs.inv1
    43          }
    44          proof [p2] {
    45              assume invs.inv2
    46          }
    47          proof [p3] {
    48              assume invs.inv3
    49          }
    50      }
    51  
    52  # The invariants
    53  # --------------
    54  
    55      isolate invs = {
    56  
    57          # well-behaved nodes observe their own actions faithfully:
    58          invariant [inv1] well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
    59          # if a value is precommitted by a well-behaved node, then a quorum is observed to prevote it:
    60          invariant [inv2] (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil  -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V)
    61          # if a value is decided by a well-behaved node, then a quorum is observed to precommit it:
    62          invariant [inv3] (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V)
    63          private {
    64              invariant (precommitted(N,R,V) | prevoted(N,R,V)) -> 0 <= R
    65              invariant R < 0 -> left_round(N,R)
    66          }
    67  
    68      } with this, nset, round, accountable_bft.max_2f_byzantine
    69  
    70  # The theorems proved with tactics
    71  # --------------------------------
    72  
    73  # Using complete induction on rounds, we prove that, assuming that the
    74  # invariants inv1, inv2, and inv3 hold, the accountability property holds.
    75  
    76  # For technical reasons, we separate the proof in two steps
    77      isolate lemma_1 = {
    78  
    79          specification {
    80              theorem [thm] {
    81                  property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
    82                  property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil  -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V)
    83                  property [p3] forall R,V. (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V)
    84                  #-------------------------------------------------------------------------------------------------------------------------------------------
    85                  property agreement | accountability_violation
    86              }
    87              proof {
    88                  assume inductive_property # the theorem follows from what we prove by induction below
    89              }
    90          }
    91  
    92          implementation {
    93              # complete induction is not built-in, so we introduce it with an axiom. Note that this only holds for a type where 0 is the smallest element
    94              axiom [complete_induction] {
    95                  relation p(X:round)
    96                  { # base case
    97                      property p(0)
    98                  }
    99                  { # inductive step: show that if the property is true for all X lower or equal to x and y=x+1, then the property is true of y
   100                      individual a:round
   101                      individual b:round
   102                      property (forall X. 0 <= X & X <= a -> p(X)) & round.succ(a,b) -> p(b)
   103                  }
   104                  #--------------------------
   105                  property forall X . 0 <= X -> p(X)
   106              }
   107  
   108              # The main lemma: if inv1 and inv2 below hold and a quorum is observed to
   109              # precommit V1 at R1 and another quorum is observed to precommit V2~=V1 at
   110              # R2>=R1, then the intersection of two quorums (i.e. f+1 nodes) is observed to
   111              # violate the protocol. We prove this by complete induction on R2.
   112              theorem [inductive_property] {
   113                  property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
   114                  property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) -> V = value.nil | exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V)
   115                  #-----------------------------------------------------------------------------------------------------------------------
   116                  property forall R2. 0 <= R2  -> ((exists V2,Q1,R1,V1,Q1 . V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & 0 <= R1 & R1 <= R2 & nset.is_quorum(Q1) & (forall N . nset.member(N,Q1) -> observed_precommitted(N,R1,V1)) & (exists Q2 . nset.is_quorum(Q2) & forall N . nset.member(N,Q2) -> observed_prevoted(N,R2,V2))) -> accountability_violation)
   117              }
   118              proof {
   119                  apply complete_induction # the two subgoals (base case and inductive case) are then discharged automatically
   120                  # NOTE: this can take a long time depending on the SMT random seed (to try a different seed, use `ivy_check seed=$RANDOM`
   121              }
   122          }
   123      } with this, round, nset, accountable_bft.max_2f_byzantine, defs.observed_equivocation_def, defs.observed_unlawful_prevote_def, defs.accountability_violation_def, defs.agreement_def
   124  
   125  } with round
   126  
   127  # The final proof
   128  # ===============
   129  
   130  isolate accountable_safety_1 = {
   131  
   132  # First we instantiate the concrete protocol:
   133      instantiate tendermint(abstract_accountable_safety)
   134  
   135  # We then define what we mean by agreement
   136      relation agreement
   137      definition [agreement_def] agreement = forall N1,N2. well_behaved(N1) & well_behaved(N2) & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2)
   138  
   139      invariant abstract_accountable_safety.agreement -> agreement
   140  
   141      invariant [accountability] agreement | abstract_accountable_safety.accountability_violation
   142  
   143  } with value, round, proposers, shim, abstract_accountable_safety, abstract_accountable_safety.defs.agreement_def, accountable_safety_1.agreement_def