Skip to content

Commit

Permalink
add basic fakecast for bba and hbbft
Browse files Browse the repository at this point in the history
  • Loading branch information
evanmcc committed Feb 1, 2019
1 parent 1dc8fa2 commit 4e534ac
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ rebar3.crashdump
ct_run.*
*.js
*nohost
trace*
14 changes: 13 additions & 1 deletion src/hbbft_bba.erl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ status(BBAData) ->

-spec init(tpke_privkey:privkey(), pos_integer(), non_neg_integer()) -> bba_data().
init(SK, N, F) ->
%% fakecast:trace("init n ~p f ~p", [N, F]),
#bba_data{secret_key=SK, n=N, f=F}.

%% upon receiving input binput , set est0 := binput and proceed as
Expand All @@ -80,7 +81,8 @@ init(SK, N, F) ->
%% – bin_values {}
-spec input(bba_data(), 0 | 1) -> {bba_data(), ok | {send, [hbbft_utils:multicast(bval_msg())]}}.
input(Data = #bba_data{state=init}, BInput) ->
{Data#bba_data{est = BInput, broadcasted=add(BInput, Data#bba_data.broadcasted)}, {send, [{multicast, {bval, Data#bba_data.round, BInput}}]}};
{Data#bba_data{est = BInput, broadcasted=add(BInput, Data#bba_data.broadcasted)},
{send, [{multicast, {bval, Data#bba_data.round, BInput}}]}};
input(Data = #bba_data{state=done}, _BInput) ->
{Data, ok}.

Expand Down Expand Up @@ -117,6 +119,7 @@ handle_msg(Data = #bba_data{round=R, coin=Coin}, J, {{coin, R}, CMsg}) when Coin
%% dispatch the message to the nested coin protocol
case hbbft_cc:handle_msg(Data#bba_data.coin, J, CMsg) of
ignore ->
%% fakecast:trace("coin ignore"),
ignore;
{_NewCoin, {result, Result}} ->
%% ok, we've obtained the common coin
Expand All @@ -141,6 +144,7 @@ handle_msg(Data, J, {term, B}) when B == 0; B == 1 ->
decide(NewData, [])
end;
handle_msg(_Data, _J, _Msg) ->
%% fakecast:trace("end ignore: d ~p j ~p m ~p", [_Data, _J, _Msg]),
ignore.

%% – upon receiving BVALr (b) messages from f + 1 nodes, if
Expand All @@ -154,10 +158,12 @@ bval(Data=#bba_data{f=F}, Id, V) ->
{NewData, ToSend} = case WitnessCount >= F+1 andalso not has(V, Data#bba_data.broadcasted) of
true ->
%% add to broadcasted
%% fakecast:trace("got enough"),
NewData0 = Data#bba_data{bval_witness=Witness,
broadcasted=add(V, Data#bba_data.broadcasted)},
{NewData0, [{multicast, {bval, Data#bba_data.round, V}}]};
false ->
%% fakecast:trace("updating witness"),
{Data#bba_data{bval_witness=Witness}, []}
end,

Expand Down Expand Up @@ -285,6 +291,7 @@ deserialize(#bba_serialized_data{state=State,

decide(Data=#bba_data{n=N, f=F}, ToSend0) ->
%% check if we have n-f aux messages
%% fakecast:trace("decide ~p ~p ~p", [N, F, Data]),
case threshold(N, F, Data, aux) of
true ->
case schedule(Data#bba_data.round) of
Expand Down Expand Up @@ -455,6 +462,8 @@ check_coin_flip({Data, ToSend}, Flip) ->
false ->
undefined
end,
%% fakecast:trace("count is right: round ~p b ~p flip ~p output ~p",
%% [Data#bba_data.round, B, Flip, Data#bba_data.output]),
case B == Flip andalso Data#bba_data.output == B of
true ->
%% we are done
Expand All @@ -464,10 +473,13 @@ check_coin_flip({Data, ToSend}, Flip) ->
%% increment round and continue
NewData = init(Data#bba_data.secret_key, Data#bba_data.n, Data#bba_data.f),
{NewData2, {send, NewToSend}} = input(NewData#bba_data{round=Data#bba_data.round + 1, output=Output, terminate_witness=Data#bba_data.terminate_witness}, B),
%% fakecast:trace("new round mesages: ~p ++ ~p",
%% [ToSend, NewToSend]),
{NewData2, {send, ToSend ++ NewToSend}}
end;
false ->
%% else estr+1 := s%2
%% fakecast:trace("count is wrong"),
B = Flip,
NewData = init(Data#bba_data.secret_key, Data#bba_data.n, Data#bba_data.f),
{NewData2, {send, NewToSend}} = input(NewData#bba_data{round=Data#bba_data.round + 1, terminate_witness=Data#bba_data.terminate_witness}, B),
Expand Down
87 changes: 83 additions & 4 deletions test/hbbft_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
encrypt_decrypt_test/1,
start_on_demand_test/1,
one_actor_wrong_key_test/1,
one_actor_corrupted_key_test/1
one_actor_corrupted_key_test/1,
initial_fakecast_test/1
]).

all() ->
Expand All @@ -26,12 +27,13 @@ all() ->
encrypt_decrypt_test,
start_on_demand_test,
one_actor_wrong_key_test,
one_actor_corrupted_key_test
one_actor_corrupted_key_test,
initial_fakecast_test
].

init_per_testcase(_, Config) ->
N = 5,
F = N div 4,
N = 7,
F = N div 3,
Module = hbbft,
BatchSize = 20,
{ok, Dealer} = dealer:new(N, F+1, 'SS512'),
Expand Down Expand Up @@ -385,6 +387,83 @@ one_actor_corrupted_key_test(Config) ->
io:format("chain contains ~p distinct transactions~n", [length(BlockTxns)]),
ok.


-record(state,
{
node_count :: integer(),
stopped = false :: boolean(),
results = sets:new() :: sets:set()
}).

trivial(_Message, _From, _To, _NodeState, _NewState, _Actions,
#state{stopped = false} = State) ->
case rand:uniform(100) of
100 ->
{actions, [{stop_node, 4}, {stop_node, 2}
], State#state{stopped = true}};
_ ->
{continue, State}
end;
trivial(_Message, _From, To, _NodeState, _NewState, {result, Result},
#state{results = Results0} = State) ->
Results = sets:add_element({result, {To, Result}}, Results0),
%% ct:pal("results len ~p ~p", [sets:size(Results), sets:to_list(Results)]),
case sets:size(Results) == State#state.node_count of
true ->
{result, Results};
false ->
{continue, State#state{results = Results}}
end;
trivial(_Message, _From, _To, _NodeState, _NewState, _Actions, ModelState) ->
{continue, ModelState}.

initial_fakecast_test(Config) ->
N = proplists:get_value(n, Config),
F = proplists:get_value(f, Config),
BatchSize = proplists:get_value(batchsize, Config),
Module = proplists:get_value(module, Config),
PrivateKeys = proplists:get_value(privatekeys, Config),

Init = fun() ->
{ok,
{
Module,
random,
favor_concurrent,
[aaa, bbb, ccc, ddd, eee, fff, ggg], %% are names useful?
0,
[[Sk, N, F, ID, BatchSize, infinity]
|| {ID, Sk} <- lists:zip(lists:seq(0, N - 1), PrivateKeys)],
5000
},
#state{node_count = N - 2}
}
end,
Msgs = [ crypto:strong_rand_bytes(128) || _ <- lists:seq(1, N*10)],
%% send each message to a random subset of the HBBFT actors
Input =
fun() ->
lists:foldl(fun(ID, Acc) ->
Size = max(length(Msgs), BatchSize + (rand:uniform(length(Msgs)))),
Subset = hbbft_test_utils:random_n(Size, Msgs),
lists:append([{ID, Msg} || Msg <- Subset], Acc)
end, [], lists:seq(0, N - 1))
end,
%% start it on runnin'
{ok, ConvergedResults} = fakecast:start_test(Init, fun trivial/7, %%{1543,962578,549287},
os:timestamp(),
Input),

%% check all N actors returned a result
?assertEqual(N - 2, sets:size(ConvergedResults)),
DistinctResults = sets:from_list([BVal || {result, {_, BVal}} <- sets:to_list(ConvergedResults)]),
%% check all N actors returned the same result
?assertEqual(1, sets:size(DistinctResults)),
{_, _, AcceptedMsgs} = lists:unzip3(lists:flatten(sets:to_list(DistinctResults))),
%% check all the Msgs are actually from the original set
?assert(sets:is_subset(sets:from_list(lists:flatten(AcceptedMsgs)), sets:from_list(Msgs))),
ok.

%% helper functions

enumerate(List) ->
Expand Down
115 changes: 113 additions & 2 deletions test/hbbft_bba_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
init_with_ones_test/1,
init_with_mixed_zeros_and_ones_test/1,
f_dead_test/1,
fplusone_dead_test/1
fplusone_dead_test/1,
fakecast_test/1
]).

all() ->
Expand All @@ -20,7 +21,8 @@ all() ->
init_with_ones_test,
init_with_mixed_zeros_and_ones_test,
f_dead_test,
fplusone_dead_test
fplusone_dead_test,
fakecast_test
].

init_per_testcase(_, Config) ->
Expand Down Expand Up @@ -194,3 +196,112 @@ fplusone_dead_test(Config) ->
%% should not converge
?assertEqual(0, sets:size(ConvergedResults)),
ok.


-record(state,
{
node_count :: integer(),
stopped = false :: boolean(),
results = sets:new() :: sets:set()
}).

neg(1) -> 0;
neg(0) -> 1.

alt(1) -> 2;
alt(0) -> 1;
alt(2) -> 3;
alt(3) -> 1.

trivial(_Message, _, 1, _NodeState, _NewState, {send, [{multicast, {bval, R, V}}]},
#state{} = State) ->
{actions, [{alter_actions, {send, [{unicast, 0, {bval, R, V}},
{unicast, 1, {bval, R, V}},
{unicast, 2, {bval, R, neg(V)}},
{unicast, 3, {bval, R, neg(V)}}]}}],
State};
trivial(_Message, _, 1, _NodeState, _NewState, {send, [{multicast, {aux, R, V}}]},
#state{} = State) ->
{actions, [{alter_actions, {send, [{unicast, 3, {aux, R, V}},
{unicast, 2, {aux, R, V}},
{unicast, 1, {aux, R, alt(V)}},
{unicast, 0, {aux, R, alt(V)}}]}}],
State};
trivial(_Message, _From, To, _NodeState, _NewState, {result, Result},
#state{results = Results0} = State) ->
Results = sets:add_element({result, {To, Result}}, Results0),
%% ct:pal("results len ~p ~p", [sets:size(Results), sets:to_list(Results)]),
case sets:size(Results) == State#state.node_count of
true ->
{result, Results};
false ->
{continue, State#state{results = Results}}
end;
trivial(_Message, _From, To, _NodeState, _NewState, {result_and_send, Result, _},
#state{results = Results0} = State) ->
Results = sets:add_element({result, {To, Result}}, Results0),
%% ct:pal("results len ~p ~p", [sets:size(Results), sets:to_list(Results)]),
case sets:size(Results) == State#state.node_count of
true ->
{result, Results};
false ->
{continue, State#state{results = Results}}
end;
trivial(_Message, _From, _To, _NodeState, _NewState, _Actions, ModelState) ->
%%fakecast:trace("act ~p", [_Actions]),
{continue, ModelState}.

fakecast_test(Config) ->
Module = proplists:get_value(module, Config),
N = 4,
F = 1,
{ok, Dealer} = dealer:new(N, F+1, 'SS512'),
{ok, {_PubKey, PrivateKeys}} = dealer:deal(Dealer),
Init = fun() ->
{ok,
{
Module,
random,
favor_concurrent,
[aaa, bbb, ccc, ddd],
0,
[[Sk, N, F]
|| Sk <- PrivateKeys],
1000
},
#state{node_count = N}
}
end,
Me = self(),
Input =
fun() ->
A = rand:uniform(2) - 1,
%%B = rand:uniform(2) - 1,
%%IVec = hbbft_test_utils:shuffle([A,A,A,B]),
IVec = [A,A,A,A],
Inputs = lists:zip(lists:seq(0, 3), IVec),

%% send this out to the running process for checking.
%% not 100% sure if this is safe
Me ! {input_vector, Inputs},
Inputs
end,

{ok, Results} = fakecast:start_test(Init, fun trivial/7,
{1544,461835,550446},
%%os:timestamp(),
Input),
Inputs = receive {input_vector, I} -> I after 10000 -> throw(timeout) end,
%% figure out which was the majority value
Majority =
case length(lists:filter(fun({_, X}) -> X =:= 1 end, Inputs)) of
4 -> 1;
3 -> 1;
1 -> 0;
0 -> 0
end,
%% make sure we got it and make sure there's just one distinct result
ResultsList = sets:to_list(Results),
ct:pal("ResultsList: ~p~nInputs: ~p", [ResultsList, Inputs]),
?assertMatch([Majority], lists:usort([BVal || {result, {_, BVal}} <- ResultsList])),
ok.
2 changes: 1 addition & 1 deletion test/hbbft_test_utils.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(hbbft_test_utils).

-export([do_send_outer/4, random_n/2, enumerate/1, merge_replies/3]).
-export([do_send_outer/4, shuffle/1, random_n/2, enumerate/1, merge_replies/3]).

do_send_outer(_Mod, [], States, Acc) ->
{States, Acc};
Expand Down

0 comments on commit 4e534ac

Please sign in to comment.