Compare commits
No commits in common. "823291986ec320b0c5cd695f4d6ebe4bcb4db445" and "4c09490f8a40c3beaa3cf0fa0cf0296698a1cf29" have entirely different histories.
823291986e
...
4c09490f8a
86
src/hz.erl
86
src/hz.erl
@ -9,7 +9,7 @@
|
|||||||
%%%
|
%%%
|
||||||
%%% The get/set admin functions are for setting or checking things like the Gajumaru
|
%%% The get/set admin functions are for setting or checking things like the Gajumaru
|
||||||
%%% "network ID" and list of addresses of nodes you want to use for answering
|
%%% "network ID" and list of addresses of nodes you want to use for answering
|
||||||
%%% queries to the blockchain. Get functions are arity 0, and set functions are arity 1.
|
%%% queries to the blockchain.
|
||||||
%%%
|
%%%
|
||||||
%%% The JSON query interface functions are the blockchain query functions themselves
|
%%% The JSON query interface functions are the blockchain query functions themselves
|
||||||
%%% which are translated to network queries and return Erlang messages as responses.
|
%%% which are translated to network queries and return Erlang messages as responses.
|
||||||
@ -18,8 +18,8 @@
|
|||||||
%%% a desired call to a smart contract on the chain to call data serialized in a form
|
%%% a desired call to a smart contract on the chain to call data serialized in a form
|
||||||
%%% that a Gajumaru compatible wallet or library can sign and submit to a Gajumaru node.
|
%%% that a Gajumaru compatible wallet or library can sign and submit to a Gajumaru node.
|
||||||
%%%
|
%%%
|
||||||
%%% NOTE:
|
%%% This module does not implement the OTP application behavior.
|
||||||
%%% This module does not implement the OTP application behavior. Refer to hakuzaru.erl.
|
%%% helper functions.
|
||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(hz).
|
-module(hz).
|
||||||
@ -225,7 +225,7 @@
|
|||||||
NetworkID :: string(),
|
NetworkID :: string(),
|
||||||
Reason :: term().
|
Reason :: term().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Returns the network ID or the atom `none' if unavailable.
|
%% Returns the network ID or the atom `none' if it is unset.
|
||||||
%% Checking this is not normally necessary, but if network ID assignment is dynamic
|
%% Checking this is not normally necessary, but if network ID assignment is dynamic
|
||||||
%% in your system it may be necessary to call this before attempting to form
|
%% in your system it may be necessary to call this before attempting to form
|
||||||
%% call data or perform other actions on chain that require a signature.
|
%% call data or perform other actions on chain that require a signature.
|
||||||
@ -241,9 +241,7 @@ network_id() ->
|
|||||||
%% @doc
|
%% @doc
|
||||||
%% Returns the list of currently assigned nodes.
|
%% Returns the list of currently assigned nodes.
|
||||||
%% The normal reason to call this is in preparation for altering the nodes list or
|
%% The normal reason to call this is in preparation for altering the nodes list or
|
||||||
%% checking the current list in debugging. Note that the first node in the list is
|
%% checking the current list in debugging.
|
||||||
%% the "sticky" node: the one that will be used for submitting transactions and
|
|
||||||
%% querying `next_nonce'.
|
|
||||||
|
|
||||||
chain_nodes() ->
|
chain_nodes() ->
|
||||||
hz_man:chain_nodes().
|
hz_man:chain_nodes().
|
||||||
@ -253,26 +251,19 @@ chain_nodes() ->
|
|||||||
when List :: [chain_node()],
|
when List :: [chain_node()],
|
||||||
Reason :: {invalid, [term()]}.
|
Reason :: {invalid, [term()]}.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Sets the chain nodes that will be queried whenever you communicate with the chain.
|
%% Sets the nodes that are intended to be used as your interface to the peer
|
||||||
|
%% network. The common situation is that your project runs a non-mining node as
|
||||||
|
%% part of your backend infrastructure. Typically one or two nodes is plenty, but
|
||||||
|
%% this may need to expand depending on how much query load your application generates.
|
||||||
|
%% The Hakuzaru manager will load balance by round-robin distribution.
|
||||||
%%
|
%%
|
||||||
%% The common situation is that a project runs a non-mining node as part of the backend
|
%% NOTE: When load balancing in this way be aware that there can be race conditions
|
||||||
%% infrastructure. Typically one or two nodes is plenty, but this may need to expand
|
%% among the backend nodes with regard to a single account's current nonce when performing
|
||||||
%% depending on how much query load your application generates.
|
%% contract calls in quick succession. Round robin distribution is extremely useful when
|
||||||
%%
|
%% performing rapid lookups to the chain, but does not work well when submitting many
|
||||||
%% There are two situations: one node, or multiple nodes.
|
%% transactions to the chain from a single user in a short period of time. A future version
|
||||||
%%
|
%% of this library will allow the caller to designate a single node as "sticky" to be used
|
||||||
%% Single node:
|
%% exclusively in the case of nonce reads and TX submissions.
|
||||||
%% In the case of a single node, everything passes through that one node. Duh.
|
|
||||||
%%
|
|
||||||
%% Multiple nodes:
|
|
||||||
%% In the case of multiple nodes a distinction is made between the node to which
|
|
||||||
%% transactions that update the chain state are made and to which `next_nonce' queries
|
|
||||||
%% are made, and nodes that are used for read-only queries. The node to which stateful
|
|
||||||
%% transactions are submitted is called the "sticky node". This is the first node
|
|
||||||
%% (head position) in the list of nodes submitted to the chain when `chain_nodes/1'
|
|
||||||
%% is called. If using multiple nodes but the sticky node should also be used for
|
|
||||||
%% read-only queries, submit the sticky node at the head of the list and again in
|
|
||||||
%% the tail.
|
|
||||||
|
|
||||||
chain_nodes(List) when is_list(List) ->
|
chain_nodes(List) when is_list(List) ->
|
||||||
hz_man:chain_nodes(List).
|
hz_man:chain_nodes(List).
|
||||||
@ -280,16 +271,7 @@ chain_nodes(List) when is_list(List) ->
|
|||||||
|
|
||||||
-spec tls() -> boolean().
|
-spec tls() -> boolean().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Check whether TLS is in use. The typical situation is to not use TLS as nodes that
|
%% Check whether TLS is in use.
|
||||||
%% serve as part of the backend of an application are typically run in the same
|
|
||||||
%% backend network as the application service. When accessing chain nodes over the WAN
|
|
||||||
%% however, TLS is strongly recommended to avoid a MITM attack.
|
|
||||||
%%
|
|
||||||
%% In this version of Hakuzaru TLS is either on or off for all nodes, making a mixed
|
|
||||||
%% infrastructure complicated to support without two Hakuzaru instances. This will
|
|
||||||
%% likely become a per-node setting in the future.
|
|
||||||
%%
|
|
||||||
%% TLS defaults to `false'.
|
|
||||||
|
|
||||||
tls() ->
|
tls() ->
|
||||||
hz_man:tls().
|
hz_man:tls().
|
||||||
@ -299,8 +281,6 @@ tls() ->
|
|||||||
%% @doc
|
%% @doc
|
||||||
%% Set TLS true or false. That's what a boolean is, by the way, `true' or `false'.
|
%% Set TLS true or false. That's what a boolean is, by the way, `true' or `false'.
|
||||||
%% This is a condescending comment. That means I am talking down to you.
|
%% This is a condescending comment. That means I am talking down to you.
|
||||||
%%
|
|
||||||
%% TLS defaults to `false'.
|
|
||||||
|
|
||||||
tls(Boolean) ->
|
tls(Boolean) ->
|
||||||
hz_man:tls(Boolean).
|
hz_man:tls(Boolean).
|
||||||
@ -311,8 +291,6 @@ tls(Boolean) ->
|
|||||||
when Timeout :: pos_integer() | infinity.
|
when Timeout :: pos_integer() | infinity.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Returns the current request timeout setting in milliseconds.
|
%% Returns the current request timeout setting in milliseconds.
|
||||||
%% The default timeout is 5,000ms.
|
|
||||||
%% The max timeout is 120,000ms.
|
|
||||||
|
|
||||||
timeout() ->
|
timeout() ->
|
||||||
hz_man:timeout().
|
hz_man:timeout().
|
||||||
@ -322,8 +300,6 @@ timeout() ->
|
|||||||
when MS :: pos_integer() | infinity.
|
when MS :: pos_integer() | infinity.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Sets the request timeout in milliseconds.
|
%% Sets the request timeout in milliseconds.
|
||||||
%% The default timeout is 5,000ms.
|
|
||||||
%% The max timeout is 120,000ms.
|
|
||||||
|
|
||||||
timeout(MS) ->
|
timeout(MS) ->
|
||||||
hz_man:timeout(MS).
|
hz_man:timeout(MS).
|
||||||
@ -600,18 +576,18 @@ acc_pending_txs(AccountID) ->
|
|||||||
%% Retrieve the next nonce for the given account
|
%% Retrieve the next nonce for the given account
|
||||||
|
|
||||||
next_nonce(AccountID) ->
|
next_nonce(AccountID) ->
|
||||||
case request_sticky(["/v3/accounts/", AccountID, "/next-nonce"]) of
|
% case request(["/v3/accounts/", AccountID, "/next-nonce"]) of
|
||||||
{ok, #{"next_nonce" := Nonce}} -> {ok, Nonce};
|
% {ok, #{"next_nonce" := Nonce}} -> {ok, Nonce};
|
||||||
{ok, #{"reason" := "Account not found"}} -> {ok, 1};
|
|
||||||
{ok, #{"reason" := Reason}} -> {error, Reason};
|
|
||||||
Error -> Error
|
|
||||||
end.
|
|
||||||
% case request_sticky(["/v3/accounts/", AccountID]) of
|
|
||||||
% {ok, #{"nonce" := Nonce}} -> {ok, Nonce + 1};
|
|
||||||
% {ok, #{"reason" := "Account not found"}} -> {ok, 1};
|
% {ok, #{"reason" := "Account not found"}} -> {ok, 1};
|
||||||
% {ok, #{"reason" := Reason}} -> {error, Reason};
|
% {ok, #{"reason" := Reason}} -> {error, Reason};
|
||||||
% Error -> Error
|
% Error -> Error
|
||||||
% end.
|
% end.
|
||||||
|
case request(["/v3/accounts/", AccountID]) of
|
||||||
|
{ok, #{"nonce" := Nonce}} -> {ok, Nonce + 1};
|
||||||
|
{ok, #{"reason" := "Account not found"}} -> {ok, 1};
|
||||||
|
{ok, #{"reason" := Reason}} -> {error, Reason};
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec dry_run(TX) -> {ok, Result} | {error, Reason}
|
-spec dry_run(TX) -> {ok, Result} | {error, Reason}
|
||||||
@ -753,7 +729,7 @@ tx_info(ID) ->
|
|||||||
|
|
||||||
post_tx(Data) when is_binary(Data) ->
|
post_tx(Data) when is_binary(Data) ->
|
||||||
JSON = zj:binary_encode(#{tx => Data}),
|
JSON = zj:binary_encode(#{tx => Data}),
|
||||||
request_sticky("/v3/transactions", JSON);
|
request("/v3/transactions", JSON);
|
||||||
post_tx(Data) when is_list(Data) ->
|
post_tx(Data) when is_list(Data) ->
|
||||||
post_tx(list_to_binary(Data)).
|
post_tx(list_to_binary(Data)).
|
||||||
|
|
||||||
@ -865,14 +841,6 @@ status_chainends() ->
|
|||||||
request("/v3/status/chain-ends").
|
request("/v3/status/chain-ends").
|
||||||
|
|
||||||
|
|
||||||
request_sticky(Path) ->
|
|
||||||
hz_man:request_sticky(unicode:characters_to_list(Path)).
|
|
||||||
|
|
||||||
|
|
||||||
request_sticky(Path, Payload) ->
|
|
||||||
hz_man:request_sticky(unicode:characters_to_list(Path), Payload).
|
|
||||||
|
|
||||||
|
|
||||||
request(Path) ->
|
request(Path) ->
|
||||||
hz_man:request(unicode:characters_to_list(Path)).
|
hz_man:request(unicode:characters_to_list(Path)).
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-license("MIT").
|
-license("MIT").
|
||||||
|
|
||||||
-export([connect/4, connect_slowly/4]).
|
-export([connect/4, slowly_connect/4]).
|
||||||
|
|
||||||
|
|
||||||
connect(Node = {Host, Port}, Request, From, Timeout) ->
|
connect(Node = {Host, Port}, Request, From, Timeout) ->
|
||||||
@ -206,7 +206,7 @@ read_hval(_, Received, _, _, _) ->
|
|||||||
{error, headers}.
|
{error, headers}.
|
||||||
|
|
||||||
|
|
||||||
connect_slowly(Node, {get, Path}, From, Timeout) ->
|
slowly_connect(Node, {get, Path}, From, Timeout) ->
|
||||||
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
|
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
|
||||||
URL = lists:flatten(url(Node, Path)),
|
URL = lists:flatten(url(Node, Path)),
|
||||||
Request = {URL, []},
|
Request = {URL, []},
|
||||||
@ -217,7 +217,7 @@ connect_slowly(Node, {get, Path}, From, Timeout) ->
|
|||||||
BAD -> {error, BAD}
|
BAD -> {error, BAD}
|
||||||
end,
|
end,
|
||||||
gen_server:reply(From, Result);
|
gen_server:reply(From, Result);
|
||||||
connect_slowly(Node, {post, Path, Payload}, From, Timeout) ->
|
slowly_connect(Node, {post, Path, Payload}, From, Timeout) ->
|
||||||
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
|
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
|
||||||
URL = lists:flatten(url(Node, Path)),
|
URL = lists:flatten(url(Node, Path)),
|
||||||
Request = {URL, [], "application/json", Payload},
|
Request = {URL, [], "application/json", Payload},
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
timeout/0, timeout/1]).
|
timeout/0, timeout/1]).
|
||||||
|
|
||||||
%% The whole point of this module:
|
%% The whole point of this module:
|
||||||
-export([request_sticky/1, request_sticky/2, request/1, request/2]).
|
-export([request/1, request/2]).
|
||||||
|
|
||||||
%% gen_server goo
|
%% gen_server goo
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
@ -94,25 +94,6 @@ timeout(Value) when 0 < Value, Value =< 120000 ->
|
|||||||
gen_server:cast(?MODULE, {timeout, Value}).
|
gen_server:cast(?MODULE, {timeout, Value}).
|
||||||
|
|
||||||
|
|
||||||
-spec request_sticky(Path) -> {ok, Value} | {error, Reason}
|
|
||||||
when Path :: unicode:charlist(),
|
|
||||||
Value :: map(),
|
|
||||||
Reason :: hz:chain_error().
|
|
||||||
|
|
||||||
request_sticky(Path) ->
|
|
||||||
gen_server:call(?MODULE, {request_sticky, {get, Path}}, infinity).
|
|
||||||
|
|
||||||
|
|
||||||
-spec request_sticky(Path, Data) -> {ok, Value} | {error, Reason}
|
|
||||||
when Path :: unicode:charlist(),
|
|
||||||
Data :: unicode:charlist(),
|
|
||||||
Value :: map(),
|
|
||||||
Reason :: hz:chain_error().
|
|
||||||
|
|
||||||
request_sticky(Path, Data) ->
|
|
||||||
gen_server:call(?MODULE, {request_sticky, {post, Path, Data}}, infinity).
|
|
||||||
|
|
||||||
|
|
||||||
-spec request(Path) -> {ok, Value} | {error, Reason}
|
-spec request(Path) -> {ok, Value} | {error, Reason}
|
||||||
when Path :: unicode:charlist(),
|
when Path :: unicode:charlist(),
|
||||||
Value :: map(),
|
Value :: map(),
|
||||||
@ -164,13 +145,10 @@ init(none) ->
|
|||||||
handle_call({request, Request}, From, State) ->
|
handle_call({request, Request}, From, State) ->
|
||||||
NewState = do_request(Request, From, State),
|
NewState = do_request(Request, From, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
handle_call({request_sticky, Request}, From, State) ->
|
|
||||||
NewState = do_request_sticky(Request, From, State),
|
|
||||||
{noreply, NewState};
|
|
||||||
handle_call(tls, _, State = #s{tls = TLS}) ->
|
handle_call(tls, _, State = #s{tls = TLS}) ->
|
||||||
{reply, TLS, State};
|
{reply, TLS, State};
|
||||||
handle_call(chain_nodes, _, State) ->
|
handle_call(chain_nodes, _, State = #s{chain_nodes = {Wait, Used}}) ->
|
||||||
Nodes = do_chain_nodes(State),
|
Nodes = lists:append(Wait, Used),
|
||||||
{reply, Nodes, State};
|
{reply, Nodes, State};
|
||||||
handle_call(timeout, _, State = #s{timeout = Value}) ->
|
handle_call(timeout, _, State = #s{timeout = Value}) ->
|
||||||
{reply, Value, State};
|
{reply, Value, State};
|
||||||
@ -182,9 +160,10 @@ handle_call(Unexpected, From, State) ->
|
|||||||
handle_cast({tls, Boolean}, State) ->
|
handle_cast({tls, Boolean}, State) ->
|
||||||
NewState = do_tls(Boolean, State),
|
NewState = do_tls(Boolean, State),
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
handle_cast({chain_nodes, List}, State) ->
|
handle_cast({chain_nodes, []}, State) ->
|
||||||
NewState = do_chain_nodes(List, State),
|
{noreply, State#s{chain_nodes = {[], []}}};
|
||||||
{noreply, NewState};
|
handle_cast({chain_nodes, ToUse}, State) ->
|
||||||
|
{noreply, State#s{chain_nodes = {ToUse, []}}};
|
||||||
handle_cast({timeout, Value}, State) ->
|
handle_cast({timeout, Value}, State) ->
|
||||||
{noreply, State#s{timeout = Value}};
|
{noreply, State#s{timeout = Value}};
|
||||||
handle_cast(Unexpected, State) ->
|
handle_cast(Unexpected, State) ->
|
||||||
@ -239,23 +218,6 @@ terminate(_, _) ->
|
|||||||
|
|
||||||
%%% Doer Functions
|
%%% Doer Functions
|
||||||
|
|
||||||
do_chain_nodes(#s{sticky = none, chain_nodes = {Wait, Used}}) ->
|
|
||||||
lists:append(Wait, Used);
|
|
||||||
do_chain_nodes(#s{sticky = Sticky, chain_nodes = {Wait, Used}}) ->
|
|
||||||
case lists:append(Wait, Used) of
|
|
||||||
[Sticky] -> [Sticky];
|
|
||||||
Nodes -> [Sticky | Nodes]
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
do_chain_nodes([], State) ->
|
|
||||||
State#s{sticky = none, chain_nodes = {[], []}};
|
|
||||||
do_chain_nodes(List = [Sticky], State) ->
|
|
||||||
State#s{sticky = Sticky, chain_nodes = {List, []}};
|
|
||||||
do_chain_nodes([Sticky | List], State) ->
|
|
||||||
State#s{sticky = Sticky, chain_nodes = {List, []}}.
|
|
||||||
|
|
||||||
|
|
||||||
do_tls(true, State) ->
|
do_tls(true, State) ->
|
||||||
ok = ssl:start(),
|
ok = ssl:start(),
|
||||||
State#s{tls = true};
|
State#s{tls = true};
|
||||||
@ -265,21 +227,17 @@ do_tls(_, State) ->
|
|||||||
State.
|
State.
|
||||||
|
|
||||||
|
|
||||||
do_request_sticky(_, From, State = #s{sticky = none}) ->
|
do_request(_, From, State = #s{chain_nodes = {[], []}}) ->
|
||||||
ok = gen_server:reply(From, {error, no_nodes}),
|
ok = gen_server:reply(From, {error, no_nodes}),
|
||||||
State;
|
State;
|
||||||
do_request_sticky(Request,
|
do_request(Request,
|
||||||
From,
|
From,
|
||||||
State = #s{tls = TLS,
|
State = #s{tls = false,
|
||||||
fetchers = Fetchers,
|
fetchers = Fetchers,
|
||||||
sticky = Node,
|
chain_nodes = {[Node | Rest], Used},
|
||||||
timeout = Timeout}) ->
|
timeout = Timeout}) ->
|
||||||
Now = erlang:system_time(nanosecond),
|
Now = erlang:system_time(nanosecond),
|
||||||
Fetcher =
|
Fetcher = fun() -> hz_fetcher:connect(Node, Request, From, Timeout) end,
|
||||||
case TLS of
|
|
||||||
true -> fun() -> hz_fetcher:connect_slowly(Node, Request, From, Timeout) end;
|
|
||||||
false -> fun() -> hz_fetcher:connect(Node, Request, From, Timeout) end
|
|
||||||
end,
|
|
||||||
{PID, Mon} = spawn_monitor(Fetcher),
|
{PID, Mon} = spawn_monitor(Fetcher),
|
||||||
New = #fetcher{pid = PID,
|
New = #fetcher{pid = PID,
|
||||||
mon = Mon,
|
mon = Mon,
|
||||||
@ -287,24 +245,15 @@ do_request_sticky(Request,
|
|||||||
node = Node,
|
node = Node,
|
||||||
from = From,
|
from = From,
|
||||||
req = Request},
|
req = Request},
|
||||||
State#s{fetchers = [New | Fetchers]}.
|
State#s{fetchers = [New | Fetchers], chain_nodes = {Rest, [Node | Used]}};
|
||||||
|
|
||||||
|
|
||||||
do_request(_, From, State = #s{chain_nodes = {[], []}}) ->
|
|
||||||
ok = gen_server:reply(From, {error, no_nodes}),
|
|
||||||
State;
|
|
||||||
do_request(Request,
|
do_request(Request,
|
||||||
From,
|
From,
|
||||||
State = #s{tls = TLS,
|
State = #s{tls = true,
|
||||||
fetchers = Fetchers,
|
fetchers = Fetchers,
|
||||||
chain_nodes = {[Node | Rest], Used},
|
chain_nodes = {[Node | Rest], Used},
|
||||||
timeout = Timeout}) ->
|
timeout = Timeout}) ->
|
||||||
Now = erlang:system_time(nanosecond),
|
Now = erlang:system_time(nanosecond),
|
||||||
Fetcher =
|
Fetcher = fun() -> hz_fetcher:slowly_connect(Node, Request, From, Timeout) end,
|
||||||
case TLS of
|
|
||||||
true -> fun() -> hz_fetcher:connect_slowly(Node, Request, From, Timeout) end;
|
|
||||||
false -> fun() -> hz_fetcher:connect(Node, Request, From, Timeout) end
|
|
||||||
end,
|
|
||||||
{PID, Mon} = spawn_monitor(Fetcher),
|
{PID, Mon} = spawn_monitor(Fetcher),
|
||||||
New = #fetcher{pid = PID,
|
New = #fetcher{pid = PID,
|
||||||
mon = Mon,
|
mon = Mon,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user