Alternating bit protocol
This file contains a liveness proof of the alternating bit protocol, and illustrates Ivy's liveness to safety tactic on an infinite-state system that requires both dynamic abstraction and temporal prophecy. The example is discussed in the following papers:
Oded Padon, Jochen Hoenicke, Giuliano Losa, Andreas Podelski, Mooly Sagiv, and Sharon Shoham: Reducing Liveness to Safety in First-Order Logic. POPL 2018: Article 26.
Oded Padon, Jochen Hoenicke, Kenneth L. McMillan, Andreas Podelski, Mooly Sagiv, Sharon Shoham: Temporal Prophecy for Proving Temporal Properties of Infinite-State Systems. FMCAD 2018: 1-11
To verify this file, run:
$ ivy_check alternating_bit_protocol.ivy
...
OK
¤
A basic version of alternating bit protocol with binary (1-bit) sequence number and FIFO channels.
¤
¤
Module for axiomatizing a totally ordered set with fixed order
The module includes an le relation, a minimal element (zero) and get_succ and get_pred actions.
In this module, the order is arbitrary and fixed.
¤
module total_order(carrier) = {
relation le(X:carrier,Y:carrier) # less than or equal
axiom le(X, X) # Reflexivity
axiom le(X, Y) & le(Y, Z) -> le(X, Z) # Transitivity
axiom le(X, Y) & le(Y, X) -> X = Y # Anti-symmetry
axiom le(X, Y) | le(Y, X) # Totality
individual zero:carrier
axiom le(zero, X)
action get_succ(x:carrier) returns (y:carrier) = {
assume le(x,y) & x ~= y & ((le(x, Z) & x ~= Z) -> le(y, Z));
}
action get_pred(y:carrier) returns (x:carrier) = {
require y ~= zero;
assume le(x,y) & x ~= y & ((le(x, Z) & x ~= Z) -> le(y, Z));
}
}
¤
Module for a fifo channel
¤
module fifo_channel(m_t) = {
relation le(X:m_t,Y:m_t) # partial order representing messages in
invariant le(X, Y) & le(Y, Z) -> le(X, Z) # Transitivity
invariant le(X, Y) & le(Y, X) -> X = Y # Anti-symmetry
invariant le(X, Y) -> le(X, X) & le(Y, Y) # Partial reflexivity
invariant le(X, X) & le(Y, Y) -> le(X, Y) | le(Y, X) # Partial Totality
after init {
le(X, Y) := false;
}
action send(m: m_t) = {
require ~le(m, m);
le(m, m) := true;
le(m, X) := le(X,X);
}
action receive returns (m: m_t) = {
assume le(m, m);
assume le(m,X) -> X = m;
le(X,m) := false;
}
action drop(m:m_t) = {
le(X,Y) := le(X, Y) & X ~= m & Y ~= m;
}
}
¤
The protocol itself, with tracking of events for expressing fairness assumptions.
¤
isolate abp = {
type index_t
instantiate index : total_order(index_t)
type value
individual bot:value # special bottom value
type data_msg_t
instantiate data_msg : fifo_channel(data_msg_t)
function d(D:data_msg_t): value # immutable
relation dbit(D:data_msg_t) # immutable
type ack_msg_t
instantiate ack_msg : fifo_channel(ack_msg_t)
relation abit(A:ack_msg_t) # immutable
action gen_data(v:value)
action sender_send_data(m:data_msg_t, a:ack_msg_t)
action sender_receive_ack(m:data_msg_t, a:ack_msg_t)
action receiver_receive_data(m:data_msg_t, a:ack_msg_t)
action receiver_send_ack(m:data_msg_t, a:ack_msg_t)
action data_msg_drop(m:data_msg_t, a:ack_msg_t)
action ack_msg_drop(m:data_msg_t, a:ack_msg_t)
specification {
individual sender_array(I:index_t) : value
individual receiver_array(I:index_t) : value
individual sender_i:index_t
individual sender_gen_i:index_t
individual receiver_i:index_t
relation sender_bit
relation receiver_bit
relation sender_scheduled
relation receiver_scheduled
relation data_sent
relation data_received
relation ack_sent
relation ack_received
after init {
sender_array(I) := bot;
receiver_array(I) := bot;
sender_i := index.zero;
sender_gen_i := index.zero;
receiver_i := index.zero;
sender_bit := false;
receiver_bit := false;
sender_scheduled := false;
receiver_scheduled := false;
data_sent := false;
data_received := false;
ack_sent := false;
ack_received := false;
}
¤
Protocol actions
¤
before gen_data {
require v ~= bot;
sender_array(sender_gen_i) := v;
sender_gen_i := index.get_succ(sender_gen_i);
}
before sender_send_data {
sender_scheduled := true;
sender_scheduled := false;
if (sender_array(sender_i) ~= bot) {
assume d(m) = sender_array(sender_i);
assume dbit(m) <-> sender_bit;
call data_msg.send(m);
data_sent := true;
data_sent := false;
};
}
before sender_receive_ack {
ack_received := true;
ack_received := false;
a := ack_msg.receive();
if abit(a) <-> sender_bit {
sender_bit := ~sender_bit;
sender_i := index.get_succ(sender_i)
};
}
before receiver_receive_data {
data_received := true;
data_received := false;
m := data_msg.receive();
if dbit(m) <-> receiver_bit {
receiver_bit := ~receiver_bit;
receiver_array(receiver_i) := d(m);
receiver_i := index.get_succ(receiver_i)
};
}
before receiver_send_ack {
receiver_scheduled := true;
receiver_scheduled := false;
assume abit(a) <-> ~receiver_bit;
call ack_msg.send(a);
ack_sent := true;
ack_sent := false;
}
before data_msg_drop {
call data_msg.drop(m);
}
before ack_msg_drop {
call ack_msg.drop(a);
}
¤
Invariants for proving safety (also help for liveness)
¤
safety - receiver array has values from sender array for all received indices
invariant receiver_array(I) ~= bot -> receiver_array(I) = sender_array(I)
invariant index.le(sender_gen_i, I) <-> sender_array(I) = bot
invariant index.le(receiver_i, I) <-> receiver_array(I) = bot
invariant index.le(sender_i, sender_gen_i)
invariant ~sender_bit & ~receiver_bit & ack_msg.le(A,A) -> abit(A)
invariant ~sender_bit & ~receiver_bit & data_msg.le(M1,M2) -> ~(dbit(M1) & ~dbit(M2))
invariant sender_bit & receiver_bit & ack_msg.le(A,A) -> ~abit(A)
invariant sender_bit & receiver_bit & data_msg.le(M1,M2) -> ~(~dbit(M1) & dbit(M2))
invariant ~sender_bit & receiver_bit & data_msg.le(M,M) -> ~dbit(M)
invariant ~sender_bit & receiver_bit & ack_msg.le(A1,A2) -> ~(abit(A1) & ~abit(A2))
invariant sender_bit & ~receiver_bit & data_msg.le(M,M) -> dbit(M)
invariant sender_bit & ~receiver_bit & ack_msg.le(A1,A2) -> ~(~abit(A1) & abit(A2))
invariant (sender_bit <-> receiver_bit) -> sender_i = receiver_i
invariant (sender_bit <-> ~receiver_bit) -> (
~index.le(receiver_i, sender_i) &
(forall I. ~index.le(I,sender_i) -> index.le(receiver_i,I))
)
invariant data_msg.le(M,M) & (dbit(M) <-> sender_bit) -> ~index.le(sender_gen_i, sender_i)
invariant data_msg.le(M,M) & (dbit(M) <-> sender_bit) -> d(M) = sender_array(sender_i)
invariant data_msg.le(M,M) -> d(M) ~= bot
invariant ack_msg.le(A,A) & (abit(A) <-> sender_bit) -> ~index.le(receiver_i,sender_i)
invariant ack_msg.le(A,A) & (abit(A) <-> sender_bit) -> receiver_array(sender_i) = sender_array(sender_i)
invariant ack_msg.le(A,A) & (abit(A) <-> sender_bit) -> receiver_array(sender_i) ~= bot
¤
Temporal property and its proof
The property to prove is: ( (globally eventually sender_scheduled) & # scheduling fairness (globally eventually receiver_scheduled) & # scheduling fairness ((globally eventually data_sent) -> (globally eventually data_received)) # data channel fairness ((globally eventually ack_sent) -> (globally eventually ack_received)) # ack channel fairness ) -> (forall I. globally (sender_array(I) ~= bot -> eventually (receiver_array(I) ~= bot)))
The set A of formulas containts the following formulas (and their subformulas): 1. the property 2. eventually globally ~sender_bit & ~receiver_bit 3. eventually globally ~sender_bit & receiver_bit 4. eventually globally sender_bit & ~receiver_bit 5. eventually globally sender_bit & receiver_bit
We also use "witness constants" for the following formula: ~ globally (sender_array(I) ~= bot -> eventually receiver_array(I) ~= bot)
¤
individual sk0:index_t # witness for the formula (exists I. ~ globally (sender_array(I) ~= bot -> (eventually receiver_array(I) ~= bot)))
temporal property [liveness] (
((exists I. ~ globally (sender_array(I ) ~= bot -> (eventually receiver_array(I ) ~= bot))) ->
(~ globally (sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot))))
) -> (
(
(globally eventually sender_scheduled) & # scheduling fairness
(globally eventually receiver_scheduled) & # scheduling fairness
((globally eventually data_sent) -> (globally eventually data_received)) & # data channel fairness
((globally eventually ack_sent) -> (globally eventually ack_received)) # ack channel fairness
) -> (forall I. globally (sender_array(I) ~= bot -> eventually (receiver_array(I) ~= bot)))
)
proof {
tactic l2s with
¤
Invariants for proving liveness
¤
basic, should be added automatically
invariant l2s_waiting | l2s_frozen | l2s_saved
invariant ~l2s_waiting | ~l2s_frozen
invariant ~l2s_waiting | ~l2s_saved
invariant ~l2s_frozen | ~l2s_saved
invariant l2s_saved -> (index.le(X,Y) <-> ($l2s_s X,Y. index.le(X,Y))(X,Y))
invariant l2s_saved -> (index.zero = ($l2s_s. index.zero))
invariant l2s_saved -> (abit(X) <-> ($l2s_s X. abit(X))(X))
invariant l2s_saved -> (dbit(X) <-> ($l2s_s X. dbit(X))(X))
invariant l2s_saved -> (d(X) = ($l2s_s X. d(X))(X))
invariant l2s_saved -> (sk0 = ($l2s_s. sk0))
invariant ~sender_scheduled
invariant ~receiver_scheduled
invariant ~data_sent
invariant ~data_received
invariant ~ack_sent
invariant ~ack_received
invariant globally eventually sender_scheduled
invariant globally eventually receiver_scheduled
invariant (globally eventually data_sent) -> (globally eventually data_received)
invariant (globally eventually ack_sent) -> (globally eventually ack_received)
invariant ~globally (sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot))
invariant ~($l2s_w. ~(sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot))) -> ~(sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot))
invariant ~l2s_waiting -> sender_array(sk0) ~= bot
invariant ~l2s_waiting -> (~eventually receiver_array(sk0) ~= bot)
invariant ~l2s_waiting -> receiver_array(sk0) = bot
basic use of temporal prophecy formulas
invariant (eventually globally (~sender_bit & ~receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally (~sender_bit & ~receiver_bit))) -> globally (~sender_bit & ~receiver_bit)
invariant (eventually globally (~sender_bit & receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally (~sender_bit & receiver_bit))) -> globally (~sender_bit & receiver_bit)
invariant (eventually globally ( sender_bit & ~receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally ( sender_bit & ~receiver_bit))) -> globally ( sender_bit & ~receiver_bit)
invariant (eventually globally ( sender_bit & receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally ( sender_bit & receiver_bit))) -> globally ( sender_bit & receiver_bit)
invariant index.le(I,sender_gen_i) -> l2s_d(I)
invariant data_msg.le(M,M) -> l2s_d(M)
invariant ack_msg.le(A,A) -> l2s_d(A)
invariant ~l2s_waiting -> ( index.le(I,sk0) -> l2s_a(I) )
invariant ~l2s_waiting & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) -> ( data_msg.le(M,M) & (dbit(M) <-> ~sender_bit) -> l2s_a(M) )
invariant ~l2s_waiting & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) -> ( ack_msg.le(A,A) & (abit(A) <-> receiver_bit) -> l2s_a(A) )
invariant ~l2s_waiting -> index.le(sender_i, sk0)
invariant ~l2s_waiting -> index.le(receiver_i, sk0)
invariant l2s_saved -> ($l2s_s X,Y. index.le(X,Y))(($l2s_s. sender_i), ($l2s_s. sk0))
invariant l2s_saved -> ($l2s_s X,Y. index.le(X,Y))(($l2s_s. receiver_i), ($l2s_s. sk0))
invariant l2s_saved -> index.le(($l2s_s. sender_i), sender_i)
invariant l2s_saved -> index.le(($l2s_s. receiver_i), receiver_i)
invariant l2s_saved & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) -> (
( sender_bit <-> ($l2s_s. sender_bit) ) &
( receiver_bit <-> ($l2s_s. receiver_bit) ) &
( sender_i = ($l2s_s. sender_i) ) &
( receiver_i = ($l2s_s. receiver_i) )
)
invariant l2s_saved & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) -> (
data_msg.le(M,M) & (dbit(M) <-> ~sender_bit) -> ($l2s_s X,Y. data_msg.le(X,Y))(M,M)
)
invariant l2s_saved & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) -> (
ack_msg.le(A,A) & (abit(A) <-> receiver_bit) -> ($l2s_s X,Y. ack_msg.le(X,Y))(A,A)
)
invariant l2s_saved & (sender_bit <-> ~($l2s_s. sender_bit)) -> sender_i ~= ($l2s_s. sender_i)
invariant l2s_saved & (receiver_bit <-> ~($l2s_s. receiver_bit)) -> receiver_i ~= ($l2s_s. receiver_i)
invariant ~(globally eventually data_sent) & (~l2s_waiting | ~($l2s_w. globally ~data_sent)) -> globally ~data_sent
invariant ~(globally eventually ack_sent) & (~l2s_waiting | ~($l2s_w. globally ~ack_sent)) -> globally ~ack_sent
invariant l2s_saved -> (($l2s_s. globally ~data_sent) -> globally ~data_sent)
invariant l2s_saved -> (($l2s_s. globally ~ack_sent) -> globally ~ack_sent)
invariant l2s_saved & ~($l2s_w. sender_scheduled) -> ~($l2s_s. globally ~data_sent)
invariant l2s_saved & ~($l2s_w. receiver_scheduled) -> ~($l2s_s. globally ~ack_sent)
invariant l2s_saved & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) & ~($l2s_w. data_received) & ~(globally ~data_sent) & (($l2s_s. sender_bit) <-> ($l2s_s. receiver_bit)) ->
exists M:data_msg_t. l2s_a(M) & ~data_msg.le(M,M) & ($l2s_s X,Y. data_msg.le(X,Y))(M,M) & (dbit(M) <-> ~receiver_bit)
invariant l2s_saved & (
(globally (~sender_bit & ~receiver_bit)) |
(globally (~sender_bit & receiver_bit)) |
(globally ( sender_bit & ~receiver_bit)) |
(globally ( sender_bit & receiver_bit))
) & ~($l2s_w. ack_received) & ~(globally ~ack_sent) & (($l2s_s. sender_bit) <-> ~($l2s_s. receiver_bit)) ->
exists A:ack_msg_t. l2s_a(A) & ~ack_msg.le(A,A) & ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) & (abit(A) <-> ~sender_bit)
invariant l2s_saved & ~(globally (~sender_bit & ~receiver_bit)) & ~($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & ~($l2s_w. ~(~sender_bit & ~receiver_bit)) -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) )
invariant l2s_saved & ~(globally (~sender_bit & receiver_bit)) & ~($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & ~($l2s_w. ~(~sender_bit & receiver_bit)) -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) )
invariant l2s_saved & ~(globally ( sender_bit & ~receiver_bit)) & ($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & ~($l2s_w. ~( sender_bit & ~receiver_bit)) -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) )
invariant l2s_saved & ~(globally ( sender_bit & receiver_bit)) & ($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & ~($l2s_w. ~( sender_bit & receiver_bit)) -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) )
}
}
}
export abp.gen_data
export abp.sender_send_data
export abp.sender_receive_ack
export abp.receiver_receive_data
export abp.receiver_send_ack
export abp.data_msg_drop
export abp.ack_msg_drop