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