Toy lock udp
An Ivy model of the toy lock example from https://github.com/Microsoft/Ironclad/blob/master/ironfleet/src/Dafny/Distributed/Protocol/Lock/Node.i.dfy
A total order helper module
module total_order(t,r) = {
axiom r(X:t,X) # Reflexivity
axiom r(X:t, Y) & r(Y, Z) -> r(X, Z) # Transitivity
axiom r(X:t, Y) & r(Y, X) -> X = Y # Anti-symmetry
axiom r(X:t, Y) | r(Y, X) # Totality
}
¤
Types, relations and functions describing state of the network
¤
type tag
type node
type epoch
type packet
individual xfer:tag, ack:tag
axiom xfer ~= ack
relation (X:epoch <= Y:epoch)
instantiate total_order(epoch, <=)
module counter(t) = {
action incr(input:t) returns (output:t) = {
assume input < 0xff;
output := input + 1
}
}
module abs_counter(t,c) = {
action incr(input:t) returns (output:t) = {
assert ~(output <= input)
}
mixin incr after c.incr
}
module topology = {
action next(n:node) returns (succ:node) = {
succ := n + 1
}
action prev(n:node) returns (pred:node) = {
pred := n - 1
}
}
derived succ(X:node,Y:node) = (Y = X + 1)
module abs_topology(top) = {
action next(n:node) returns (s:node) = {
assert succ(n,s) & (succ(n,X) -> s = X)
}
mixin next after top.next
}
module formatter = {
individual conc(T:tag,E:epoch) : packet
action mk_transfer(e:epoch) returns (p:packet) = {
p := conc(xfer,e)
}
action mk_ack(e:epoch) returns (p:packet) = {
p := conc(ack,e)
}
interpret conc -> concat
}
individual get_tg(P:packet) : tag # the packet's tag
individual get_ep(P:packet) : epoch # the packet's epoch
interpret get_ep -> bfe[7][0]
interpret get_tg -> bfe[9][8]
module abs_formatter(low) = {
Make a packet. Right now there are only transfer packets so there is no tag needed.
action mk_transfer(e:epoch) returns (p:packet) = {
assert get_tg(p) = xfer & get_ep(p) = e
}
mixin mk_transfer after low.mk_transfer
action mk_ack(e:epoch) returns (p:packet) = {
assert get_tg(p) = ack & get_ep(p) = e
}
mixin mk_ack after low.mk_ack
}
module abs_low_intf(fmt) = {
relation sent(D:node, P:packet) # the set of sent packets
init ~sent(D,P) # initially, no packets sent
action send(dst:node,p:packet) = {
sent(dst,p) := true
}
action recv(dst:node) returns(p:packet) = {
assert sent(dst,p)
}
}
module net_intf(lower,fmt,pro,top) = {
This is a concrete implementation that sends and receives formatted packets via a lower-level service
The parameter "lower" is the lower-level interface and "fmt" is the packet formatter.
This represents a packet that has been sent but not acked. This could be replaced by a finite buffer.
relation pend(S:node) # a packet sent by S is pending
individual pend_pkt(S:node):packet # the pending packet
init ~pend(S)
action send_transfer(src:node, e:epoch, dst:node) = {
local p : packet {
p := fmt.mk_transfer(e);
call lower.send(dst, p);
pend(src) := true;
pend_pkt(src) := p
}
}
action incoming(n:node) = {
local p:packet, e:epoch, s:node {
p := lower.recv(n);
e := get_ep(p);
if get_tg(p) = xfer {
call pro.accept(n,e);
local a:packet, s:node {
a := fmt.mk_ack(e);
s := top.prev(n);
call lower.send(s,a)
}
}
else {
if get_ep(p) = get_ep(pend_pkt(n)) {
pend(n) := false
}
}
}
}
action timeout(n:node) = {
if pend(n) {
local dst:node {
dst := top.next(n);
call lower.send(dst,pend_pkt(n))
}
}
}
conjecture l.sent(D,P) & get_tg(P) = xfer -> an.transfer(get_ep(P),D)
conjecture pend(S) & P = pend_pkt(S) & succ(S,D) -> get_tg(P) = xfer & an.transfer(get_ep(P),D)
}
module abs_net_intf(net,pro,top) = {
Notice that this module takes two parameters: net is the transport layer and pro is the application layer. It specifies both the calls from pro to net and the calls from net to pro.
state of abstract network interface
relation transfer(E:epoch, N:node) # the node is the message destination
init ~transfer(E, N)
action send_transfer_pre(src:node, e:epoch, dst:node) = {
local n2:node {
n2 := top.next(src);
assert dst = n2
}
}
mixin send_transfer_pre before net.send_transfer
action send_transfer(src:node, e:epoch, dst:node) = {
transfer(e,dst) := true
}
mixin send_transfer after net.send_transfer
action accept(dst:node,e:epoch) = {
assert transfer(e,dst)
}
mixin accept before pro.accept
}
module proto(cnt,top,net) = {
ep(n) is the current epoch of node n
individual ep(N:node) : epoch
relation held(N:node)
individual first:node
init held(X) <-> X=first
init N ~= first -> ep(N) = 0
init ~(ep(first) <= 0)
relation locked(E:epoch, N:node) # the node is the message source
init ~locked(E, N)
¤
Protocol description
¤
release the lock and send a transfer message
action grant(n1:node) = {
local e:epoch, n2:node {
assume held(n1);
e := cnt.incr(ep(n1));
n2 := top.next(n1);
call net.send_transfer(n1, e, n2);
held(n1) := false
}
}
action accept(n:node,e:epoch) = {
if ~(e <= ep(n)) {
held(n) := true;
ep(n) := e;
locked(e, n) := true
}
}
action critical(n:node) = {
assume held(n);
assert X ~= n -> ~held(X)
}
¤
invariant conjectures
no two locked at same epoch (as a sanity check) conjecture locked(E, N1) & locked(E, N2) -> N1 = N2
epochs transfer to at most one node
conjecture an.transfer(E, N1) & an.transfer(E, N2) -> N1 = N2
holding node's epoch is higher than any other node's epoch (this implies a single node holds the lock)
conjecture held(N) & N ~= M -> ~(ep(N) <= ep(M))
conjecture held(N) & an.transfer(E, M) -> (E <= ep(N))
conjecture an.transfer(E, N) & ~(E <= ep(N)) -> ~(E <= ep(M))
conjecture an.transfer(E, N) & ~(E <= ep(N)) & an.transfer(F, M) -> (F <= E)
}
Notice that the formatter and low interface are currently abstract specifications that could be refined in the future.
instantiate c : counter(epoch)
instantiate t : topology
instantiate f : formatter()
instantiate l : abs_low_intf(f)
instantiate n : net_intf(l,f,p,t)
instantiate p : proto(c,t,n)
interpret epoch -> bv[8]
interpret node -> bv[2]
interpret packet -> bv[10]
interpret tag -> bv[2]
export p.grant
export n.incoming
export n.timeout
export p.critical
¤
All after this is proof
Overlay the abstract counter specification
instantiate an : abs_net_intf(n,p,t)
instantiate ac : abs_counter(epoch,c)
instantiate af : abs_formatter(f)
instantiate at : abs_topology(t)
isolate iso_p = p with an,ac,at
isolate iso_c = c with ac,epoch
isolate iso_n = n with an, l, af, at
isolate iso_f = f with af, node, epoch, packet, tag, get_ep, get_tg
isolate iso_t = t with at, node, succ
isolate iso_code = p with c, n, f, t, node, epoch, packet, tag