github.com/aakash4dev/cometbft@v0.38.2/spec/ivy-proofs/classic_safety.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 classic safety property: assuming that every two quorums 11 # have a well-behaved node in common, no two well-behaved nodes ever disagree. 12 13 # The proof is done in two steps: first we prove the the abstract specification 14 # satisfies the property, and then we show by refinement that this property 15 # also holds in the concrete specification. 16 17 # To see what is checked in the refinement proof, use `ivy_show isolate=classic_safety classic_safety.ivy` 18 # To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_classic_safety classic_safety.ivy` 19 20 # To check the whole proof, use `ivy_check classic_safety.ivy`. 21 22 # Note that all the verification conditions sent to Z3 for this proof are in 23 # EPR. 24 25 # Classic safety in the abstract model 26 # ==================================== 27 28 # We start by proving that classic safety holds in the abstract model. 29 30 isolate abstract_classic_safety = { 31 32 instantiate abstract_tendermint 33 34 invariant [classic_safety] classic_bft.quorum_intersection & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2 35 36 # The notion of choosable value 37 # ----------------------------- 38 39 relation choosable(R:round, V:value) 40 definition choosable(R,V) = exists Q . nset.is_quorum(Q) & forall N . well_behaved(N) & nset.member(N,Q) -> ~left_round(N,R) | precommitted(N,R,V) 41 42 # Main invariants 43 # --------------- 44 45 # `classic_safety` is inductive relative to those invariants 46 47 invariant [decision_is_quorum_precommit] (exists N1 . decided(N1,R,V)) -> exists Q. nset.is_quorum(Q) & forall N2. well_behaved(N2) & nset.member(N2, Q) -> precommitted(N2,R,V) 48 49 invariant [precommitted_is_quorum_prevote] V ~= value.nil & (exists N1 . precommitted(N1,R,V)) -> exists Q. nset.is_quorum(Q) & forall N2. well_behaved(N2) & nset.member(N2, Q) -> prevoted(N2,R,V) 50 51 invariant [prevote_unique_per_round] prevoted(N,R,V1) & prevoted(N,R,V2) -> V1 = V2 52 53 # This is the core invariant: as long as a precommitted value is still choosable, it remains protected by a lock and prevents any new value from being prevoted: 54 invariant [locks] classic_bft.quorum_intersection & V ~= value.nil & precommitted(N,R,V) & choosable(R,V) -> locked(N,R,V) & forall R2,V2 . R < R2 & prevoted(N,R2,V2) -> V2 = V | V2 = value.nil 55 56 # Supporting invariants 57 # --------------------- 58 59 # The main invariants are inductive relative to those 60 61 invariant decided(N,R,V) -> V ~= value.nil 62 63 invariant left_round(N,R2) & R1 < R2 -> left_round(N,R1) # if a node left round R2>R1, then it also left R1: 64 65 invariant prevoted(N,R2,V2) & R1 < R2 -> left_round(N,R1) 66 invariant precommitted(N,R2,V2) & R1 < R2 -> left_round(N,R1) 67 68 } with round, nset, classic_bft.quorum_intersection_def 69 70 # The refinement proof 71 # ==================== 72 73 # Now, thanks to the refinement relation that we establish in 74 # `concrete_tendermint.ivy`, we prove that classic safety transfers to the 75 # concrete specification: 76 isolate classic_safety = { 77 78 # We instantiate the `tendermint` module providing `abstract_classic_safety` as abstract model. 79 instantiate tendermint(abstract_classic_safety) 80 81 # We prove that if every two quorums have a well-behaved node in common, 82 # then well-behaved nodes never disagree: 83 invariant [classic_safety] classic_bft.quorum_intersection & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2) 84 85 } with value, round, proposers, shim, abstract_classic_safety # here we list all the specifications that we rely on for this proof