214 lines
5.8 KiB
Erlang
214 lines
5.8 KiB
Erlang
% @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}.
|