280 lines
9.2 KiB
Erlang
280 lines
9.2 KiB
Erlang
-module(gmhc_eureka).
|
|
-vsn("0.8.3").
|
|
|
|
-export([get_pool_address/0]).
|
|
-export([cache_good_address/1,
|
|
invalidate_cache/0,
|
|
cached_address/0,
|
|
cache_filename/0,
|
|
cache_dir/0]).
|
|
|
|
-include_lib("kernel/include/logger.hrl").
|
|
-include("gmhc_events.hrl").
|
|
|
|
get_pool_address() ->
|
|
case cached_address() of
|
|
{ok, _} = Ok -> Ok;
|
|
{error, _} ->
|
|
get_pool_address_()
|
|
end.
|
|
|
|
cached_address() ->
|
|
I0 = cache_info(),
|
|
CacheF = cache_filename(I0),
|
|
?LOG_DEBUG("Eureka cache filename: ~p", [CacheF]),
|
|
case file:read_file(CacheF) of
|
|
{ok, Bin} ->
|
|
NowTS = erlang:system_time(seconds),
|
|
OldestTS = NowTS - 24*60*60,
|
|
try binary_to_term(Bin) of
|
|
#{ ts := TS
|
|
, network := N
|
|
, pubkey := PK
|
|
, host := _
|
|
, port := _
|
|
, pool_id := _} = Map when N == map_get(network, I0),
|
|
PK == map_get(pubkey, I0) ->
|
|
if TS >= OldestTS ->
|
|
Result = maps:remove(ts, Map),
|
|
?LOG_DEBUG("Cached eureka info: ~p", [Result]),
|
|
{ok, Result};
|
|
true ->
|
|
{error, outdated}
|
|
end;
|
|
Other ->
|
|
{error, {invalid_cache_term, Other}}
|
|
catch
|
|
error:E ->
|
|
{error, {invalid_cache_data, E}}
|
|
end;
|
|
{error, _} = Err ->
|
|
Err
|
|
end.
|
|
|
|
cache_good_address(#{host := _,
|
|
port := _,
|
|
pool_id := _} = I0) ->
|
|
CacheInfo = cache_info(I0),
|
|
CacheF = cache_filename(CacheInfo),
|
|
|
|
ToCache = CacheInfo#{ts => erlang:system_time(seconds)},
|
|
case filelib:ensure_dir(CacheF) of
|
|
ok ->
|
|
case file:write_file(CacheF, term_to_binary(ToCache)) of
|
|
ok ->
|
|
?LOG_DEBUG("Cached eureka info in: ~p", [CacheF]),
|
|
{ok, ToCache};
|
|
{error, _} = Err ->
|
|
?LOG_DEBUG("Couldn't cache eureka in ~p: ~p", [CacheF, Err]),
|
|
Err
|
|
end;
|
|
{error, _} = Err ->
|
|
?LOG_ERROR("Cannot save cached info to ~s", [CacheF]),
|
|
Err
|
|
end.
|
|
|
|
invalidate_cache() ->
|
|
CacheF = cache_filename(),
|
|
case file:delete(CacheF) of
|
|
ok ->
|
|
?LOG_DEBUG("Eureka cache file removed (~p)", [CacheF]),
|
|
ok;
|
|
{error, _} = Err ->
|
|
?LOG_DEBUG("Couldn't remove Eureka cache (~p): ~p", [CacheF, Err]),
|
|
Err
|
|
end.
|
|
|
|
cache_info(#{ host := Addr
|
|
, port := Port
|
|
, pool_id := PoolId }) ->
|
|
I0 = cache_info(),
|
|
I0#{ host => unicode:characters_to_binary(Addr)
|
|
, port => Port
|
|
, pool_id => unicode:characters_to_binary(PoolId)}.
|
|
|
|
cache_info() ->
|
|
Pubkey = gmhc_config:get_config([<<"pubkey">>]),
|
|
Network = gmhc_config:get_config([<<"network">>]),
|
|
#{ pubkey => Pubkey
|
|
, network => Network }.
|
|
|
|
cache_filename() ->
|
|
cache_filename(cache_info()).
|
|
|
|
cache_filename(#{network := Network, pubkey := Pubkey}) ->
|
|
Path = filename:join(cache_dir(), Network),
|
|
<<"ak_", PKShort:8/binary, _/binary>> = Pubkey,
|
|
filename:join(Path, "gmhc_eureka." ++ binary_to_list(PKShort) ++ ".cache").
|
|
|
|
cache_dir() ->
|
|
case gmconfig:find_config([<<"cache_dir">>]) of
|
|
{ok, D} ->
|
|
D;
|
|
undefined ->
|
|
case setup_zomp:is_zomp_context() of
|
|
true ->
|
|
cache_dir_zomp();
|
|
false ->
|
|
filename:join(setup:data_dir(), "gmhive.cache")
|
|
end
|
|
end.
|
|
|
|
cache_dir_zomp() ->
|
|
#{package_id := {Realm, App, _}} = zx_daemon:meta(),
|
|
filename:join(zx_lib:ppath(var, {Realm, App}), "gmhive.cache").
|
|
|
|
get_pool_address_() ->
|
|
case gmconfig:find_config([<<"pool_admin">>, <<"url">>], [user_config]) of
|
|
{ok, URL0} ->
|
|
case expand_url(URL0) of
|
|
<<"local">> ->
|
|
{ok, #{host => <<"127.0.0.1">>,
|
|
port => gmconfig:get_config(
|
|
[<<"pool">>, <<"port">>], [schema_default]),
|
|
pool_id => gmhc_config:get_config([<<"pool">>, <<"id">>]) }};
|
|
URL ->
|
|
?LOG_INFO("Trying to connect to ~p", [URL]),
|
|
connect1(URL)
|
|
end;
|
|
undefined ->
|
|
Network = gmconfig:get_config([<<"network">>]),
|
|
URL0 = gmconfig:get_config([ <<"pool_admin">>
|
|
, <<"default_per_network">>
|
|
, Network ],
|
|
[schema_default]),
|
|
URL = expand_url(URL0),
|
|
?LOG_INFO("Using default for ~p: ~p", [Network, URL]),
|
|
connect1(URL)
|
|
end.
|
|
|
|
connect1(URL0) ->
|
|
URL = binary_to_list(URL0),
|
|
Res = request(get, URL),
|
|
?LOG_DEBUG("Res = ~p", [Res]),
|
|
case Res of
|
|
{ok, Body} ->
|
|
try get_host_port(json:decode(iolist_to_binary(Body)))
|
|
catch
|
|
error:_ ->
|
|
gmhc_events:publish(error, ?ERR_EVT(#{error => invalid_json,
|
|
data => Body})),
|
|
{error, invalid_json}
|
|
end;
|
|
{error, _} = Error ->
|
|
gmhc_events:publish(error, ?ERR_EVT(#{error => connect_failure,
|
|
data => Error})),
|
|
Error
|
|
end.
|
|
|
|
get_host_port(#{ <<"address">> := Addr
|
|
, <<"port">> := Port
|
|
, <<"pool_id">> := PoolId } = Data) ->
|
|
?LOG_DEBUG("Data = ~p", [Data]),
|
|
{ok, #{ host => Addr
|
|
, port => Port
|
|
, pool_id => PoolId }}.
|
|
|
|
request(get, URL) ->
|
|
case request(get, URL, []) of
|
|
{ok, #{body := Body}} ->
|
|
{ok, Body};
|
|
Other ->
|
|
%% TODO: perhaps return a more informative reason?
|
|
gmhc_events:publish(error, ?ERR_EVT(#{error => get_failed,
|
|
url => URL,
|
|
data => Other })),
|
|
{error, failed}
|
|
end.
|
|
|
|
request(get, URL, []) ->
|
|
Headers = [],
|
|
HttpOpts = [{timeout, 15000}],
|
|
Opts = [],
|
|
Profile = default,
|
|
request_result(httpc:request(get, {URL, Headers}, HttpOpts, Opts, Profile)).
|
|
%% request(post, URL, Body) ->
|
|
%% post_request(URL, Body).
|
|
|
|
expand_url(URL) ->
|
|
case re:run(URL, <<"{[^}]+}">>, []) of
|
|
{match, _} ->
|
|
expand_vars(URL);
|
|
nomatch ->
|
|
URL
|
|
end.
|
|
|
|
expand_vars(S) ->
|
|
expand_vars(S, <<>>).
|
|
|
|
expand_vars(<<"{", Rest/binary>>, Acc) ->
|
|
{Var, Rest1} = get_var_name(Rest),
|
|
expand_vars(Rest1, <<Acc/binary, (expand_var(Var))/binary>>);
|
|
expand_vars(<<H, T/binary>>, Acc) ->
|
|
expand_vars(T, <<Acc/binary, H>>);
|
|
expand_vars(<<>>, Acc) ->
|
|
Acc.
|
|
|
|
expand_var(<<"CLIENT_ID">>) ->
|
|
gmhc_config:get_config([<<"pubkey">>]).
|
|
|
|
get_var_name(S) ->
|
|
get_var_name(S, <<>>).
|
|
|
|
get_var_name(<<"}", Rest/binary>>, Acc) ->
|
|
{Acc, Rest};
|
|
get_var_name(<<H, T/binary>>, Acc) ->
|
|
get_var_name(T, <<Acc/binary, H>>).
|
|
|
|
%% From hz.erl ==========================================================
|
|
|
|
% This is Bitcoin's variable-length unsigned integer encoding
|
|
% See: https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
%% vencode(N) when N =< 0 ->
|
|
%% {error, {non_pos_N, N}};
|
|
%% vencode(N) when N < 16#FD ->
|
|
%% {ok, <<N>>};
|
|
%% vencode(N) when N =< 16#FFFF ->
|
|
%% NBytes = eu(N, 2),
|
|
%% {ok, <<16#FD, NBytes/binary>>};
|
|
%% vencode(N) when N =< 16#FFFF_FFFF ->
|
|
%% NBytes = eu(N, 4),
|
|
%% {ok, <<16#FE, NBytes/binary>>};
|
|
%% vencode(N) when N < (2 bsl 64) ->
|
|
%% NBytes = eu(N, 8),
|
|
%% {ok, <<16#FF, NBytes/binary>>}.
|
|
|
|
|
|
% eu = encode unsigned (little endian with a given byte width)
|
|
% means add zero bytes to the end as needed
|
|
%% eu(N, Size) ->
|
|
%% Bytes = binary:encode_unsigned(N, little),
|
|
%% NExtraZeros = Size - byte_size(Bytes),
|
|
%% ExtraZeros = << <<0>> || _ <- lists:seq(1, NExtraZeros) >>,
|
|
%% <<Bytes/binary, ExtraZeros/binary>>.
|
|
|
|
%% ======================================================================
|
|
|
|
%% From gmplugin_web_demo_handler.erl ===================================
|
|
|
|
%% post_request(URL, Map) ->
|
|
%% ?LOG_DEBUG("Map = ~p", [Map]),
|
|
%% Body = json:encode(Map),
|
|
%% ?LOG_DEBUG("Body = ~s", [Body]),
|
|
%% PostRes = httpc:request(post, {URL, [], "application/json", Body}, [], []),
|
|
%% request_result(PostRes).
|
|
|
|
%% ======================================================================
|
|
|
|
request_result(Result) ->
|
|
?LOG_DEBUG("Request result: ~p", [Result]),
|
|
request_result_(Result).
|
|
|
|
request_result_({ok, {{_, C200, Ok}, _Hdrs, Body}}) when C200 >= 200, C200 < 300 ->
|
|
{ok, #{code => C200, msg => Ok, body => Body}};
|
|
request_result_({ok, {{_, C200, Ok}, Body}}) when C200 >= 200, C200 < 300 ->
|
|
{ok, #{code => C200, msg => Ok, body => Body}};
|
|
request_result_({ok, {{_, Code, Error}, _Hdrs, Body}}) ->
|
|
{error, #{code => Code, msg => Error, body => Body}};
|
|
request_result_(_) ->
|
|
{error, #{code => 500, msg => <<"Internal error">>, body => <<>>}}.
|