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 }