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.

173 lines
7.5 KiB

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