#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: trusted isolate net = { # `trusted` indicates to Ivy that this is a trusted object, i.e. we assume # without proof that receive is only called when its requirement is true. 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. specification { # 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 } } # In contrast to the network object, the shim is not a trusted component. # 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.proposal { 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.