diff --git a/.gitignore b/.gitignore index fa45f06..9b42f45 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ rebar3.crashdump ct_run.* *.js *nohost +trace* diff --git a/src/hbbft_bba.erl b/src/hbbft_bba.erl index de22ac1..129f41a 100644 --- a/src/hbbft_bba.erl +++ b/src/hbbft_bba.erl @@ -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 @@ -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}. @@ -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 @@ -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 @@ -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, @@ -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 @@ -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 @@ -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), diff --git a/test/hbbft_SUITE.erl b/test/hbbft_SUITE.erl index 752b809..477575d 100644 --- a/test/hbbft_SUITE.erl +++ b/test/hbbft_SUITE.erl @@ -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() -> @@ -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'), @@ -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) -> diff --git a/test/hbbft_bba_SUITE.erl b/test/hbbft_bba_SUITE.erl index 979ae60..c10de9b 100644 --- a/test/hbbft_bba_SUITE.erl +++ b/test/hbbft_bba_SUITE.erl @@ -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() -> @@ -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) -> @@ -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. diff --git a/test/hbbft_test_utils.erl b/test/hbbft_test_utils.erl index 26bd058..e2dc0b0 100644 --- a/test/hbbft_test_utils.erl +++ b/test/hbbft_test_utils.erl @@ -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};