% @doc spy: knows constraints {recipient, amount, payload} % % spies the chain for transactions that satisfy that constraint % -module(fd_spy). -vsn("0.2.0"). % MVP: register search patterns % simply print to console when one of them is found -behavior(gen_server). -export_type([ ]). -export([ %% caller context reg/3, %% api start_link/0, %% process context init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2 ]). % recipient is a string -type pubkey32() :: <<_:256>>. -record(sp, {recipient :: pubkey32(), amount :: pos_integer(), payload :: binary()}). -type search_pattern() :: #sp{}. -record(s, {last_height_seen = none :: none | integer(), searching_for = [] :: [search_pattern()]}). -type state() :: #s{}. -include("$zx_include/zx_logger.hrl"). %%----------------------------------------------------------------------------- %% caller context %%----------------------------------------------------------------------------- -spec reg(Recipient, Amount, Payload) -> ok | {error, Reason} when Recipient :: pubkey32(), Amount :: pos_integer(), Payload :: binary(), Reason :: any(). reg(R, A, P) when is_binary(R), byte_size(R) =:= 32, is_integer(A), A >= 0, is_binary(P) -> gen_server:call(?MODULE, {reg, R, A, P}). %% gen_server callbacks start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). %%----------------------------------------------------------------------------- %% process context below this line %%----------------------------------------------------------------------------- %% gen_server callbacks -spec init(none) -> {ok, state()}. init(none) -> tell("starting fd_spy"), hz:chain_nodes(fewd:chain_nodes()), erlang:send_after(1000, self(), check_chain), InitState = #s{}, {ok, InitState}. -spec handle_call(Msg, From, State) -> Result when Msg :: any(), From :: {pid(), reference()}, State :: state(), Result :: {reply, Reply, NewState} | {noreply, NewState}, Reply :: any(), NewState :: state(). handle_call({reg, Recipient, Amount, Payload}, _From, State) -> {Reply, NewState} = do_reg(Recipient, Amount, Payload, State), {reply, Reply, NewState}; handle_call(Unexpected, From, State) -> tell("~tp: unexpected call from ~tp: ~tp", [?MODULE, Unexpected, From]), {noreply, State}. handle_cast(Unexpected, State) -> tell("~tp: unexpected cast: ~tp", [?MODULE, Unexpected]), {noreply, State}. handle_info(check_chain, State) -> NewState = do_check_chain(State), erlang:send_after(1000, self(), check_chain), {noreply, NewState}; handle_info(Unexpected, State) -> tell("~tp: unexpected info: ~tp", [?MODULE, Unexpected]), {noreply, State}. code_change(_, State, _) -> {ok, State}. terminate(_, _) -> ok. %%----------------------------------------------------------------------------- %% internals %%----------------------------------------------------------------------------- do_check_chain(State = #s{last_height_seen = none}) -> case hz:kb_current_height() of {ok, Max} -> hh(Max-1, Max, State); Error -> tell("~tp hz error: ~tp", [?MODULE, Error]), State end; do_check_chain(State = #s{last_height_seen = Min}) -> case hz:kb_current_height() of {ok, Max} -> hh(Min, Max, State); Error -> tell("~tp hz error: ~tp", [?MODULE, Error]), State end. % handle height hh(PrevHeight, NewHeight, State) when PrevHeight < NewHeight -> tell("~tp cool: PrevHeight=~tp, NewHeight=~tp", [?MODULE, PrevHeight, NewHeight]), Spends = fd_hz:filter_spends(fd_hz:txs_from_to(PrevHeight + 1, NewHeight)), tell("~tp spends: ~tp", [?MODULE, Spends]), NewState = State#s{last_height_seen = NewHeight}, NewState; hh(PrevHeight, NewHeight, State) when PrevHeight >= NewHeight -> log(info, "~tp lame: PrevHeight=~tp, NewHeight=~tp", [?MODULE, PrevHeight, NewHeight]), State. -spec do_reg(Recipient, Amount, Payload, State) -> {Reply, NewState} when Recipient :: pubkey32(), Amount :: pos_integer(), Payload :: binary(), State :: state(), Reply :: ok | {error, Reason}, Reason :: any(), NewState :: state(). do_reg(Recipient, Amount, Payload, State) -> case already_registered(Recipient, Payload, State) of true -> {error, already_registered}; false -> really_register(Recipient, Amount, Payload, State) end. -spec already_registered(Recipient, Payload, State) -> boolean() when Recipient :: pubkey32(), Payload :: binary(), State :: state(). already_registered(Recipient, Payload, _State = #s{searching_for = SPs}) -> ar(Recipient, Payload, SPs). ar(Recipient, Payload, [#sp{recipient=Recipient, payload=Payload} | _]) -> true; ar(Recipient, Payload, [_ | Rest]) -> ar(Recipient, Payload, Rest); ar(_, _, []) -> false. -spec really_register(Recipient, Amount, Payload, State) -> {Reply, NewState} when Recipient :: pubkey32(), Amount :: pos_integer(), Payload :: binary(), State :: state(), Reply :: ok | {error, Reason}, Reason :: any(), NewState :: state(). really_register(R, A, P, State = #s{searching_for = SPs}) -> NewSearchPattern = #sp{recipient = R, amount = A, payload = P}, tell("~tp: really_register(~tp,~tp,~tp)", [?MODULE, R, A, P]), NewSearchPatterns = [NewSearchPattern | SPs], NewState = State#s{searching_for = NewSearchPatterns}, {reply, ok, NewState}.