This file contains a first attempt at refining the network model to¤
a sort of toy TCP.¤
Two conjectures were added about the messages in¤
the lower (UDP) network layer (that is, they must obey the same¤
invariants are the message in the transport layer messages queues.¤
A bug was observed in the lower network layer specification, which¤
did not promise to deliver a message only to its destination.¤
Forgot to include seq_nums object in iso_n¤
include std
¤
This is the abstract specification of the sharded hash table protocol. It defines the consistency reference model (reference)
¤
¤
Global type declarations
¤
¤
type of local time
type ltime
instantiate total_order(ltime)
¤
type of hash table data
type data
¤
type of hash table keys
type key
instantiate total_non_strict_order(key)
object keys = {
action max returns (x:key) = {
assume X <= x
}
action next(x:key) returns (y:key) = {
assert exists Z. ~(Z <= x);
assume x <= y & x ~= y & (X <= y & X ~= y -> X <= x)
}
delegate next
}
axiom 0 <= K:key
¤
type of message types
type mtype = {request_t, reply_t, delegate_t, ack_t}
¤
type of hash table ops
type otype = {read, write}
¤
id's of protocol agents
type id
¤
Shards contain a subset of a map from a low to a hi key
type shard
function shard_lo(S:shard) : key
function shard_hi(S:shard) : key
function shard_value(S:shard,K:key) : data
¤
A request message has a source, key, op data and ltime
type req
function req_src(R:req) : id
function req_key(R:req) : key
function req_otype(R:req) : otype
function req_data(R:req) : data
function req_ltime(R:req) : ltime
axiom
req_src(R) = req_src(S) &
req_key(R) = req_key(S) &
req_otype(R) = req_otype(S) &
req_data(R) = req_data(S) &
req_ltime(R) = req_ltime(S) &
req_src(R) = req_src(S)
-> R = S
object reqs = {
action make(src:id,k:key,op:otype,d:data,lt:ltime) returns(res:req) = {
assume req_src(R) = src &
req_key(R) = k &
req_otype(R) = op &
req_data(R) = d &
req_ltime(R) = lt
}
}
¤
structure of hash table events to serialize
module hashev = {
individual type_ : otype
individual key_ : key
individual data_ : data # data for write and cas
individual id_ : id # process id of op
relation serialized # this event has happened
init ~serialized
}
¤
Reference specification
This module describes a set of memory events ordered by local time. It provides actions that assign global time to events in increasing order. The pre-conditions of these actions enforce the consistency model (that is, what orderings in local time must be preserved in global time).
¤
object reference = {
¤
memory events by local time
instantiate evs(T:ltime) : hashev
¤
global memory state obeys partial function axiom
individual hash(A:key) : data
init hash(A) = 0
¤
serialize an event lt at current global time. The "id" parameter tells us what process is serializing the event.
action serialize(lt:ltime, id_:id) = {
assert ~evs(lt).serialized;
evs(lt).serialized := true;
local a : key, d : data {
a := evs(lt).key_;
d := evs(lt).data_;
if evs(lt).type_ = read {
evs(lt).data_ := hash(a)
}
else { # write
hash(a) := d
}
}
}
delegate serialize
¤
Note: if we want linearizability, we can add begin and end transaction events to this interface
}
¤
Specification of hash tables
module hash_top(key,value) = {
function hash(X:key) : data
init hash(X) = 0
action set_(k:key, v:value) = {
hash(k) := v
}
action get(k:key) returns (v:value) = {
assume v = hash(k)
}
action extract_(lo:key,hi:key) returns(res:shard) = {
assume shard_lo(res) = lo;
assume shard_hi(res) = hi;
assume (lo <= X & X <= hi) -> shard_value(res,X) = hash(X)
}
action incorporate(s:shard) = {
hash(K) := shard_value(s,K) if (shard_lo(s) <= K & K <= shard_hi(s)) else hash(K)
}
}
¤
Specification of delegation map
module delegation_map_spec = {
relation map(K:key, X:id)
init map(K,X) <-> X = 0
action set_(lo:key,hi:key,dst:id) = {
map(K,X) := (X = dst) & (lo <= K & K <= hi)
| map(K,X) & ~(lo <= K & K <= hi)
}
action get(k:key) returns (val:id) = {
assert map(k,val) # assume not assign because this code is ghost
}
conjecture map(K,V) & map(K,W) -> V = W
}
¤
Implementation of delegation map
module delegation_map() = {
function entries(K:key) :id
init entries(0) = 0
instance has : ordered_set(key)
action set_(lo:key,hi:key,dst:id) = {
if lo <= hi {
if hi ~= keys.max {
local nkey:key, nid:id {
nkey := keys.next(hi);
nid := entries(has.get_glb(nkey));
entries(nkey) := nid;
call has.insert(nkey)
}
};
call has.erase(lo,hi);
entries(lo) := dst;
call has.insert(lo)
}
}
action get(k:key) returns (val:id) = {
val := entries(has.get_glb(k))
}
conjecture has.s(K) & has.succ.map(K,L) & K <= M & (L = 0 | ~(L <= M)) -> dma.map(me,M,entries(K))
conjecture has.s(0)
}
¤
This is the top-level server description
"ref" is the reference object "me" is the server's id net is the network
module server_top(ref,me,neti) = {
instance hash : hash_top(key,data)
instance dm : delegation_map
private dm
action handle_request(rq:req) = {
local src:id, k:key, op:otype, d:data, lt:ltime, ow:id {
src := req_src(rq);
k := req_key(rq);
op := req_otype(rq);
d := req_data(rq);
lt := req_ltime(rq);
ow := dm.get(k);
if ow = me {
call ref.serialize(lt,me); # this is ghost!
if op = read {
d := hash.get(k)
}
else {
call hash.set_(k,d)
};
call neti.send_reply(me, src, d, lt)
} else {
call neti.send_request(me, ow, rq) # forward request
}
}
}
action generate_request(rq:req) = {
call handle_request(rq)
}
action recv_request(rq:req) = {
call handle_request(rq)
}
action shard(dst:id, lo:key, hi:key) = {
assume dst ~= me;
assume lo <= K & K <= hi -> dm.get(K:key) = me;
call dm.set_(lo,hi,dst);
call neti.send_delegate(me,dst,hash.extract_(lo,hi))
}
action recv_delegate(s:shard) = {
call dm.set_(shard_lo(s),shard_hi(s),me);
call hash.incorporate(s)
}
conjecture dma(me).map(K,me) -> hash.hash(K) = ref.hash(K)
conjecture dma(me).map(K,me) & X ~= me -> ~dma(X).map(K,X)
conjecture dma(me).map(K,me) -> ~(net.delegated(X,S) & shard_lo(S) <= K & K <= shard_hi(S))
conjecture net.delegated(X,S) & shard_lo(S) <= K & K <= shard_hi(S)
& net.delegated(X1,S1) & shard_lo(S1) <= K & K <= shard_hi(S1)
-> X = X1 & S = S1
conjecture net.requested(D,R) & L = req_ltime(R)->
(req_key(R) = ref.evs(L).key_ &
req_otype(R) = ref.evs(L).type_ &
(req_otype(R) = write -> req_data(R) = ref.evs(L).data_))
conjecture net.requested(D,R) -> io_spec.generated(req_ltime(R)) & ~ref.evs(req_ltime(R)).serialized
conjecture net.requested(D1,R1) & net.requested(D2,R2) & req_ltime(R1) = req_ltime(R2)
-> D1 = D2 & R1 = R2
conjecture net.delegated(X,S) & shard_lo(S) <= K & K <= shard_hi(S) -> shard_value(S,K) = ref.hash(K)
conjecture ref.evs(T).serialized -> io_spec.generated(T)
}
¤
Specification of network top layer
Currently, delivery is out-of-order non-duplicating
module net_top(ref,proto,neti) = {
relation requested(D:id,R:req)
relation delegated(D:id,S:shard)
init ~requested(D,R)
init ~delegated(D,S)
action send_request(src:id,dst:id,rq:req) = {
assert ~requested(dst,rq);
requested(dst,rq) := true
}
execute send_request before neti.send_request
action send_delegate(src:id,dst:id,s:shard) = {
assert ~delegated(dst,s);
delegated(dst,s) := true
}
execute send_delegate before neti.send_delegate
action recv_request(dst:id,rq:req) = {
assert requested(dst,rq);
requested(dst,rq) := false
}
execute recv_request before proto.recv_request
action recv_delegate(dst:id,s:shard) = {
assume delegated(dst,s);
delegated(dst,s) := false
}
execute recv_delegate before proto.recv_delegate
}
type seq_num
instantiate total_non_strict_order(seq_num)
object seq_nums = {
action next(seq:seq_num) returns (res:seq_num) = {
assume seq <= res & seq ~= res & (X <= res & X ~= res -> X <= seq)
}
}
type net_msg
function net_msg_mtype(M:net_msg) : mtype
function net_msg_src(M:net_msg) : id
function net_msg_dst(M:net_msg) : id
function net_msg_req(M:net_msg) : req
function net_msg_seq_num(M:net_msg) : seq_num
function net_msg_shard(M:net_msg) : shard
object encoder = {
action mk_req(src:id,dst:id,rq:req,seq:seq_num) returns (msg:net_msg) = {
assume net_msg_src(msg) = src
& net_msg_dst(msg) = dst
& net_msg_req(msg) = rq
& net_msg_seq_num(msg) = seq
& net_msg_mtype(msg) = request_t
}
action mk_delegate(src:id,dst:id,sh:shard,seq:seq_num) returns (msg:net_msg) = {
assume net_msg_src(msg) = src
& net_msg_dst(msg) = dst
& net_msg_shard(msg) = sh
& net_msg_seq_num(msg) = seq
& net_msg_mtype(msg) = delegate_t
}
action mk_ack(src:id,dst:id,seq:seq_num) returns (msg:net_msg) = {
assume net_msg_src(msg) = src
& net_msg_dst(msg) = dst
& net_msg_seq_num(msg) = seq
& net_msg_mtype(msg) = ack_t
}
}
module message_queue_top(me) = {
relation contents(M:net_msg)
init ~contents(M)
action enqueue(msg:net_msg) = {
assert ~contents(msg);
contents(msg) := true
}
action empty returns (res:bool) = {
assume contents(M) -> ~res;
assume ~res -> exists M. contents(M)
}
action pick_one returns (res:net_msg) = {
assume ~contents(res) -> ~contents(M)
}
action delete_all(seq:seq_num) = {
contents(M) := contents(M) & ~(net_msg_seq_num(M) <= seq)
}
}
module net_impl(ref,proto,me,lower) = {
instance mq(D:id) : message_queue_top(D)
individual send_seq(S:id) : seq_num
individual recv_seq(S:id) : seq_num
init recv_seq(S) = 0 & send_seq(S) = 0
action send_request(dst:id,rq:req) = {
local msg : net_msg, seq : seq_num {
msg := encoder.mk_req(me,dst,rq,send_seq(dst));
send_seq(dst) := seq_nums.next(send_seq(dst));
call mq(dst).enqueue(msg);
call lower.send_msg(msg)
}
}
action send_delegate(dst:id,sh:shard) = {
local msg : net_msg, seq : seq_num {
msg := encoder.mk_delegate(me,dst,sh,send_seq(dst));
send_seq(dst) := seq_nums.next(send_seq(dst));
call mq(dst).enqueue(msg);
call lower.send_msg(msg)
}
}
action recv_msg(msg:net_msg) = {
local src:id,seq:seq_num {
seq := net_msg_seq_num(msg);
src := net_msg_src(msg);
if seq <= recv_seq(src) & net_msg_mtype(msg) ~= ack_t {
call lower.send_msg(encoder.mk_ack(me,src,seq))
};
if seq = recv_seq(src) {
recv_seq(src) := seq_nums.next(recv_seq(src));
if net_msg_mtype(msg) = request_t {
call proto.recv_request(me,net_msg_req(msg))
}
else if net_msg_mtype(msg) = delegate_t {
call proto.recv_delegate(me,net_msg_shard(msg))
}
else if net_msg_mtype(msg) = ack_t {
call mq(src).delete_all(seq)
}
}
}
}
action timeout = {
if exists D:id,M:net_msg. mq(D).contents(M) {
local dst:id, msg:net_msg, b:bool {
b := mq(dst).empty;
assume ~b;
msg := mq(dst).pick_one;
call lower.send_msg(msg)
}
}
}
action send_reply(dst:id, d:data, lt:ltime) = {
}
= D's receive sequence number, then the message is pending.
conjecture mq(D).contents(M) & neti(D).recv_seq(me) <= net_msg_seq_num(M)
& net_msg_mtype(M) = request_t -> net.requested(D,net_msg_req(M))
= D's receive sequence number, then the message is pending.
conjecture mq(D).contents(M) & neti(D).recv_seq(me) <= net_msg_seq_num(M)
& net_msg_mtype(M) = delegate_t -> net.delegated(D,net_msg_shard(M))
conjecture neti(S1).mq(D).contents(M1) & neti(D).recv_seq(S1) <= net_msg_seq_num(M1)
& neti(S2).mq(D).contents(M2) & neti(D).recv_seq(S2) <= net_msg_seq_num(M2)
& net_msg_mtype(M1) = request_t & net_msg_mtype(M2) = request_t
& (S1 ~= S2 | net_msg_seq_num(M1) ~= net_msg_seq_num(M2))
-> net_msg_req(M1) ~= net_msg_req(M2)
conjecture neti(S1).mq(D).contents(M1) & neti(D).recv_seq(S1) <= net_msg_seq_num(M1)
& neti(S2).mq(D).contents(M2) & neti(D).recv_seq(S2) <= net_msg_seq_num(M2)
& net_msg_mtype(M1) = delegate_t & net_msg_mtype(M2) = delegate_t
& (S1 ~= S2 | net_msg_seq_num(M1) ~= net_msg_seq_num(M2))
-> net_msg_shard(M1) ~= net_msg_shard(M2)
conjecture mq(D).contents(M) -> ~(send_seq(D) <= net_msg_seq_num(M))
conjecture mq(D).contents(M1) & mq(D).contents(M2) & M1 ~= M2
-> net_msg_seq_num(M1) ~= net_msg_seq_num(M2)
conjecture low.sent(M) & net_msg_src(M) = me & net_msg_dst(M) = D
& mq(D).contents(M2) & net_msg_seq_num(M2) = net_msg_seq_num(M)
& net_msg_mtype(M) ~= ack_t -> M = M2
A sent non-ack message with seq num >= receiver must be in the corresponding queue
conjecture low.sent(M) & net_msg_src(M) = S & net_msg_dst(M) = D
& neti(D).recv_seq(S) <= net_msg_seq_num(M) & net_msg_mtype(M) ~= ack_t
-> neti(S).mq(D).contents(M)
conjecture low.sent(M) & net_msg_src(M) = S & net_msg_dst(M) = D
& net_msg_mtype(M) = ack_t -> ~(neti(S).recv_seq(D) <= net_msg_seq_num(M))
conjecture low.sent(M) & net_msg_src(M) = me & net_msg_mtype(M) ~= ack_t
-> ~(send_seq(net_msg_dst(M)) <= net_msg_seq_num(M))
conjecture mq(D).contents(M) -> net_msg_src(M) = me & net_msg_dst(M) = D & net_msg_mtype(M) ~= ack_t
}
module lower(net_impl) = {
relation sent(M:net_msg)
init ~sent(M)
action send_msg(msg:net_msg) = {
sent(msg) := true
}
action recv_msg(dst:id, msg:net_msg) = {
added fact that only deliver to destination¤
assert sent(msg) & dst = net_msg_dst(msg)
}
execute recv_msg before net_impl.recv_msg
}
¤
instantiate some components
make one server for each id
instance servers(X:id) : server_top(reference,X,neti)
instance net : net_top(reference,servers,neti)
instance dma(X:id) : delegation_map_spec
execute dma.set_ before servers.dm.set_
execute dma.get after servers.dm.get
instance neti(X:id) : net_impl(reference,servers,X,low)
instance low : lower(neti)
input/output specification¤
object io_spec = {
relation generated(L:ltime)
init ~generated(L)
action generate_request(dst:id,rq:req) = {
assert ~generated(req_ltime(rq));
assert L = req_ltime(rq)->
(req_key(rq) = reference.evs(L).key_ &
req_otype(rq) = reference.evs(L).type_ &
(req_otype(rq) = write -> req_data(rq) = reference.evs(L).data_));
generated(req_ltime(rq)) := true
}
execute generate_request before servers.generate_request
action send_reply(src:id, dst:id, d:data, lt:ltime) = {
assert reference.evs(lt).data_ = d # for now, just assert data values are correct
}
execute send_reply before neti.send_reply
}
¤
export some stuff
export servers.generate_request
export servers.shard
export neti.recv_msg
export neti.timeout
¤
verify the servers
This says verify all the actions of "servers", keeping "ref" and "net" concrete.
isolate iso_s = servers with reference,net,dma,io_spec
isolate iso_n = neti with net,low,seq_nums,encoder
isolate iso_d = servers(me).dm with dma(me),keys