Compare commits

..

13 Commits

Author SHA1 Message Date
Jarvis Carroll
d66b8a0af9 remaining types
`tx` isn't defined in all the same places that pointee, name, base_tx,
fr, fp are defined, but actually it is the only one not in the list I
was looking at, so we are all good. As demonstration, there is also a
test case for Set.set, despite Set.set not being defined as a builtin
type.
2025-09-26 20:46:12 +10:00
Jarvis Carroll
2abb15cceb Add more builtin types
We probably should extract these from the standard library instead of
cherry picking the ones that are needed by the chain? e.g. Chain.tx
still doesn't work.
2025-09-26 09:05:59 +10:00
Jarvis Carroll
057861e9cf Add a map for builtin types
This makes it much easier to implement all these standard library
things.

In doing so I changed the convention for option, hash, unit, to be
stringy rather than atoms.

Also I changed some error messages based on what was more helpful during
debugging of the unit tests.
2025-09-24 16:27:32 +10:00
Jarvis Carroll
7ffc96b68a Refactor type normalization
Some of these checks were redundant, and we probably don't actually need
substitution to wrap success/failure, since it isn't expected to fail
anyway... Now the logic is much simpler, and adding more built-in type
definitions should be easy.
2025-09-24 14:10:29 +10:00
Jarvis Carroll
a1be8cbc14 coerce hashes
It turns out there are a lot of types that, like option, should only be
valid as an opaque/normalized type, but should be substituted for
something different in the flat representation. If we restructure things
a little then we can implement all of these in one go.
2025-09-23 19:22:30 +10:00
Jarvis Carroll
3822bb69c9 Coerce binaries as-is
Sophia accepts both sg_... and #... as signatures, so we should probably
accept binaries as signatures directly. People might expect to be able
to put the listy string "#..." in too, but that is more complex to do.
2025-09-23 17:34:55 +10:00
Jarvis Carroll
6506fc91bd Also coerce unicode strings to FATE
This is mainly so that gajudesk can pass text box content to hz as-is,
but also allows users to pass utf8 binaries in, if they want to, for
some reason.
2025-09-23 17:07:41 +10:00
Jarvis Carroll
68a0c3d623 coerce character
It's really just an integer... Should we flatten it to an integer
instead? I don't know.
2025-09-23 16:00:56 +10:00
Jarvis Carroll
d3fb598506 coerce bits
The thing to remember about bits is that they are actually integers...
It is tempting to present bits as binaries, but that hides the nuance of
the infinite leading zeroes, the potential for infinite leading ones,
etc.
2025-09-23 15:42:53 +10:00
Jarvis Carroll
4ed2bd0cd1 coerce bytes 2025-09-23 14:35:42 +10:00
Jarvis Carroll
0caf5a61c7 coerce stringy booleans 2025-09-23 12:44:20 +10:00
Jarvis Carroll
7704d82c6f serialize signatures
This took a surprising number of goose chases to work out... I had to
find out
- what is the gmser prefix for a signature (sg_)
- what is the gmb wrapper for a signature (none)
- what errors gmser can report when a signature is invalid
- what an example of a valid signature is
- what that example signature serializes to
2025-09-23 12:39:07 +10:00
Jarvis Carroll
a6f58a95e2 add more atoms to AACI 2025-09-18 23:31:11 +10:00
9 changed files with 85 additions and 253 deletions

View File

@ -3,7 +3,7 @@
{included_applications,[]},
{applications,[stdlib,kernel]},
{description,"Gajumaru interoperation library"},
{vsn,"0.7.0"},
{vsn,"0.6.1"},
{modules,[hakuzaru,hz,hz_fetcher,hz_grids,hz_key_master,hz_man,
hz_sup]},
{mod,{hakuzaru,[]}}]}.

View File

@ -6,7 +6,7 @@
%%% @end
-module(hakuzaru).
-vsn("0.7.0").
-vsn("0.6.1").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").

View File

@ -9,7 +9,7 @@
%%%
%%% 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
%%% 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
%%% which are translated to network queries and return Erlang messages as responses.
@ -18,12 +18,12 @@
%%% 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.
%%%
%%% NOTE:
%%% This module does not implement the OTP application behavior. Refer to hakuzaru.erl.
%%% This module does not implement the OTP application behavior.
%%% helper functions.
%%% @end
-module(hz).
-vsn("0.7.0").
-vsn("0.6.1").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").
@ -73,8 +73,8 @@
decode_bytearray_fate/1, decode_bytearray/2,
spend/5, spend/10,
sign_tx/2, sign_tx/3,
sign_message/2, verify_signature/3,
sign_binary/2, verify_bin_signature/3]).
sign_message/2,
verify_signature/3]).
%%% Types
@ -225,7 +225,7 @@
NetworkID :: string(),
Reason :: term().
%% @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
%% 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.
@ -241,9 +241,7 @@ network_id() ->
%% @doc
%% Returns the list of currently assigned nodes.
%% 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
%% the "sticky" node: the one that will be used for submitting transactions and
%% querying `next_nonce'.
%% checking the current list in debugging.
chain_nodes() ->
hz_man:chain_nodes().
@ -253,26 +251,19 @@ chain_nodes() ->
when List :: [chain_node()],
Reason :: {invalid, [term()]}.
%% @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
%% infrastructure. Typically one or two nodes is plenty, but this may need to expand
%% depending on how much query load your application generates.
%%
%% There are two situations: one node, or multiple nodes.
%%
%% Single node:
%% 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.
%% NOTE: When load balancing in this way be aware that there can be race conditions
%% among the backend nodes with regard to a single account's current nonce when performing
%% 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
%% 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
%% exclusively in the case of nonce reads and TX submissions.
chain_nodes(List) when is_list(List) ->
hz_man:chain_nodes(List).
@ -280,16 +271,7 @@ chain_nodes(List) when is_list(List) ->
-spec tls() -> boolean().
%% @doc
%% Check whether TLS is in use. The typical situation is to not use TLS as nodes that
%% 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'.
%% Check whether TLS is in use.
tls() ->
hz_man:tls().
@ -299,8 +281,6 @@ tls() ->
%% @doc
%% 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.
%%
%% TLS defaults to `false'.
tls(Boolean) ->
hz_man:tls(Boolean).
@ -311,8 +291,6 @@ tls(Boolean) ->
when Timeout :: pos_integer() | infinity.
%% @doc
%% Returns the current request timeout setting in milliseconds.
%% The default timeout is 5,000ms.
%% The max timeout is 120,000ms.
timeout() ->
hz_man:timeout().
@ -322,8 +300,6 @@ timeout() ->
when MS :: pos_integer() | infinity.
%% @doc
%% Sets the request timeout in milliseconds.
%% The default timeout is 5,000ms.
%% The max timeout is 120,000ms.
timeout(MS) ->
hz_man:timeout(MS).
@ -600,18 +576,18 @@ acc_pending_txs(AccountID) ->
%% Retrieve the next nonce for the given account
next_nonce(AccountID) ->
case request_sticky(["/v3/accounts/", AccountID, "/next-nonce"]) of
{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};
% case request(["/v3/accounts/", AccountID, "/next-nonce"]) of
% {ok, #{"next_nonce" := Nonce}} -> {ok, Nonce};
% {ok, #{"reason" := "Account not found"}} -> {ok, 1};
% {ok, #{"reason" := Reason}} -> {error, Reason};
% Error -> Error
% 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}
@ -753,7 +729,7 @@ tx_info(ID) ->
post_tx(Data) when is_binary(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(list_to_binary(Data)).
@ -865,14 +841,6 @@ status_chainends() ->
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) ->
hz_man:request(unicode:characters_to_list(Path)).
@ -922,7 +890,7 @@ contract_create(CreatorID, Path, InitArgs) ->
when CreatorID :: pubkey(),
Nonce :: pos_integer(),
Amount :: non_neg_integer(),
TTL :: non_neg_integer(),
TTL :: pos_integer(),
Gas :: pos_integer(),
GasPrice :: pos_integer(),
Path :: file:filename(),
@ -1175,10 +1143,9 @@ assemble_calldata2(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallDat
read_aci(Path) ->
case file:read_file(Path) of
{ok, Bin} ->
try
{ok, binary_to_term(Bin, [safe])}
catch
error:badarg -> {error, bad_aci}
case zx_lib:b_to_ts(Bin) of
error -> {error, bad_aci};
OK -> OK
end;
Error ->
Error
@ -1259,7 +1226,7 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) ->
Gas :: pos_integer(),
GasPrice :: pos_integer(),
Amount :: non_neg_integer(),
TTL :: non_neg_integer(),
TTL :: pos_integer(),
AACI :: aaci(),
ConID :: unicode:chardata(),
Fun :: string(),
@ -1888,11 +1855,9 @@ coerce({_, _, contract}, {contract, Bin}, from_fate) ->
Address = gmser_api_encoder:encode(contract_pubkey, Bin),
{ok, unicode:characters_to_list(Address)};
coerce({_, _, signature}, S, to_fate) when is_binary(S) andalso (byte_size(S) =:= 64) ->
% Usually to pass a binary in, you need to wrap it as {raw, Binary}, but
% since sg_... strings OR hex blobs can be used as signatures in Sophia, we
% special case this case based on the length. Even if a binary starts with
% "sg_", 64 characters is not enough to represent a 64 byte signature, so
% the most optimistic interpretation is to use the binary directly.
% If it is a binary of 64 bytes then it can be used as is... If it is an
% sg_... string of 64 bytes, then it is too short to be valid, so just
% interpret it as a binary directly.
{ok, S};
coerce({O, N, signature}, S, to_fate) ->
coerce_chain_object(O, N, signature, signature, S);
@ -2008,8 +1973,6 @@ coerce_bytes(O, N, Count, Bytes) when byte_size(Bytes) /= Count ->
coerce_bytes(_, _, _, Bytes) ->
{ok, Bytes}.
coerce_chain_object(_, _, _, _, {raw, Binary}) ->
{ok, Binary};
coerce_chain_object(O, N, T, Tag, S) ->
case decode_chain_object(Tag, S) of
{ok, Data} -> {ok, coerce_chain_object2(T, Data)};
@ -2431,7 +2394,7 @@ spend3(DSenderID,
Sig :: binary().
sign_message(Message, SecKey) ->
Prefix = message_sig_prefix(),
Prefix = <<"Gajumaru Signed Message:\n">>,
{ok, PSize} = vencode(byte_size(Prefix)),
{ok, MSize} = vencode(byte_size(Message)),
Smashed = iolist_to_binary([PSize, Prefix, MSize, Message]),
@ -2440,7 +2403,7 @@ sign_message(Message, SecKey) ->
-spec verify_signature(Sig, Message, PubKey) -> Result
when Sig :: string(), % base64 encoded signature,
when Sig :: binary(),
Message :: iodata(),
PubKey :: pubkey(),
Result :: {ok, Outcome :: boolean()}
@ -2465,7 +2428,7 @@ verify_signature2(Sig, Message, PK) ->
% the user from accidentally signing a transaction disguised as a message.
%
% Salt the message then hash with blake2b.
Prefix = message_sig_prefix(),
Prefix = <<"Gajumaru Signed Message:\n">>,
{ok, PSize} = vencode(byte_size(Prefix)),
{ok, MSize} = vencode(byte_size(Message)),
Smashed = iolist_to_binary([PSize, Prefix, MSize, Message]),
@ -2475,7 +2438,6 @@ verify_signature2(Sig, Message, PK) ->
Result = ecu_eddsa:sign_verify_detached(Signature, Hashed, PK),
{ok, Result}.
message_sig_prefix() -> <<"Gajumaru Signed Message:\n">>.
% This is Bitcoin's variable-length unsigned integer encoding
% See: https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
@ -2503,42 +2465,6 @@ eu(N, Size) ->
<<Bytes/binary, ExtraZeros/binary>>.
-spec sign_binary(Binary, SecKey) -> Sig
when Binary :: binary(),
SecKey :: binary(),
Sig :: binary().
sign_binary(Binary, SecKey) ->
Prefix = binary_sig_prefix(),
Target = <<Prefix/binary, Binary/binary>>,
{ok, Hash} = eblake2:blake2b(32, Target),
ecu_eddsa:sign_detached(Hash, SecKey).
-spec verify_bin_signature(Sig, Binary, PubKey) -> Result
when Sig :: string(), % base64 encoded signature,
Binary :: binary(),
PubKey :: pubkey(),
Result :: {ok, Outcome :: boolean()}
| {error, Reason :: term()}.
verify_bin_signature(Sig, Binary, PubKey) ->
case gmser_api_encoder:decode(PubKey) of
{account_pubkey, PK} -> verify_bin_signature2(Sig, Binary, PK);
Other -> {error, {bad_key, Other}}
end.
verify_bin_signature2(Sig, Binary, PK) ->
Prefix = binary_sig_prefix(),
Target = <<Prefix/binary, Binary/binary>>,
{ok, Hash} = eblake2:blake2b(32, Target),
Signature = base64:decode(Sig),
Result = ecu_eddsa:sign_verify_detached(Signature, Hash, PK),
{ok, Result}.
binary_sig_prefix() -> <<"Gajumaru Signed Binary:">>.
%%% Debug functionality
% debug_network() ->
@ -2637,7 +2563,6 @@ coerce_signature_binary_test() ->
169,85,212,142,14,12,233,252,97,50,193,158,229,51,123,206,222,
249,2,3,85,173,106,150,243,253,89,128,248,52,195,140,95,114,
233,110,119,143,206,137,124,36,63,154,85,7>>,
{ok, Binary} = coerce(Type, {raw, Binary}, to_fate),
{ok, Binary} = coerce(Type, Binary, to_fate),
ok.

View File

@ -1,10 +1,12 @@
-module(hz_fetcher).
-vsn("0.7.0").
-vsn("0.6.1").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("MIT").
-export([connect/4, connect_slowly/4]).
-export([connect/4, slowly_connect/4]).
-include("$zx_include/zx_logger.hrl").
connect(Node = {Host, Port}, Request, From, Timeout) ->
@ -76,7 +78,7 @@ parse(Received, Sock, From, Timer) ->
<<"HTTP/1.1 500 Internal Server Error\r\n", Tail/binary>> ->
parse2(500, Tail, Sock, From, Timer);
_ ->
ok = disconnect(Sock),
ok = zx_net:disconnect(Sock),
ok = erlang:cancel_timer(Timer, [{async, true}]),
gen_server:reply(From, {error, {received, Received}})
end.
@ -113,7 +115,7 @@ consume2(Length, Received, Sock, From, Timer) ->
if
Size == Length ->
ok = erlang:cancel_timer(Timer, [{async, true}]),
ok = disconnect(Sock),
ok = zx_net:disconnect(Sock),
Result = zj:decode(Received),
gen_server:reply(From, Result);
Size < Length ->
@ -206,7 +208,7 @@ read_hval(_, Received, _, _, _) ->
{error, headers}.
connect_slowly(Node, {get, Path}, From, Timeout) ->
slowly_connect(Node, {get, Path}, From, Timeout) ->
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
URL = lists:flatten(url(Node, Path)),
Request = {URL, []},
@ -217,7 +219,7 @@ connect_slowly(Node, {get, Path}, From, Timeout) ->
BAD -> {error, BAD}
end,
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}],
URL = lists:flatten(url(Node, Path)),
Request = {URL, [], "application/json", Payload},
@ -234,45 +236,3 @@ url({Node, Port}, Path) when is_list(Node) ->
["https://", Node, ":", integer_to_list(Port), Path];
url({Node, Port}, Path) when is_tuple(Node) ->
["https://", inet:ntoa(Node), ":", integer_to_list(Port), Path].
disconnect(Socket) ->
case peername(Socket) of
{ok, {Addr, Port}} ->
Host = inet:ntoa(Addr),
disconnect(Socket, Host, Port);
{error, Reason} ->
log(warning, "Disconnect failed with: ~w", [Reason])
end.
disconnect(Socket, Host, Port) ->
case gen_tcp:shutdown(Socket, read_write) of
ok ->
ok;
{error, enotconn} ->
receive
{tcp_closed, Socket} -> ok
after 0 -> ok
end;
{error, E} ->
ok = log(warning, "~ts:~w disconnect failed with: ~w", [Host, Port, E]),
receive
{tcp_closed, Socket} -> ok
after 0 -> ok
end
end.
peername(Socket) ->
case inet:peername(Socket) of
{ok, {{0, 0, 0, 0, 0, 65535, X, Y}, Port}} ->
<<A:8, B:8, C:8, D:8>> = <<X:16, Y:16>>,
{ok, {{A, B, C, D}, Port}};
Other ->
Other
end.
log(Level, Format, Args) ->
Raw = io_lib:format("~w ~w: " ++ Format, [?MODULE, self() | Args]),
Entry = unicode:characters_to_list(Raw),
logger:log(Level, Entry).

View File

@ -37,7 +37,7 @@
%%% @end
-module(hz_grids).
-vsn("0.7.0").
-vsn("0.6.1").
-export([url/2, parse/1, req/2, req/3]).

View File

@ -8,7 +8,7 @@
%%% @end
-module(hz_key_master).
-vsn("0.7.0").
-vsn("0.6.1").
-export([make_key/1, encode/1, decode/1]).
@ -91,8 +91,9 @@ chunksize(N, C, A) -> chunksize(N div C, C, A + 1).
read_words() ->
ModPath = code:which(?MODULE),
Path = filename:join([filename:dirname(filename:dirname(ModPath)), "priv", "words4096.txt"]),
{ok, V} = zx_lib:string_to_version(proplists:get_value(vsn, module_info(attributes))),
HZ_Lib = zx_lib:ppath(lib, {"otpr", "hakuzaru", V}),
Path = filename:join([HZ_Lib, "priv", "words4096.txt"]),
{ok, Bin} = file:read_file(Path),
string:lexemes(Bin, "\n").

View File

@ -9,7 +9,7 @@
%%% @end
-module(hz_man).
-vsn("0.7.0").
-vsn("0.6.1").
-behavior(gen_server).
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
@ -21,13 +21,16 @@
timeout/0, timeout/1]).
%% 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
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
%% TODO: Make logging more flexible
-include("$zx_include/zx_logger.hrl").
%%% Type and Record Definitions
@ -40,11 +43,11 @@
req = none :: none | binary()}).
-record(s,
{tls = false :: boolean(),
chain_nodes = {[], []} :: {[hz:chain_node()], [hz:chain_node()]},
sticky = none :: none | hz:chain_node(),
fetchers = [] :: [#fetcher{}],
timeout = 5000 :: pos_integer()}).
{tls = false :: boolean(),
chain_nodes = {[], []} :: {[hz:chain_node()], [hz:chain_node()]},
sticky = none :: none | hz:chain_node(),
fetchers = [] :: [#fetcher{}],
timeout = 5000 :: pos_integer()}).
-type state() :: #s{}.
@ -94,25 +97,6 @@ timeout(Value) when 0 < Value, Value =< 120000 ->
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}
when Path :: unicode:charlist(),
Value :: map(),
@ -164,13 +148,10 @@ init(none) ->
handle_call({request, Request}, From, State) ->
NewState = do_request(Request, From, State),
{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}) ->
{reply, TLS, State};
handle_call(chain_nodes, _, State) ->
Nodes = do_chain_nodes(State),
handle_call(chain_nodes, _, State = #s{chain_nodes = {Wait, Used}}) ->
Nodes = lists:append(Wait, Used),
{reply, Nodes, State};
handle_call(timeout, _, State = #s{timeout = Value}) ->
{reply, Value, State};
@ -182,9 +163,10 @@ handle_call(Unexpected, From, State) ->
handle_cast({tls, Boolean}, State) ->
NewState = do_tls(Boolean, State),
{noreply, NewState};
handle_cast({chain_nodes, List}, State) ->
NewState = do_chain_nodes(List, State),
{noreply, NewState};
handle_cast({chain_nodes, []}, State) ->
{noreply, State#s{chain_nodes = {[], []}}};
handle_cast({chain_nodes, ToUse}, State) ->
{noreply, State#s{chain_nodes = {ToUse, []}}};
handle_cast({timeout, Value}, State) ->
{noreply, State#s{timeout = Value}};
handle_cast(Unexpected, State) ->
@ -239,23 +221,6 @@ terminate(_, _) ->
%%% 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) ->
ok = ssl:start(),
State#s{tls = true};
@ -265,21 +230,17 @@ do_tls(_, 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}),
State;
do_request_sticky(Request,
From,
State = #s{tls = TLS,
fetchers = Fetchers,
sticky = Node,
timeout = Timeout}) ->
do_request(Request,
From,
State = #s{tls = false,
fetchers = Fetchers,
chain_nodes = {[Node | Rest], Used},
timeout = Timeout}) ->
Now = erlang:system_time(nanosecond),
Fetcher =
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,
Fetcher = fun() -> hz_fetcher:connect(Node, Request, From, Timeout) end,
{PID, Mon} = spawn_monitor(Fetcher),
New = #fetcher{pid = PID,
mon = Mon,
@ -287,24 +248,15 @@ do_request_sticky(Request,
node = Node,
from = From,
req = Request},
State#s{fetchers = [New | Fetchers]}.
do_request(_, From, State = #s{chain_nodes = {[], []}}) ->
ok = gen_server:reply(From, {error, no_nodes}),
State;
State#s{fetchers = [New | Fetchers], chain_nodes = {Rest, [Node | Used]}};
do_request(Request,
From,
State = #s{tls = TLS,
State = #s{tls = true,
fetchers = Fetchers,
chain_nodes = {[Node | Rest], Used},
timeout = Timeout}) ->
Now = erlang:system_time(nanosecond),
Fetcher =
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,
Fetcher = fun() -> hz_fetcher:slowly_connect(Node, Request, From, Timeout) end,
{PID, Mon} = spawn_monitor(Fetcher),
New = #fetcher{pid = PID,
mon = Mon,
@ -316,9 +268,3 @@ do_request(Request,
do_request(Request, From, State = #s{chain_nodes = {[], Used}}) ->
Fresh = lists:reverse(Used),
do_request(Request, From, State#s{chain_nodes = {Fresh, []}}).
log(Level, Format, Args) ->
Raw = io_lib:format("~w ~w: " ++ Format, [?MODULE, self() | Args]),
Entry = unicode:characters_to_list(Raw),
logger:log(Level, Entry).

View File

@ -9,7 +9,7 @@
%%% @end
-module(hz_sup).
-vsn("0.7.0").
-vsn("0.6.1").
-behaviour(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").

View File

@ -4,7 +4,7 @@
{prefix,"hz"}.
{desc,"Gajumaru interoperation library"}.
{author,"Craig Everett"}.
{package_id,{"otpr","hakuzaru",{0,7,0}}}.
{package_id,{"otpr","hakuzaru",{0,6,1}}}.
{deps,[{"otpr","sophia",{9,0,0}},
{"otpr","gmserialization",{0,1,3}},
{"otpr","gmbytecode",{3,4,1}},