Verified Vector Clocks

Verified Vector Clocks

RICON|West 2013, Lightning Talk

3e09fee7b359be847ed5fa48f524a3d3?s=128

Christopher Meiklejohn

October 29, 2013
Tweet

Transcript

  1. Verified Vector Clocks Christopher Meiklejohn RICON|West 2013

  2. Do you even know how Verified Vector Clocks work? Christopher

    Meiklejohn RICON|West 2013
  3. cmeiklejohn / @cmeik My Name is My Name

  4. Verify data structures; use them in Erlang. What it do?

  5. Coq: Interactive Theorem Prover

  6. None
  7. verlang: Verified Erlang

  8. Pierre Letouzey (MiniML) verlang: Verified Erlang

  9. Tim Carstens (Core Erlang) verlang: Verified Erlang

  10. No module nesting; ie. Coq.Arith.EqNat. verlang: Verified Erlang

  11. No currying; non associative -> constructor. verlang: Verified Erlang

  12. Inter- vs. intra module calls. verlang: Verified Erlang

  13. No receive primitive. verlang: Verified Erlang

  14. Attempt #1

  15. First pass, similar to the G-Counter. Attempt #1

  16. Failed; too much internal to FMapWeakList. Attempt #1

  17. Propositions and constructors do not export. Attempt #1

  18. Attempt #2

  19. Coq

  20. ! ! Require Import Coq.Lists.List. Require Import Coq.Arith.EqNat. Require Import

    Coq.Bool.Bool. ! Module VVClock. ! Definition vclock := list (nat * nat)%type. coq/vvclock.v
  21. (* % @doc Create a brand new vclock. -spec fresh()

    -> vclock(). fresh() -> []. *) ! Definition fresh : vclock := nil. coq/vvclock.v
  22. (* % @doc Increment VClock at Node. -spec increment(Node ::

    vclock_node(), VClock :: vclock()) -> vclock(). increment(Node, VClock) -> increment(Node, timestamp(), VClock). ! % @doc Increment VClock at Node. -spec increment(Node :: vclock_node(), IncTs :: timestamp(), VClock :: vclock()) -> vclock(). increment(Node, IncTs, VClock) -> {{_Ctr, _TS}=C1,NewV} = case lists:keytake(Node, 1, VClock) of false -> {{1, IncTs}, VClock}; {value, {_N, {C, _T}}, ModV} -> {{C + 1, IncTs}, ModV} end, [{Node,C1}|NewV]. *) ! Definition increment (actor : nat) (vclock : vclock) := match (find (fun clock => match clock with | pair x _ => beq_nat actor x end) vclock) with | None => cons (pair actor 1) vclock | Some (pair x y) => cons (pair x (S y)) (filter (fun clock => match clock with | pair x _ => negb (beq_nat actor x) end) vclock) end. coq/vvclock.v
  23. (* % @doc Compares two VClocks for equality. -spec equal(VClockA

    :: vclock(), VClockB :: vclock()) -> boolean(). equal(VA,VB) -> lists:sort(VA) =:= lists:sort(VB). *) ! Definition equal' status_and_vclock clock := match clock, status_and_vclock with | pair actor count, pair status vclock => match find (fun clock => match clock with | pair x _ => beq_nat actor x end) vclock with | None => pair false vclock | Some (pair _ y) => pair (andb status (beq_nat count y)) vclock end end. ! Definition equal vc1 vc2 := match fold_left equal' vc1 (pair true vc2) with | pair false _ => false | pair true _ => match fold_left equal' vc2 (pair true vc1) with | pair false _ => false | pair true _ => true end end. coq/vvclock.v
  24. ! (* % @doc Return true if Va is a

    direct descendant of Vb, % else false -- remember, a vclock is its own descendant! -spec descends(Va :: vclock()|[], Vb :: vclock()|[]) -> boolean(). descends(_, []) -> % all vclocks descend from the empty vclock true; descends(Va, Vb) -> [{NodeB, {CtrB, _T}}|RestB] = Vb, case lists:keyfind(NodeB, 1, Va) of false -> false; {_, {CtrA, _TSA}} -> (CtrA >= CtrB) andalso descends(Va,RestB) end. *) ! Fixpoint ble_nat (n m : nat) {struct n} : bool := match n with | O => true | S n' => match m with | O => false | S m' => ble_nat n' m' end end. ! Definition descends' status_and_vclock clock := match clock, status_and_vclock with | pair actor count, pair status vclock => match find (fun clock => match clock with | pair x _ => beq_nat actor x end) vclock with | None => pair false vclock | Some (pair _ y) => pair (andb status (ble_nat count y)) vclock end end. ! Definition descends vc1 vc2 := match fold_left descends' vc1 (pair true vc2) with | pair false _ => false | pair true _ => true end. coq/vvclock.v
  25. (* % @doc Combine all VClocks in the input list

    into their least possible % common descendant. -spec merge(VClocks :: [vclock()]) -> vclock() | []. merge([]) -> []; merge([SingleVclock]) -> SingleVclock; merge([First|Rest]) -> merge(Rest, lists:keysort(1, First)). ! merge([], NClock) -> NClock; merge([AClock|VClocks],NClock) -> merge(VClocks, merge(lists:keysort(1, AClock), NClock, [])). ! merge([], [], AccClock) -> lists:reverse(AccClock); merge([], Left, AccClock) -> lists:reverse(AccClock, Left); merge(Left, [], AccClock) -> lists:reverse(AccClock, Left); merge(V=[{Node1,{Ctr1,TS1}=CT1}=NCT1|VClock], N=[{Node2,{Ctr2,TS2}=CT2}=NCT2|NClock], AccClock) -> if Node1 < Node2 -> merge(VClock, N, [NCT1|AccClock]); Node1 > Node2 -> merge(V, NClock, [NCT2|AccClock]); true -> ({_Ctr,_TS} = CT) = if Ctr1 > Ctr2 -> CT1; Ctr1 < Ctr2 -> CT2; true -> {Ctr1, erlang:max(TS1,TS2)} end, merge(VClock, NClock, [{Node1,CT}|AccClock]) end. *) ! Definition max' vclock clock := match clock with | pair actor count => match find (fun clock => match clock with | pair x _ => beq_nat actor x end) vclock with | None => cons (pair actor count) vclock | Some (pair _ y) => cons (pair actor (max count y)) (filter (fun clock => match clock with | pair x _ => negb (beq_nat actor x) end) vclock) end end. ! Definition merge vc1 vc2 := fold_left max' vc1 vc2. coq/vvclock.v
  26. Extraction Language CoreErlang. ! Recursive Extraction VVClock. coq/vvclock.v

  27. Core Erlang

  28. module 'vvclock' [ 'fresh'/0, 'increment'/2, 'equal@'/2, 'equal'/2, ‘! ble_nat'/2, 'descends@'/2,

    'descends'/2, 'max@'/2, 'merge'/2 ] ! attributes [ ]! ! 'fresh'/0 = fun() ->! []! 'increment'/2 = fun (_actor, _vclock) ->! case call 'Coq.Lists.List':'find'! ( fun (_clock) ->! case _clock of! { 'Pair'! , _y! , _x! } when 'true' ->! call 'Coq.Arith.EqNat':'beq_nat'! ( _actor! , _y! )! end! , _vclock! ) of! {'Some', _p} when 'true' ->! case _p of! { 'Pair'! , _y! , _x! } when 'true' ->! [{ 'Pair'! , _y! , {'S', _x}! }|call 'Coq.Lists.List':'filter'! ( fun (_clock) ->! case _clock of! { 'Pair'! , _y0! , _x0! } when 'true' ->! call 'Coq.Init.Datatypes':'negb' (call 'Coq.Arith.EqNat':'beq_nat'! ( _actor! , _y0! ))! end! , _vclock! )]! end! 'None' when 'true' ->! [{ 'Pair'! , _actor! , {'S', 'O'}! }|_vclock]! end! src/vvclock.core
  29. src/vvclock.core 'equal@'/2 = fun (_status_and_vclock, _clock) ->! case _clock of!

    { 'Pair'! , _count! , _actor! } when 'true' ->! case _status_and_vclock of! { 'Pair'! , _vclock! , _status! } when 'true' ->! case call 'Coq.Lists.List':'find'! ( fun (_clock0) ->! case _clock0 of! { 'Pair'! , _y! , _x! } when 'true' ->! call 'Coq.Arith.EqNat':'beq_nat'! ( _count! , _y! )! end! , _status! ) of! {'Some', _p} when 'true' ->! case _p of! { 'Pair'! , _y! , _n! } when 'true' ->! { 'Pair'! , case _vclock of! 'True' when 'true' ->! call 'Coq.Arith.EqNat':'beq_nat'! ( _actor! , _n! )! 'False' when 'true' ->! 'False'! end! , _status! }! end! 'None' when 'true' ->! { 'Pair'! , 'False'! , _status! }! end! end! end
  30. src/vvclock.core 'equal'/2 = fun (_vc1, _vc2) ->! case call 'Coq.Lists.List':'fold_left'!

    ( 'equal@'! , _vc1! , { 'Pair'! , 'True'! , _vc2! }! ) of! { 'Pair'! , _l! , _b! } when 'true' ->! case _l of! 'True' when 'true' ->! case call 'Coq.Lists.List':'fold_left'! ( 'equal@'! , _vc2! , { 'Pair'! , 'True'! , _vc1! }! ) of! { 'Pair'! , _l0! , _b0! } when 'true' ->! _l0! end! 'False' when 'true' ->! 'False'! end! end! 'ble_nat'/2 = fun (_n, _m) ->! case _n of! 'O' when 'true' ->! 'True'! {'S', _n@} when 'true' ->! case _m of! 'O' when 'true' ->! 'False'! {'S', _m@} when 'true' ->! call 'vvclock.VVClock':'ble_nat'! ( _n@! , _m@! )! end! end!
  31. src/vvclock.core 'descends@'/2 = fun (_status_and_vclock, _clock) ->! case _clock of!

    { 'Pair'! , _count! , _actor! } when 'true' ->! case _status_and_vclock of! { 'Pair'! , _vclock! , _status! } when 'true' ->! case call 'Coq.Lists.List':'find'! ( fun (_clock0) ->! case _clock0 of! { 'Pair'! , _y! , _x! } when 'true' ->! call 'Coq.Arith.EqNat':'beq_nat'! ( _count! , _y! )! end! , _status! ) of! {'Some', _p} when 'true' ->! case _p of! { 'Pair'! , _y! , _n! } when 'true' ->! { 'Pair'! , case _vclock of! 'True' when 'true' ->! call 'vvclock.VVClock':'ble_nat'! ( _actor! , _n! )! 'False' when 'true' ->! 'False'! end! , _status! }! end! 'None' when 'true' ->! { 'Pair'! , 'True'! , _status! }! end! end! end!
  32. src/vvclock.core 'descends'/2 = fun (_vc1, _vc2) ->! case call 'Coq.Lists.List':'fold_left'!

    ( 'descends@'/2! , _vc1! , { 'Pair'! , 'True'! , _vc2! }! ) of! { 'Pair'! , _l! , _b! } when 'true' ->! _l! end!
  33. src/vvclock.core 'max@'/2 = fun (_vclock, _clock) ->! case _clock of!

    { 'Pair'! , _count! , _actor! } when 'true' ->! case call 'Coq.Lists.List':'find'! ( fun (_clock0) ->! case _clock0 of! { 'Pair'! , _y! , _x! } when 'true' ->! call 'Coq.Arith.EqNat':'beq_nat'! ( _count! , _y! )! end! , _vclock! ) of! {'Some', _p} when 'true' ->! case _p of! { 'Pair'! , _y! , _n! } when 'true' ->! [{ 'Pair'! , _count! , call 'Coq.Init.Peano':'max'! ( _actor! , _n! )! }|call 'Coq.Lists.List':'filter'! ( fun (_clock0) ->! case _clock0 of! { 'Pair'! , _y0! , _x! } when 'true' ->! call 'Coq.Init.Datatypes':'negb' (call 'Coq.Arith.EqNat':'beq_nat'! ( _count! , _y0! ))! end! , _vclock! )]! end! 'None' when 'true' ->! [{ 'Pair'! , _count! , _actor! }|_vclock]! end! end! 'merge'/2 = fun (_vc1, _vc2) ->! call 'Coq.Lists.List':'fold_left'! ( 'max'! , _vc1! , _vc2! )! end!
  34. Insert slide here.

  35. Containing code for Coq.Arith.EqNat, Coq.Init.Datatypes, Coq.Init.Peano, Coq.Lists.List. Insert slide here.

  36. %% @doc Take from riak_core/src/vclock.erl. riak_core_test() -> A = vvclock:fresh(),

    B = vvclock:fresh(), A1 = vvclock:increment('O', A), B1 = vvclock:increment({'S', 'O'}, B), B2 = vvclock:increment({'S', 'O'}, B1), ! 'True' = vvclock:descends(A1, A), 'True' = vvclock:descends(B1, B), 'False' = vvclock:descends(A1, B1), ! A2 = vvclock:increment('O', A1), C = vvclock:merge(A2, B1), C1 = vvclock:increment({'S', {'S', 'O'}}, C), ! 'True' = vvclock:descends(C1, A2), 'True' = vvclock:descends(C1, B1), 'False' = vvclock:descends(B1, C1), 'False' = vvclock:descends(B1, A1), ! ok. src/test.erl
  37. A: [] B: [] A1: [{'Pair','O',{'S','O'}}] B1: [{'Pair',{'S','O'},{'S','O'}}] B2: [{'Pair',{'S','O'},{'S',{'S','O'}}}]

    A2: [{'Pair','O',{'S',{'S','O'}}}] C: [{'Pair','O',{'S',{'S','O'}}},{'Pair',{'S','O'},{'S','O'}}] C1: [{'Pair',{'S',{'S','O'}},{'S','O'}}, {'Pair','O',{'S',{'S','O'}}}, {'Pair',{'S','O'},{'S','O'}}] make itest
  38. Conclusion

  39. Implemented riak_core/vclock.erl in Coq… Conclusion

  40. …except pruning. Conclusion

  41. …products of naturals. Conclusion

  42. Passes tests… Conclusion

  43. …if you modify assertions, and arguments. Conclusion

  44. Proofs; ltac vs. mtac. Conclusion

  45. Buggy extraction. Conclusion

  46. Missing constructors (fresh/0 not exported). Conclusion

  47. Higher-order functions missing arities. Conclusion

  48. Currying produces broken code. Conclusion

  49. No backtraces or debugging information. Conclusion

  50. Clause-to-case conversion difficulties. Conclusion

  51. https://github.com/cmeiklejohn/vvclocks https://github.com/cmeiklejohn/distributed-data-structures Thanks!