#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.
|