- #lang ivy1.7
- # ---
- # layout: page
- # title: Network model and network shim
- # ---
-
- # Here we define a network module, which is our model of the network, and a
- # shim module that sits on top of the network and which, upon receiving a
- # message, calls the appropriate protocol handler.
-
- include domain_model
-
- # Here we define an enumeration type for identifying the 3 different types of
- # messages that nodes send.
- object msg_kind = { # TODO: merge with step_t
- type this = {proposal, prevote, precommit}
- }
-
- # Here we define the type of messages `msg`. Its members are structs with the fields described below.
- object msg = {
- type this = struct {
- m_kind : msg_kind,
- m_src : node,
- m_round : round,
- m_value : value,
- m_vround : round
- }
- }
-
- # This is our model of the network:
- isolate net = {
-
- export action recv(dst:node,v:msg)
- action send(src:node,dst:node,v:msg)
- # Note that the `recv` action is exported, meaning that it can be called
- # non-deterministically by the environment any time it is enabled. In other
- # words, a packet that is in flight can be received at any time. In this
- # sense, the network is fully asynchronous. Moreover, there is no
- # requirement that a given message will be received at all.
-
- # The state of the network consists of all the packets that have been
- # sent so far, along with their destination.
- relation sent(V:msg, N:node)
-
- after init {
- sent(V, N) := false
- }
-
- before send {
- sent(v,dst) := true
- }
-
- before recv {
- require sent(v,dst) # only sent messages can be received.
- }
- }
-
- # The network shim sits on top of the network and, upon receiving a message,
- # calls the appropriate protocol handler. It also exposes a `broadcast` action
- # that sends to all nodes.
-
- isolate shim = {
-
- # In order not repeat the same code for each handler, we use a handler
- # module parameterized by the type of message it will handle. Below we
- # instantiate this module for the 3 types of messages of Tendermint
- module handler(p_kind) = {
- action handle(dst:node,m:msg)
- object spec = {
- before handle {
- assert sent(m,dst) & m.m_kind = p_kind
- }
- }
- }
-
- instance proposal_handler : handler(msg_kind.proposal)
- instance prevote_handler : handler(msg_kind.prevote)
- instance precommit_handler : handler(msg_kind.precommit)
-
- relation sent(M:msg,N:node)
-
- action broadcast(src:node,m:msg)
- action send(src:node,dst:node,m:msg)
-
- specification {
- after init {
- sent(M,D) := false;
- }
- before broadcast {
- sent(m,D) := true
- }
- before send {
- sent(m,dst) := true
- }
- }
-
- # Here we give an implementation of it that satisfies its specification:
- implementation {
-
- implement net.recv(dst:node,m:msg) {
-
- if m.m_kind = msg_kind.proposal {
- call proposal_handler.handle(dst,m)
- }
- else if m.m_kind = msg_kind.prevote {
- call prevote_handler.handle(dst,m)
- }
- else if m.m_kind = msg_kind.precommit {
- call precommit_handler.handle(dst,m)
- }
- }
-
- implement broadcast { # broadcast sends to all nodes, including the sender.
- var iter := node.iter.create(0);
- while ~iter.is_end
- invariant net.sent(M,D) -> sent(M,D)
- {
- var n := iter.val;
- call net.send(src,n,m);
- iter := iter.next;
- }
- }
-
- implement send {
- call net.send(src,dst,m)
- }
-
- private {
- invariant net.sent(M,D) -> sent(M,D)
- }
- }
-
- } with net, node # to prove that the shim implementation satisfies the shim specification, we rely on the specification of net and node.
|