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

     1  #lang ivy1.7
     2  # ---
     3  # layout: page
     4  # title: Abstract specification of Tendermint in Ivy
     5  # ---
     6  
     7  # Here we define an abstract version of the Tendermint specification. We use
     8  # two main forms of abstraction: a) We abstract over how information is
     9  # transmitted (there is no network). b) We abstract functions using relations.
    10  # For example, we abstract over a node's current round, instead only tracking
    11  # with a relation which rounds the node has left. We do something similar for
    12  # the `lockedRound` variable. This is in order to avoid using a function from
    13  # node to round, and it allows us to emit verification conditions that are
    14  # efficiently solvable by Z3.
    15  
    16  # This specification also defines the observations that are used to adjudicate
    17  # misbehavior. Well-behaved nodes faithfully observe every message that they
    18  # use to take a step, while Byzantine nodes can fake observations about
    19  # themselves (including withholding observations). Misbehavior is defined using
    20  # the collection of all observations made (in reality, those observations must
    21  # be collected first, but we do not model this process).
    22  
    23  include domain_model
    24  
    25  module abstract_tendermint = {
    26  
    27  # Protocol state
    28  # ##############
    29  
    30      relation left_round(N:node, R:round)
    31      relation prevoted(N:node, R:round, V:value)
    32      relation precommitted(N:node, R:round, V:value)
    33      relation decided(N:node, R:round, V:value)
    34      relation locked(N:node, R:round, V:value)
    35  
    36  # Accountability relations
    37  # ########################
    38  
    39      relation observed_prevoted(N:node, R:round, V:value)
    40      relation observed_precommitted(N:node, R:round, V:value)
    41  
    42  # relations that are defined in terms of the previous two:
    43      relation observed_equivocation(N:node)
    44      relation observed_unlawful_prevote(N:node)
    45      relation agreement
    46      relation accountability_violation
    47  
    48      object defs = { # we hide those definitions and use them only when needed
    49          private {
    50              definition [observed_equivocation_def] observed_equivocation(N) = exists V1,V2,R .
    51                  V1 ~= V2 & (observed_precommitted(N,R,V1) & observed_precommitted(N,R,V2) | observed_prevoted(N,R,V1) & observed_prevoted(N,R,V2))
    52  
    53              definition [observed_unlawful_prevote_def] observed_unlawful_prevote(N) = exists V1,V2,R1,R2 .
    54                  V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & R1 < R2 & observed_precommitted(N,R1,V1) & observed_prevoted(N,R2,V2)
    55                  & forall Q,R . R1 <= R & R < R2 & nset.is_quorum(Q) -> exists N2 . nset.member(N2,Q) & ~observed_prevoted(N2,R,V2)
    56  
    57              definition [agreement_def] agreement = forall N1,N2,R1,R2,V1,V2 . well_behaved(N1) & well_behaved(N2) & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2
    58  
    59              definition [accountability_violation_def] accountability_violation = exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & (forall N . nset.member(N,Q1) & nset.member(N,Q2) -> observed_equivocation(N) | observed_unlawful_prevote(N))
    60          }
    61      }
    62  
    63  # Protocol transitions
    64  # ####################
    65  
    66      after init {
    67          left_round(N,R) := R < 0;
    68          prevoted(N,R,V) := false;
    69          precommitted(N,R,V) := false;
    70          decided(N,R,V) := false;
    71          locked(N,R,V) := false;
    72  
    73          observed_prevoted(N,R,V) := false;
    74          observed_precommitted(N,R,V) := false;
    75      }
    76  
    77  # Actions are named after the corresponding line numbers in the Tendermint
    78  # arXiv paper.
    79  
    80      action l_11(n:node, r:round) = { # start round r
    81          require ~left_round(n,r);
    82          left_round(n,R) := R < r;
    83      }
    84  
    85      action l_22(n:node, rp:round, v:value) = {
    86          require ~left_round(n,rp);
    87          require ~prevoted(n,rp,V) & ~precommitted(n,rp,V);
    88          require (forall R,V . locked(n,R,V) -> V = v) | v = value.nil;
    89          prevoted(n, rp, v) := true;
    90          left_round(n, R) := R < rp; # leave all lower rounds.
    91  
    92          observed_prevoted(n, rp, v) := observed_prevoted(n, rp, v) | well_behaved(n); # the node observes itself
    93      }
    94  
    95      action l_28(n:node, rp:round, v:value, vr:round, q:nset) = {
    96          require ~left_round(n,rp) & ~prevoted(n,rp,V);
    97          require ~prevoted(n,rp,V) & ~precommitted(n,rp,V);
    98          require vr < rp;
    99          require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,vr,v) | ~well_behaved(N)));
   100          var proposal:value;
   101          if value.valid(v) & ((forall R0,V0 . locked(n,R0,V0) -> R0 <= vr) | (forall R,V . locked(n,R,V) -> V = v)) {
   102              proposal := v;
   103          }
   104          else {
   105              proposal := value.nil;
   106          };
   107          prevoted(n, rp, proposal) := true;
   108          left_round(n, R) := R < rp; # leave all lower rounds
   109  
   110          observed_prevoted(N, vr, v) := observed_prevoted(N, vr, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q
   111          observed_prevoted(n, rp, proposal) := observed_prevoted(n, rp, proposal) | well_behaved(n); # the node observes itself
   112      }
   113  
   114      action l_36(n:node, rp:round, v:value, q:nset) = {
   115          require v ~= value.nil;
   116          require ~left_round(n,rp);
   117          require exists V . prevoted(n,rp,V);
   118          require ~precommitted(n,rp,V);
   119          require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,rp,v) | ~well_behaved(N)));
   120          precommitted(n, rp, v) := true;
   121          left_round(n, R) := R < rp; # leave all lower rounds
   122          locked(n,R,V) := R <= rp & V = v;
   123  
   124          observed_prevoted(N, rp, v) := observed_prevoted(N, rp, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q
   125          observed_precommitted(n, rp, v) := observed_precommitted(n, rp, v) | well_behaved(n); # the node observes itself
   126      }
   127  
   128      action l_44(n:node, rp:round, q:nset) = {
   129          require ~left_round(n,rp);
   130          require ~precommitted(n,rp,V);
   131          require nset.is_quorum(q) & (forall N .nset.member(N,q) -> (prevoted(N,rp,value.nil) | ~well_behaved(N)));
   132          precommitted(n, rp, value.nil) := true;
   133          left_round(n, R) := R < rp; # leave all lower rounds
   134  
   135          observed_prevoted(N, rp, value.nil) := observed_prevoted(N, rp, value.nil) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q
   136          observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself
   137      }
   138  
   139      action l_57(n:node, rp:round) = {
   140          require ~left_round(n,rp);
   141          require ~prevoted(n,rp,V);
   142          prevoted(n, rp, value.nil) := true;
   143          left_round(n, R) := R < rp; # leave all lower rounds
   144  
   145          observed_prevoted(n, rp, value.nil) := observed_prevoted(n, rp, value.nil) | well_behaved(n); # the node observes itself
   146      }
   147  
   148      action l_61(n:node, rp:round) = {
   149          require ~left_round(n,rp);
   150          require ~precommitted(n,rp,V);
   151          precommitted(n, rp, value.nil) := true;
   152          left_round(n, R) := R < rp; # leave all lower rounds
   153  
   154          observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself
   155      }
   156  
   157      action decide(n:node, r:round, v:value, q:nset) = {
   158          require v ~= value.nil;
   159          require nset.is_quorum(q) & (forall N . nset.member(N, q) -> (precommitted(N, r, v) | ~well_behaved(N)));
   160          decided(n, r, v) := true;
   161  
   162          observed_precommitted(N, r, v) := observed_precommitted(N, r, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the precommits of quorum q
   163  
   164      }
   165  
   166      action misbehave = {
   167  # Byzantine nodes can claim they observed whatever they want about themselves,
   168  # but they cannot remove observations. Note that we use assume because we don't
   169  # want those to be checked; we just want them to be true (that's the model of
   170  # Byzantine behavior).
   171          observed_prevoted(N,R,V) := *;
   172          assume (old observed_prevoted(N,R,V)) -> observed_prevoted(N,R,V);
   173          assume well_behaved(N) -> old observed_prevoted(N,R,V) = observed_prevoted(N,R,V);
   174          observed_precommitted(N,R,V) := *;
   175          assume (old observed_precommitted(N,R,V)) -> observed_precommitted(N,R,V);
   176          assume well_behaved(N) -> old observed_precommitted(N,R,V) = observed_precommitted(N,R,V);
   177      }
   178  }