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.

420 lines
15 KiB

  1. #lang ivy1.7
  2. # ---
  3. # layout: page
  4. # title: Specification of Tendermint in Ivy
  5. # ---
  6. # This specification closely follows the pseudo-code given in "The latest
  7. # gossip on BFT consensus" by E. Buchman, J. Kwon, Z. Milosevic
  8. # <https://arxiv.org/abs/1807.04938>
  9. include domain_model
  10. include network_shim
  11. # We model the Tendermint protocol as an Ivy object. Like in Object-Oriented
  12. # Programming, the basic structuring unit in Ivy is the object. Objects have
  13. # internal state and actions (i.e. methods in OO parlance) that modify their
  14. # state. We model Tendermint as an object whose actions represent steps taken
  15. # by individual nodes in the protocol. Actions in Ivy can have preconditions,
  16. # and a valid execution is a sequence of actions whose preconditions are all
  17. # satisfied in the state in which they are called.
  18. # For technical reasons, we define below a `tendermint` module instead of an
  19. # object. Ivy modules are a little bit like classes in OO programs, and like
  20. # classes they can be instantiated to obtain objects. To instantiate the
  21. # `tendermint` module, we must provide an abstract-protocol object. This allows
  22. # us to use different abstract-protocol objects for different parts of the
  23. # proof, and to do so without too much notational burden (we could have used
  24. # Ivy monitors, but then we would need to prefix every variable name by the
  25. # name of the object containing it, which clutters things a bit compared to the
  26. # approach we took).
  27. # The abstract-protocol object is called by the resulting tendermint object so
  28. # as to run the abstract protocol alongside the concrete protocol. This allows
  29. # us to transfer properties proved of the abstract protocol to the concrete
  30. # protocol, as follows. First, we prove that running the abstract protocol in
  31. # this way results in a valid execution of the abstract protocol. This is done
  32. # by checking that all preconditions of the abstract actions are satisfied at
  33. # their call sites. Second, we establish a relation between abstract state and
  34. # concrete state (in the form of invariants of the resulting, two-object
  35. # transition system) that allow us to transfer properties proved in the
  36. # abstract protocol to the concrete protocol (for example, we prove that any
  37. # decision made in the Tendermint protocol is also made in the abstract
  38. # protocol; if the abstract protocol satisfies the agreement property, this
  39. # allows us to conclude that the Tendermint protocol also does).
  40. # The abstract protocol object that we will use is always the same, and only
  41. # the abstract properties that we prove about it change in the different
  42. # instantiations of the `tendermint` module. Thus we provide common invariants
  43. # that a) allow to prove that the abstract preconditions are met, and b)
  44. # provide a refinement relation (see end of the module) relating the state of
  45. # Tendermint to the state of the abstract protocol.
  46. # In the model, Byzantine nodes can send whatever messages they want, except
  47. # that they cannot forge sender identities. This reflects the fact that, in
  48. # practice, nodes use public key cryptography to sign their messages.
  49. # Finally, note that the observations that serve to adjudicate misbehavior are
  50. # defined only in the abstract protocol (they happen in the abstract actions).
  51. module tendermint(abstract_protocol) = {
  52. # the initial value of a node:
  53. function init_val(N:node): value
  54. # the three type of steps
  55. object step_t = {
  56. type this = {propose, prevote, precommit}
  57. } # refer to those e.g. as step_t.propose
  58. object server(n:node) = {
  59. # the current round of a node
  60. individual round_p: round
  61. individual step: step_t
  62. individual decision: value
  63. individual lockedValue: value
  64. individual lockedRound: round
  65. individual validValue: value
  66. individual validRound: round
  67. relation done_l34(R:round)
  68. relation done_l36(R:round, V:value)
  69. relation done_l47(R:round)
  70. # variables for scheduling request
  71. relation propose_timer_scheduled(R:round)
  72. relation prevote_timer_scheduled(R:round)
  73. relation precommit_timer_scheduled(R:round)
  74. relation _recved_proposal(Sender:node, R:round, V:value, VR:round)
  75. relation _recved_prevote(Sender:node, R:round, V:value)
  76. relation _recved_precommit(Sender:node, R:round, V:value)
  77. relation _has_started
  78. after init {
  79. round_p := 0;
  80. step := step_t.propose;
  81. decision := value.nil;
  82. lockedValue := value.nil;
  83. lockedRound := round.minus_one;
  84. validValue := value.nil;
  85. validRound := round.minus_one;
  86. done_l34(R) := false;
  87. done_l36(R, V) := false;
  88. done_l47(R) := false;
  89. propose_timer_scheduled(R) := false;
  90. prevote_timer_scheduled(R) := false;
  91. precommit_timer_scheduled(R) := false;
  92. _recved_proposal(Sender, R, V, VR) := false;
  93. _recved_prevote(Sender, R, V) := false;
  94. _recved_precommit(Sender, R, V) := false;
  95. _has_started := false;
  96. }
  97. action getValue returns (v:value) = {
  98. v := init_val(n)
  99. }
  100. export action start = {
  101. require ~_has_started;
  102. _has_started := true;
  103. # line 10
  104. call startRound(0);
  105. }
  106. # line 11-21
  107. action startRound(r:round) = {
  108. # line 12
  109. round_p := r;
  110. # line 13
  111. step := step_t.propose;
  112. var proposal : value;
  113. # line 14
  114. if (proposers.get_proposer(r) = n) {
  115. if validValue ~= value.nil { # line 15
  116. proposal := validValue; # line 16
  117. } else {
  118. proposal := getValue(); # line 18
  119. };
  120. call broadcast_proposal(r, proposal, validRound); # line 19
  121. } else {
  122. propose_timer_scheduled(r) := true; # line 21
  123. };
  124. call abstract_protocol.l_11(n, r);
  125. }
  126. # This action, as not exported, can only be called at specific call sites.
  127. action broadcast_proposal(r:round, v:value, vr:round) = {
  128. var m: msg;
  129. m.m_kind := msg_kind.proposal;
  130. m.m_src := n;
  131. m.m_round := r;
  132. m.m_value := v;
  133. m.m_vround := vr;
  134. call shim.broadcast(n,m);
  135. }
  136. implement shim.proposal_handler.handle(msg:msg) {
  137. _recved_proposal(msg.m_src, msg.m_round, msg.m_value, msg.m_vround) := true;
  138. }
  139. # line 22-27
  140. export action l_22(v:value) = {
  141. require _has_started;
  142. require _recved_proposal(proposers.get_proposer(round_p), round_p, v, round.minus_one);
  143. require step = step_t.propose;
  144. if (value.valid(v) & (lockedRound = round.minus_one | lockedValue = v)) {
  145. call broadcast_prevote(round_p, v); # line 24
  146. call abstract_protocol.l_22(n, round_p, v);
  147. } else {
  148. call broadcast_prevote(round_p, value.nil); # line 26
  149. call abstract_protocol.l_22(n, round_p, value.nil);
  150. };
  151. # line 27
  152. step := step_t.prevote;
  153. }
  154. # line 28-33
  155. export action l_28(r:round, v:value, vr:round, q:nset) = {
  156. require _has_started;
  157. require r = round_p;
  158. require _recved_proposal(proposers.get_proposer(r), r, v, vr);
  159. require nset.is_quorum(q);
  160. require nset.member(N,q) -> _recved_prevote(N,vr,v);
  161. require step = step_t.propose;
  162. require vr >= 0 & vr < r;
  163. # line 29
  164. if (value.valid(v) & (lockedRound <= vr | lockedValue = v)) {
  165. call broadcast_prevote(r, v);
  166. } else {
  167. call broadcast_prevote(r, value.nil);
  168. };
  169. call abstract_protocol.l_28(n,r,v,vr,q);
  170. step := step_t.prevote;
  171. }
  172. action broadcast_prevote(r:round, v:value) = {
  173. var m: msg;
  174. m.m_kind := msg_kind.prevote;
  175. m.m_src := n;
  176. m.m_round := r;
  177. m.m_value := v;
  178. call shim.broadcast(n,m);
  179. }
  180. implement shim.prevote_handler.handle(msg:msg) {
  181. _recved_prevote(msg.m_src, msg.m_round, msg.m_value) := true;
  182. }
  183. # line 34-35
  184. export action l_34(r:round, q:nset) = {
  185. require _has_started;
  186. require round_p = r;
  187. require nset.is_quorum(q);
  188. require exists V . nset.member(N,q) -> _recved_prevote(N,r,V);
  189. require step = step_t.prevote;
  190. require ~done_l34(r);
  191. done_l34(r) := true;
  192. prevote_timer_scheduled(r) := true;
  193. }
  194. # line 36-43
  195. export action l_36(r:round, v:value, q:nset) = {
  196. require _has_started;
  197. require r = round_p;
  198. require exists VR . round.minus_one <= VR & VR < r & _recved_proposal(proposers.get_proposer(r), r, v, VR);
  199. require nset.is_quorum(q);
  200. require nset.member(N,q) -> _recved_prevote(N,r,v);
  201. require value.valid(v);
  202. require step = step_t.prevote | step = step_t.precommit;
  203. require ~done_l36(r,v);
  204. done_l36(r, v) := true;
  205. if step = step_t.prevote {
  206. lockedValue := v; # line 38
  207. lockedRound := r; # line 39
  208. call broadcast_precommit(r, v); # line 40
  209. step := step_t.precommit; # line 41
  210. call abstract_protocol.l_36(n, r, v, q);
  211. };
  212. validValue := v; # line 42
  213. validRound := r; # line 43
  214. }
  215. # line 44-46
  216. export action l_44(r:round, q:nset) = {
  217. require _has_started;
  218. require r = round_p;
  219. require nset.is_quorum(q);
  220. require nset.member(N,q) -> _recved_prevote(N,r,value.nil);
  221. require step = step_t.prevote;
  222. call broadcast_precommit(r, value.nil); # line 45
  223. step := step_t.precommit; # line 46
  224. call abstract_protocol.l_44(n, r, q);
  225. }
  226. action broadcast_precommit(r:round, v:value) = {
  227. var m: msg;
  228. m.m_kind := msg_kind.precommit;
  229. m.m_src := n;
  230. m.m_round := r;
  231. m.m_value := v;
  232. call shim.broadcast(n,m);
  233. }
  234. implement shim.precommit_handler.handle(msg:msg) {
  235. _recved_precommit(msg.m_src, msg.m_round, msg.m_value) := true;
  236. }
  237. # line 47-48
  238. export action l_47(r:round, q:nset) = {
  239. require _has_started;
  240. require round_p = r;
  241. require nset.is_quorum(q);
  242. require nset.member(N,q) -> exists V . _recved_precommit(N,r,V);
  243. require ~done_l47(r);
  244. done_l47(r) := true;
  245. precommit_timer_scheduled(r) := true;
  246. }
  247. # line 49-54
  248. export action l_49_decide(r:round, v:value, q:nset) = {
  249. require _has_started;
  250. require exists VR . round.minus_one <= VR & VR < r & _recved_proposal(proposers.get_proposer(r), r, v, VR);
  251. require nset.is_quorum(q);
  252. require nset.member(N,q) -> _recved_precommit(N,r,v);
  253. require decision = value.nil;
  254. if value.valid(v) {
  255. decision := v;
  256. # MORE for next height
  257. call abstract_protocol.decide(n, r, v, q);
  258. }
  259. }
  260. # line 55-56
  261. export action l_55(r:round, b:nset) = {
  262. require _has_started;
  263. require nset.is_blocking(b);
  264. require nset.member(N,b) -> exists VR . round.minus_one <= VR & VR < r & exists V . _recved_proposal(N,r,V,VR) | _recved_prevote(N,r,V) | _recved_precommit(N,r,V);
  265. require r > round_p;
  266. call startRound(r); # line 56
  267. }
  268. # line 57-60
  269. export action onTimeoutPropose(r:round) = {
  270. require _has_started;
  271. require propose_timer_scheduled(r);
  272. require r = round_p;
  273. require step = step_t.propose;
  274. call broadcast_prevote(r,value.nil);
  275. step := step_t.prevote;
  276. call abstract_protocol.l_57(n,r);
  277. propose_timer_scheduled(r) := false;
  278. }
  279. # line 61-64
  280. export action onTimeoutPrevote(r:round) = {
  281. require _has_started;
  282. require prevote_timer_scheduled(r);
  283. require r = round_p;
  284. require step = step_t.prevote;
  285. call broadcast_precommit(r,value.nil);
  286. step := step_t.precommit;
  287. call abstract_protocol.l_61(n,r);
  288. prevote_timer_scheduled(r) := false;
  289. }
  290. # line 65-67
  291. export action onTimeoutPrecommit(r:round) = {
  292. require _has_started;
  293. require precommit_timer_scheduled(r);
  294. require r = round_p;
  295. call startRound(round.incr(r));
  296. precommit_timer_scheduled(r) := false;
  297. }
  298. # The Byzantine actions
  299. # ---------------------
  300. # Byzantine nodes can send whatever they want, but they cannot send
  301. # messages on behalf of well-behaved nodes. In practice this is implemented
  302. # using cryptography (e.g. public-key cryptography).
  303. export action byzantine_send(m:msg, dst:node) = {
  304. require ~well_behaved(n);
  305. require ~well_behaved(m.m_src); # cannot forge the identity of well-behaved nodes
  306. call shim.send(n,dst,m);
  307. }
  308. # Byzantine nodes can also report fake observations, as defined in the abstract protocol.
  309. export action fake_observations = {
  310. call abstract_protocol.misbehave
  311. }
  312. # Invariants
  313. # ----------
  314. # We provide common invariants that a) allow to prove that the abstract
  315. # preconditions are met, and b) provide a refinement relation.
  316. specification {
  317. invariant 0 <= round_p
  318. invariant abstract_protocol.left_round(n,R) <-> R < round_p
  319. invariant lockedRound ~= round.minus_one -> forall R,V . abstract_protocol.locked(n,R,V) <-> R <= lockedRound & lockedValue = V
  320. invariant lockedRound = round.minus_one -> forall R,V . ~abstract_protocol.locked(n,R,V)
  321. invariant forall M:msg . well_behaved(M.m_src) & M.m_kind = msg_kind.prevote & shim.sent(M,N) -> abstract_protocol.prevoted(M.m_src,M.m_round,M.m_value)
  322. invariant well_behaved(N) & _recved_prevote(N,R,V) -> abstract_protocol.prevoted(N,R,V)
  323. invariant forall M:msg . well_behaved(M.m_src) & M.m_kind = msg_kind.precommit & shim.sent(M,N) -> abstract_protocol.precommitted(M.m_src,M.m_round,M.m_value)
  324. invariant well_behaved(N) & _recved_precommit(N,R,V) -> abstract_protocol.precommitted(N,R,V)
  325. invariant (step = step_t.prevote | step = step_t.propose) -> ~abstract_protocol.precommitted(n,round_p,V)
  326. invariant step = step_t.propose -> ~abstract_protocol.prevoted(n,round_p,V)
  327. invariant step = step_t.prevote -> exists V . abstract_protocol.prevoted(n,round_p,V)
  328. invariant round_p < R -> ~(abstract_protocol.prevoted(n,R,V) | abstract_protocol.precommitted(n,R,V))
  329. invariant ~_has_started -> step = step_t.propose & ~(abstract_protocol.prevoted(n,R,V) | abstract_protocol.precommitted(n,R,V)) & round_p = 0
  330. invariant decision ~= value.nil -> exists R . abstract_protocol.decided(n,R,decision)
  331. }
  332. }
  333. }