You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
7.4 KiB

  1. #lang ivy1.7
  2. # ---
  3. # layout: page
  4. # title: Proof of Classic Safety
  5. # ---
  6. include tendermint
  7. include abstract_tendermint
  8. # Here we prove the first accountability property: if two well-behaved nodes
  9. # disagree, then there are two quorums Q1 and Q2 such that all members of the
  10. # intersection of Q1 and Q2 have violated the accountability properties.
  11. # The proof is done in two steps: first we prove the abstract specification
  12. # satisfies the property, and then we show by refinement that this property
  13. # also holds in the concrete specification.
  14. # To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_1 accountable_safety_1.ivy`
  15. # To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_1 accountable_safety_1.ivy`
  16. # To check the whole proof, use `ivy_check accountable_safety_1.ivy`.
  17. # Proof of the accountability property in the abstract specification
  18. # ==================================================================
  19. # We prove with tactics (see `lemma_1` and `lemma_2`) that, if some basic
  20. # invariants hold (see `invs` below), then the accountability property holds.
  21. isolate abstract_accountable_safety = {
  22. instantiate abstract_tendermint
  23. # The main property
  24. # -----------------
  25. # If there is disagreement, then there is evidence that a third of the nodes
  26. # have violated the protocol:
  27. invariant [accountability] agreement | 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))
  28. proof {
  29. apply lemma_2.thm # this reduces to goal to three subgoals: p1, p2, and p3 (see their definition below)
  30. proof [p1] {
  31. assume invs.inv1
  32. }
  33. proof [p2] {
  34. assume invs.inv2
  35. }
  36. proof [p3] {
  37. assume invs.inv3
  38. }
  39. }
  40. # The invariants
  41. # --------------
  42. isolate invs = {
  43. # well-behaved nodes observe their own actions faithfully:
  44. invariant [inv1] well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
  45. # if a value is precommitted by a well-behaved node, then a quorum is observed to prevote it:
  46. 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)
  47. # if a value is decided by a well-behaved node, then a quorum is observed to precommit it:
  48. 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)
  49. private {
  50. invariant (precommitted(N,R,V) | prevoted(N,R,V)) -> 0 <= R
  51. invariant R < 0 -> left_round(N,R)
  52. }
  53. } with this, nset, round, accountable_bft.max_2f_byzantine
  54. # The theorems proved with tactics
  55. # --------------------------------
  56. # Using complete induction on rounds, we prove that, assuming that the
  57. # invariants inv1, inv2, and inv3 hold, the accountability property holds.
  58. # For technical reasons, we separate the proof in two steps
  59. isolate lemma_1 = {
  60. # 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
  61. axiom [complete_induction] {
  62. relation p(X:round)
  63. { # base case
  64. property p(0)
  65. }
  66. { # 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
  67. individual a:round
  68. individual b:round
  69. property (forall X. 0 <= X & X <= a -> p(X)) & round.succ(a,b) -> p(b)
  70. }
  71. #--------------------------
  72. property forall X . 0 <= X -> p(X)
  73. }
  74. # the main lemma: if inv1 and inv2 below hold and a quorum is observed to
  75. # precommit V1 at R1 and another quorum is observed to precommit V2~=V1 at
  76. # R2>=R1, then the intersection of two quorums (i.e. f+1 nodes) is observed to
  77. # violate the protocol
  78. theorem [thm] {
  79. property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
  80. 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)
  81. #-----------------------------------------------------------------------------------------------------------------------
  82. 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))) -> 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))
  83. }
  84. proof {
  85. apply complete_induction # the two subgoals (base case and inductive case) are then discharged automatically
  86. }
  87. } with this, round, nset, accountable_bft.max_2f_byzantine, defs.observed_equivocation_def, defs.observed_unlawful_prevote_def
  88. # Now we put lemma_1 in a form that matches exactly the accountability property
  89. # we want to prove. This is a bit cumbersome and could probably be improved.
  90. isolate lemma_2 = {
  91. theorem [thm] {
  92. property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V))
  93. 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)
  94. 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)
  95. #-------------------------------------------------------------------------------------------------------------------------------------------
  96. property agreement | 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)
  97. }
  98. proof {
  99. assume lemma_1.thm
  100. }
  101. } with this, round, defs.agreement_def, lemma_1, nset, accountable_bft.max_2f_byzantine
  102. } with round
  103. # The final proof
  104. # ===============
  105. isolate accountable_safety_1 = {
  106. # First we instantiate the concrete protocol:
  107. instantiate tendermint(abstract_accountable_safety)
  108. # We then define what we mean by agreement
  109. relation agreement
  110. 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)
  111. invariant abstract_accountable_safety.agreement -> agreement
  112. invariant [accountability] agreement | exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & forall N . nset.member(N,Q1) & nset.member(N,Q2) -> abstract_accountable_safety.observed_equivocation(N) | abstract_accountable_safety.observed_unlawful_prevote(N)
  113. } with value, round, proposers, shim, abstract_accountable_safety, abstract_accountable_safety.defs.agreement_def, accountable_safety_1.agreement_def