Packages
partisan
4.1.0
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc.16
5.0.0-rc.8
5.0.0-rc.2
5.0.0-rc.1
5.0.0-beta.24
5.0.0-beta.21
5.0.0-beta.20
5.0.0-beta.18
5.0.0-beta.17
5.0.0-beta.16
5.0.0-beta.15
5.0.0-beta.14
5.0.0-beta.13
4.1.0
3.0.0
2.1.0
2.0.0
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.2
1.0.1
1.0.0
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
0.0.1
Partisan is a scalable and flexible, TCP-based membership system and distribution layer for the BEAM.
Current section
Files
Jump to
Current section
Files
src/partisan_hbbft_worker.erl
-module(partisan_hbbft_worker).
-behaviour(gen_server).
-export([start_link/6, submit_transaction/2, start_on_demand/1, get_blocks/1, get_status/1, get_buf/1, stop/1, terminate/2]).
-export([verify_chain/2, block_transactions/1]).
-export([sync/2, fetch_from/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3]).
-record(block, {
prev_hash :: binary(),
transactions :: [binary()],
signature :: binary()
}).
-record(state, {
n :: non_neg_integer(),
id :: non_neg_integer(),
hbbft :: hbbft:hbbft_data(),
blocks :: [#block{}],
tempblock :: undefined | #block{},
sk :: tpke_privkey:privkey(),
ssk :: tpke_privkey:privkey_serialized(),
to_serialize = false :: boolean(),
defer_queue = []
}).
start_link(N, F, ID, SK, BatchSize, ToSerialize) ->
gen_server:start_link({global, name(ID)}, ?MODULE, [N, F, ID, SK, BatchSize, ToSerialize], []).
stop(Pid) ->
gen_server:stop(Pid).
submit_transaction(Msg, Pid) ->
gen_server:call(Pid, {submit_txn, Msg}, infinity).
sync(Pid, Target) ->
gen_server:call(Pid, {sync, Target}, infinity).
fetch_from(Pid, Highest) ->
gen_server:call(Pid, {fetch_from, Highest}, infinity).
start_on_demand(Pid) ->
gen_server:call(Pid, start_on_demand, infinity).
get_blocks(Pid) ->
gen_server:call(Pid, get_blocks, infinity).
get_status(Pid) ->
gen_server:call(Pid, get_status, infinity).
get_buf(Pid) ->
gen_server:call(Pid, get_buf, infinity).
verify_chain([], _) ->
true;
verify_chain([G], PubKey) ->
lager:info("verifying genesis block~n"),
%% genesis block has no prev hash
case G#block.prev_hash == <<>> of
true ->
%% genesis block should have a valid signature
HM = tpke_pubkey:hash_message(PubKey, term_to_binary(G#block{signature= <<>>})),
Signature = tpke_pubkey:deserialize_element(PubKey, G#block.signature),
tpke_pubkey:verify_signature(PubKey, Signature, HM);
false ->
lager:info("no genesis block~n"),
false
end;
verify_chain(Chain, PubKey) ->
lager:info("Chain verification depth ~p~n", [length(Chain)]),
case verify_block_fit(Chain, PubKey) of
true -> verify_chain(tl(Chain), PubKey);
false ->
lager:info("bad signature~n"),
false
end.
verify_block_fit([B], _) when B#block.prev_hash == <<>> -> true;
verify_block_fit([A, B | _], PubKey) ->
%% A should have the the prev_hash of B
case A#block.prev_hash == hash_block(B) of
true ->
%% A should have a valid signature
HM = tpke_pubkey:hash_message(PubKey, term_to_binary(A#block{signature= <<>>})),
Signature = tpke_pubkey:deserialize_element(PubKey, A#block.signature),
case tpke_pubkey:verify_signature(PubKey, Signature, HM) of
true ->
true;
false ->
lager:info("bad signature~n"),
false
end;
false ->
lager:info("parent hash mismatch ~p ~p~n", [A#block.prev_hash, hash_block(B)]),
false
end.
block_transactions(Block) ->
Block#block.transactions.
init([N, F, ID, SK, BatchSize, ToSerialize]) ->
%% deserialize the secret key once
DSK = tpke_privkey:deserialize(SK),
%% init hbbft
HBBFT = hbbft:init(DSK, N, F, ID, BatchSize, infinity),
%% store the serialized state and serialized SK
{ok, #state{hbbft=HBBFT, blocks=[], id=ID, n=N, sk=DSK, ssk=SK, to_serialize=ToSerialize}}.
handle_call(start_on_demand, _From, State = #state{hbbft=HBBFT, sk=SK}) ->
NewState = dispatch(hbbft:start_on_demand(maybe_deserialize_hbbft(HBBFT, SK)), undefined, State),
{reply, ok, NewState};
handle_call({submit_txn, Txn}, _From, State = #state{hbbft=HBBFT, sk=SK}) ->
NewState = dispatch(hbbft:input(maybe_deserialize_hbbft(HBBFT, SK), Txn), undefined, State),
{reply, ok, NewState};
handle_call({sync, Target}, _From, #state{blocks = Blocks} = State) ->
case self() of
Target ->
%% deadlock avoidance, also handled at the prop level
{reply, ok, State};
_ ->
%% todo check for disjoint chain if there is ever a
%% possiblity of bad sync (I don't think there is right now)
{ok, FetchedBlocks} = fetch_from(Target, hd(Blocks)),
Blocks1 = lists:append(FetchedBlocks, Blocks),
{reply, ok, State#state{blocks = Blocks1}}
end;
handle_call({fetch_from, Highest}, _From, #state{blocks = Blocks} = State) ->
case lists:member(Highest, Blocks) of
true ->
Fetched = lists:takewhile(fun(B) -> B /= Highest end, Blocks),
{reply, {ok, Fetched}, State};
false ->
%% the syncer is higher than the syncee or there is a
%% disjoint chain, so return [] to noop
{reply, {ok, []}, State}
end;
handle_call(get_blocks, _From, State) ->
{reply, {ok, State#state.blocks}, State};
handle_call(get_status, _From, State) ->
{reply, {ok, hbbft:status(State#state.hbbft)}, State};
handle_call(get_buf, _From, State) ->
{reply, {ok, hbbft:buf(State#state.hbbft)}, State};
handle_call(Msg, _From, State) ->
lager:info("unhandled msg ~p~n", [Msg]),
{reply, ok, State}.
handle_cast({hbbft, PeerID, Msg}, State = #state{hbbft=HBBFT, sk=SK}) ->
NewState = handle_defers(dispatch(hbbft:handle_msg(maybe_deserialize_hbbft(HBBFT, SK), PeerID, Msg), {PeerID, Msg}, State)),
{noreply, NewState};
handle_cast({block, NewBlock}, State=#state{sk=SK, hbbft=HBBFT}) ->
case lists:member(NewBlock, State#state.blocks) of
false ->
lager:info("XXXXXXXX~n"),
%% a new block, check if it fits on our chain
case verify_block_fit([NewBlock|State#state.blocks], tpke_privkey:public_key(SK)) of
true ->
%% advance to the next round
lager:info("~p skipping to next round~n", [self()]),
%% remove any transactions we have from our queue (drop the signature messages, they're not needed)
{NewHBBFT, _} = hbbft:finalize_round(maybe_deserialize_hbbft(HBBFT, SK), NewBlock#block.transactions, term_to_binary(NewBlock)),
NewState = dispatch(hbbft:next_round(maybe_deserialize_hbbft(NewHBBFT, SK)), undefined, State#state{blocks=[NewBlock | State#state.blocks]}),
{noreply, NewState#state{tempblock=undefined}};
false ->
lager:info("invalid block proposed~n"),
{noreply, State}
end;
true ->
{noreply, State}
end;
handle_cast(Msg, State) ->
lager:info("unhandled msg ~p~n", [Msg]),
{noreply, State}.
handle_info(Msg, State) ->
lager:info("unhandled msg ~p~n", [Msg]),
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% @private
terminate(_Reason, _State) ->
lager:info("Terminating hbbft worker.", []),
ok.
dispatch({NewHBBFT, {send, ToSend}}, _Msg, State) ->
do_send(ToSend, State),
State#state{hbbft=maybe_serialize_HBBFT(NewHBBFT, State#state.to_serialize)};
dispatch({NewHBBFT, {result, {transactions, _, Txns}}}, _Msg, State) ->
NewBlock = case State#state.blocks of
[] ->
%% genesis block
#block{prev_hash= <<>>, transactions=Txns, signature= <<>>};
[PrevBlock|_Blocks] ->
#block{prev_hash=hash_block(PrevBlock), transactions=Txns, signature= <<>>}
end,
%% tell the badger to finish the round
dispatch(hbbft:finalize_round(maybe_deserialize_hbbft(NewHBBFT, State#state.sk), Txns, term_to_binary(NewBlock)), undefined, State#state{tempblock=NewBlock});
dispatch({NewHBBFT, {result, {signature, Sig}}}, _Msg, State = #state{tempblock=NewBlock0}) ->
NewBlock = NewBlock0#block{signature=Sig},
case os:getenv("PARTISAN") of
"true" ->
lists:foreach(fun(Dest) ->
try
Process = global:whereis_name(name(Dest)),
Node = node(Process),
Message = {block, NewBlock},
ok = partisan_pluggable_peer_service_manager:cast_message(Node, undefined, Process, Message, [])
catch
_:_ ->
%% Node might have gone offline.
ok
end
end, lists:seq(0, State#state.n - 1));
false ->
[ gen_server:cast({global, name(Dest)}, {block, NewBlock}) || Dest <- lists:seq(0, State#state.n - 1)]
end,
dispatch(hbbft:next_round(maybe_deserialize_hbbft(NewHBBFT, State#state.sk)), undefined, State#state{blocks=[NewBlock|State#state.blocks], tempblock=undefined});
dispatch({NewHBBFT, ok}, _Msg, State) ->
State#state{hbbft=maybe_serialize_HBBFT(NewHBBFT, State#state.to_serialize)};
dispatch(ignore, _Msg, State) ->
State;
dispatch({_NewHBBFT, defer}, Msg, State) ->
State#state{defer_queue=[Msg|State#state.defer_queue]};
dispatch({_NewHBBFT, already_started}, _Msg, State) ->
State;
dispatch({NewHBBFT, Other}, Msg, State) ->
lager:info("UNHANDLED ~p ~p~n", [Other, Msg]),
State#state{hbbft=maybe_serialize_HBBFT(NewHBBFT, State#state.to_serialize)};
dispatch(Other, Msg, State) ->
lager:info("UNHANDLED2 ~p ~p~n", [Other, Msg]),
State.
do_send([], _) ->
ok;
do_send([{unicast, Dest, Msg}|T], State) ->
% lager:info("~p unicasting ~p to ~p~n", [State#state.id, Msg, global:whereis_name(name(Dest))]),
case os:getenv("PARTISAN") of
"true" ->
try
Process = global:whereis_name(name(Dest)),
Node = node(Process),
Message = {hbbft, State#state.id, Msg},
lager:info("Sending partisan message to node ~p process ~p: ~p", [Node, Process, Message]),
ok = partisan_pluggable_peer_service_manager:cast_message(Node, undefined, Process, Message, [])
catch
_:_ ->
%% Node might have gone offline.
ok
end;
_ ->
gen_server:cast({global, name(Dest)}, {hbbft, State#state.id, Msg})
end,
do_send(T, State);
do_send([{multicast, Msg}|T], State) ->
% lager:info("~p multicasting ~p to ~p~n", [State#state.id, Msg, [global:whereis_name(name(Dest)) || Dest <- lists:seq(0, State#state.n - 1)]]),
case os:getenv("PARTISAN") of
"true" ->
lists:foreach(fun(Dest) ->
try
Process = global:whereis_name(name(Dest)),
Node = node(Process),
Message = {hbbft, State#state.id, Msg},
lager:info("Sending partisan message to node ~p process ~p: ~p", [Node, Process, Message]),
ok = partisan_pluggable_peer_service_manager:cast_message(Node, undefined, Process, Message, [])
catch
_:_ ->
%% Node might have gone offline.
ok
end
end, lists:seq(0, State#state.n - 1));
_ ->
[gen_server:cast({global, name(Dest)}, {hbbft, State#state.id, Msg}) || Dest <- lists:seq(0, State#state.n - 1)]
end,
do_send(T, State).
%% helper functions
name(N) ->
list_to_atom(lists:flatten(["hbbft_worker_", integer_to_list(N)])).
hash_block(Block) ->
crypto:hash(sha256, term_to_binary(Block)).
maybe_deserialize_hbbft(HBBFT, SK) ->
case hbbft:is_serialized(HBBFT) of
true -> hbbft:deserialize(HBBFT, SK);
false -> HBBFT
end.
maybe_serialize_HBBFT(HBBFT, ToSerialize) ->
case hbbft:is_serialized(HBBFT) orelse not ToSerialize of
true -> HBBFT;
false -> element(1, hbbft:serialize(HBBFT, false))
end.
handle_defers(State = #state{defer_queue=[]}) ->
State;
handle_defers(State) ->
lists:foldl(fun({PeerID, Msg}, Acc) ->
#state{hbbft=HBBFT, sk=SK} = Acc,
dispatch(hbbft:handle_msg(maybe_deserialize_hbbft(HBBFT, SK), PeerID, Msg), {PeerID, Msg}, State)
end, State#state{defer_queue=[]}, lists:reverse(State#state.defer_queue)).