22 Commits

Author SHA1 Message Date
zxq9 feae15740a WIP 2025-12-31 01:06:20 +09:00
zxq9 af1639d47b Fix missed version doodoo and patch 2025-12-23 13:55:11 +09:00
zxq9 cb36bad74b Verup patch 2025-12-23 13:50:50 +09:00
zxq9 17a2b867fe Merge pull request 'Add naked reads to read/1' (#16) from nekkid into master
Reviewed-on: #16
2025-12-23 13:48:19 +09:00
zxq9 a35118db7a Add naked reads to read/1 2025-12-23 13:48:12 +09:00
zxq9 b719c946ef Fix site link 2025-12-22 12:27:29 +09:00
zxq9 8b6085dee8 Make edoc happy 2025-12-22 11:52:29 +09:00
zxq9 60c8adb4b1 Update docs 2025-12-22 11:39:46 +09:00
zxq9 ebb84b39a1 Verup 2025-12-22 11:17:01 +09:00
zxq9 e456a96f52 Merge pull request 'formatters' (#14) from formatters into master
Reviewed-on: #14
2025-12-22 11:14:19 +09:00
zxq9 2f93c4d503 Fix docs 2025-12-22 11:14:13 +09:00
zxq9 b542205c0e Merge branch 'master' into formatters 2025-12-22 10:09:21 +09:00
zxq9 6ba0c1f2ae Merge pull request 'fix outdated web/repo links in zomp.meta' (#13) from prh/bikeshed into master
Reviewed-on: #13
Reviewed-by: Craig Everett <craigeverett@qpq.swiss>
2025-12-22 10:04:51 +09:00
zxq9 d1cae68ce7 Merge pull request 'grids2' (#11) from grids2 into master
Reviewed-on: #11
2025-12-22 10:04:37 +09:00
zxq9 ccd9a8c83d Merge pull request 'caching' (#12) from caching into master
Reviewed-on: #12
2025-12-22 10:04:15 +09:00
zxq9 f5e955b583 Allow for a 'none' amount to prompt users for an amount. 2025-12-19 21:34:06 +09:00
pharpend 61984d1529 fix ooutdated web/repo links in zomp.meta 2025-12-18 17:48:50 -08:00
zxq9 1978ca59b3 Update specs 2025-12-18 10:37:06 +09:00
zxq9 4ee6609111 Add lookup interface in hz 2025-12-18 09:06:53 +09:00
zxq9 aeb78eab38 Add AACI caching 2025-12-18 09:03:21 +09:00
zxq9 88c6f6dcc7 Spend/Transfer URLs 2025-12-18 04:13:01 +09:00
zxq9 d5ff77b278 WIP 2025-12-12 09:53:24 +09:00
11 changed files with 439 additions and 177 deletions
+59 -57
View File
@@ -1,77 +1,79 @@
@author Craig Everett <ceverett@zxq9.com> [https://gitlab.com/zxq9/zj]
@version 0.3.0
@title Vanillae: Aeternity blockchain bindings for Erlang
@author Craig Everett <craigeverett@qpq.swiss> [https://git.qpq.swiss/QPQ-AG/hakuzaru]
@version 0.8.0
@title Hakuzaru: Gajumaru blockchain bindings for Erlang
@doc
This Erlang application provides bindings for the Erlang blockchain.
The primary goal is for usage to be easy to understand and as simple as possible to use.
The secondary goal is to enable real-world projects to more easily connect with the blockchain in an obvious way and provide a clear path for them to provide feedback regarding areas that are difficult to understand, functionality that is lacking, and explain their use cases to us so we can more easily provide needed features and usage examples to make adoption easier.
This Erlang application provides bindings for the Gajumaru blockchain and basic utilities for manipulating Gajumaru-related data.
== Basic operation ==
All external interfaces expected to be used by authors of programs that use Vanillae are built into the `vanillae' module.
== To Start or Not To Start ==
Starting the `hakuzaru' application is only required if you need to query the chain.
When Vanillae is started as an application a named process called `vanillae_man' is spawned that manages interactions with and the state of the service, as well as a simple-one-for-one supervisor that manages the lifecycle of Vanillae workers (defined in `vanillae_fetcher').
The application can be started via a call to `application', or with an explicit call to `hz:start()'.
After startup `vanillae_man' must be given the address and port of a list of Aeternity nodes that are available to service requests. Note that the service nodes will need to have the "dry run" endpoint enabled and the internal service query port made available in order to provide "dry run" and mempool TX submission functionality.
Hakuzaru can also be run as a local application from the shell by invoking it with `zxh run hakuzaru' if you have `zx' installed.
The `vanillae_man' will round-robin requests to however many Aeternity nodes are provided in its configuration. Note that this congiruation is dynamic and can be changed completely at runtime.
== Operation ==
All blockchain-specific operations are accessible from the main interface modulle: `hz'
== Functions ==
The `vanillae' module exposes one function per blockchain feature provided. Most of these are actually wrappers for blockchain endpoint functions, others provide functionality specific to accomplishing a local processing task related to chain data.
When Hakuzaru is started as an application a named process called `hz_man' is spawned that manages interactions with chain nodes, as well as a simple-one-for-one supervisor that manages the lifecycle of workers (defined in `hz_fetcher').
== Initialization ==
When Vanillae is first started the vanillae_man is started but does not yet know what Aeternity nodes to use to service queries. You will need to provide it with at least one node and port where it can make Aeternity endpoint calls.
After startup `hz_man' must be given the address and port of a list of Gajumaru nodes that are available to service requests.
Note that the service nodes will need to have the dry-run endpoint enabled and the internal service query port made available in order to provide dry-runs and transaction submission.
Note that if you will need to make read-only calls to contracts that are deployed on chain (to queery their state or perform specific read-only operations provided by the contract) the backend nodes you configure will need to be configured with "dry-run" enabled.
When configuring chain nodes a list of nodes should be provided.
To avoid sync issues in the case of fast transaction formation/submission to the chain, only one node from the list of chain nodes is used for submitting transactions and querying `next_nonce/1`.
This node is called "the sticky node".
Example of a shell session where vanillae is started and initialized manually with an AE node in the local network at 192.168.10.10:3013:
The first node in the list of chain nodes provided during configuration is designated as the sticky node.
If you also want to use the sticky node as a query endpoint, include it twice in the list.
The `hz_man' will round-robin requests to however many additional Gajumaru nodes are provided in the configuration.
Note that this configuration is dynamic and can be changed at runtime, so your service can adapt to node availability on the fly if needed.
```
1> vanillae:start().
Starting.
ceverett@steak:~$ zxh run hakuzaru
Erlang/OTP 27 [erts-15.2] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
Eshell V15.2 (press Ctrl+G to abort, type help(). for help)
Fetching otpr-hakuzaru-0.8.0
[100.00%]
Recompile: src/hz_sup
Recompile: src/hz_man
Recompile: src/hz_key_master
Recompile: src/hz_grids
Recompile: src/hz_format
Recompile: src/hz_fetcher
Recompile: src/hz
Recompile: src/hakuzaru
Starting otpr-hakuzaru-0.8.0.
hz_man starting.
Started [hakuzaru]
1> hz:chain_nodes([{"groot.testnet.gajumaru.io", 3013}]).
ok
2> vanillae:status().
{error,no_nodes}
3> vanillae:ae_nodes([{"192.168.7.7", 3013}]).
ok
4> vanillae:status().
{ok,#{"difficulty" => 59729882,
2> hz:status().
{ok,#{"difficulty" => 2877405482,
"finalized" =>
#{"hash" =>
"kh_PDSn6Xru5JVdpJfdDCNpfsL8gUZvvjyhTYuzgndoy98G5oLLR",
"height" => 277454,"type" => "height"},
"genesis_key_block_hash" =>
"kh_wUCideEB8aDtUaiHCtKcfywU6oHZW6gnyci8Mw6S1RSTCnCRu",
"listening" => true,"network_id" => "ae_uat",
"kh_Qdi5MTuuhJm7xzn5JUAbYG12cX3qoLMnXrBxPGzBkMWJ4K8vq",
"hashrate" => 864394,"listening" => true,
"network_id" => "groot.testnet",
"node_revision" =>
"3a08153c635c53d92029a617f2e784731ba367c6",
"node_version" => "6.7.0",
"peer_connections" => #{"inbound" => 25,"outbound" => 10},
"peer_count" => 50,
"7b3cc1db3bb36053023167b86f7d6f2d5dcbd01d",
"node_version" => "0.1.0+203.7b3cc1db3",
"peer_connections" => #{"inbound" => 1,"outbound" => 3},
"peer_count" => 5,
"peer_pubkey" =>
"pp_fCBqobeSwhdnrzC8DoSsmWbf2GzDK61CJujmsCEd3RUkmh9Ny",
"pending_transactions_count" => 2,
"pp_2nQHucGyEt5wkYruNuRkg19cbZuEeyR9BZfvtv49F3AoyNSYMT",
"pending_transactions_count" => 0,
"protocols" =>
[#{"effective_at_height" => 425900,"version" => 5},
#{"effective_at_height" => 154300,"version" => 4},
#{"effective_at_height" => 82900,"version" => 3},
#{"effective_at_height" => 40900,"version" => 2},
#{"effective_at_height" => 0,"version" => 1}],
[#{"effective_at_height" => 0,"version" => 1}],
"solutions" => 0,"sync_progress" => 100.0,
"syncing" => false,"top_block_height" => 802644,
"syncing" => false,"top_block_height" => 277555,
"top_hash" =>
"kh_2vuNc8eG77aTmHcQDcievjKufFwR4MSSuZbEMWwW5TqUzSQy71",
"top_key_block_hash" =>
"kh_28LZSvHZPCGqeWsMsqtSjxQjQHKW1pHzoBex97oMT7U2HcLPgV"}}
'''
Alternatively, here is a start function for an application using Vanillae that initializes vanillae_man with a list of nodes provided by a configuration file:
```
start(normal, _Args) ->
ok = application:ensure_started(sasl),
{ok, Started} = application:ensure_all_started(cowboy),
ok = application:ensure_started(vanillae),
Nodes = proplists:get_value(ae_nodes, read_config(), []),
ok = vanillae:ae_nodes(Nodes),
ok = log(info, "Started: ~p~n", [[vanillae | Started]]),
Routes = [{'_', [{"/", count_top, []}]}],
Dispatch = cowboy_router:compile(Routes),
Env = #{env => #{dispatch => Dispatch}},
{ok, _} = cowboy:start_clear(count_listener, [{port, 8080}], Env),
count_sup:start_link().
"kh_2vuNc8eG77aTmHcQDcievjKufFwR4MSSuZbEMWwW5TqUzSQy71"}}
'''
+3 -3
View File
@@ -3,7 +3,7 @@
{included_applications,[]},
{applications,[stdlib,kernel]},
{description,"Gajumaru interoperation library"},
{vsn,"0.7.0"},
{modules,[hakuzaru,hz,hz_fetcher,hz_grids,hz_key_master,hz_man,
hz_sup]},
{vsn,"0.8.2"},
{modules,[hakuzaru,hz,hz_fetcher,hz_format,hz_grids,
hz_key_master,hz_man,hz_sup]},
{mod,{hakuzaru,[]}}]}.
+1 -1
View File
@@ -6,7 +6,7 @@
%%% @end
-module(hakuzaru).
-vsn("0.7.0").
-vsn("0.8.2").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").
+57 -8
View File
@@ -23,7 +23,7 @@
%%% @end
-module(hz).
-vsn("0.7.0").
-vsn("0.8.2").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").
@@ -66,6 +66,8 @@
contract_create/8,
prepare_contract/1,
prepare_aaci/1,
cache_aaci/2,
lookup_aaci/1,
aaci_lookup_spec/2,
contract_call/5,
contract_call/6,
@@ -968,6 +970,7 @@ contract_create(CreatorID, Path, InitArgs) ->
%% of course there are very good reasons why it should be set to a non-zero value
%% in the case of calls related to contract-governed payment systems.
%% </li>
%% <li>
%% <b>TTL:</b>
%% This stands for "Time-To-Live", meaning the height beyond which this element is
%% considered to be eligible for garbage collection (and therefore inaccessible!).
@@ -1073,7 +1076,7 @@ contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options,
InitArgs :: [string()],
Result :: {ok, CreateTX} | {error, Reason},
CreateTX :: binary(),
Reason :: file:posix() | term().
Reason :: file:posix() | bad_fun_name | aaci_not_found | term().
%% @doc
%% This function takes the compiler output (instead of starting from source),
%% and returns the unsigned create contract call data with default values.
@@ -1187,7 +1190,7 @@ read_aci(Path) ->
-spec contract_call(CallerID, AACI, ConID, Fun, Args) -> Result
when CallerID :: unicode:chardata(),
AACI :: aaci(),
AACI :: aaci() | {aaci, Label :: term()},
ConID :: unicode:chardata(),
Fun :: string(),
Args :: [string()],
@@ -1222,7 +1225,7 @@ contract_call(CallerID, AACI, ConID, Fun, Args) ->
-spec contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> Result
when CallerID :: unicode:chardata(),
Gas :: pos_integer(),
AACI :: aaci(),
AACI :: aaci() | {aaci, Label :: term()},
ConID :: unicode:chardata(),
Fun :: string(),
Args :: [string()],
@@ -1260,7 +1263,7 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) ->
GasPrice :: pos_integer(),
Amount :: non_neg_integer(),
TTL :: non_neg_integer(),
AACI :: aaci(),
AACI :: aaci() | {aaci, Label :: term()},
ConID :: unicode:chardata(),
Fun :: string(),
Args :: [string()],
@@ -2207,11 +2210,34 @@ zip_record_field({Name, Type}, {Remaining, Missing}) ->
{missing, {Remaining, [Name | Missing]}}
end.
-spec cache_aaci(Label, AACI) -> ok
when Label :: term(),
AACI :: aaci().
%% @doc
%% Caches an AACI for future reference in calls that would otherwise require
%% the AACI as an argument. Once cached, a pre-built AACI can be referenced in
%% later calls by substituting the AACI argument with `{aaci, Label}'.
cache_aaci(Label, AACI) ->
hz_man:cache_aaci(Label, AACI).
-spec lookup_aaci(Label) -> Result
when Label :: term(),
Result :: {ok, aaci()} | error.
%% @doc
%% Retrieve a previously prepared and cached AACI.
lookup_aaci(Label) ->
hz_man:lookup_aaci(Label).
-spec aaci_lookup_spec(AACI, Fun) -> {ok, Type} | {error, Reason}
when AACI :: aaci(),
when AACI :: aaci() | {aaci, Label :: term()},
Fun :: binary() | string(),
Type :: {term(), term()}, % FIXME
Reason :: bad_fun_name.
Reason :: bad_fun_name | aaci_not_found.
%% @doc
%% Look up the type information of a given function, in the AACI provided by
@@ -2222,6 +2248,11 @@ aaci_lookup_spec({aaci, _, FunDefs, _}, Fun) ->
case maps:find(Fun, FunDefs) of
A = {ok, _} -> A;
error -> {error, bad_fun_name}
end;
aaci_lookup_spec({aaci, Label}, Fun) ->
case hz_man:lookup_aaci(Label) of
{ok, AACI} -> aaci_lookup_spec(AACI, Fun);
error -> {error, aaci_not_found}
end.
-spec min_gas_price() -> integer().
@@ -2251,7 +2282,12 @@ min_gas() ->
encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) ->
case maps:find(Fun, FunDefs) of
{ok, {ArgDef, _ResultDef}} -> encode_call_data2(ArgDef, Fun, Args);
error -> {error, bad_fun_name}
error -> {error, bad_fun_name}
end;
encode_call_data({aaci, Label}, Fun, Args) ->
case hz_man:lookup_aaci(Label) of
{ok, AACI} -> encode_call_data(AACI, Fun, Args);
error -> {error, aaci_not_found}
end.
encode_call_data2(ArgDef, Fun, Args) ->
@@ -2425,6 +2461,19 @@ spend3(DSenderID,
hz:post_tx(Encoded).
-spec sign(Scheme, Target, SecKey) -> Sig
when Scheme :: message | binary | bitcoin,
Target :: binary(),
SecKey :: binary(),
Sig :: binary().
sign(message, Target, SecKey) -> sign_message(Message, SecKey);
sign(binary, Target, SecKey) -> sign_binary(Target, SecKey);
sign(bitcoin, Target, SecKey) -> sign_bitcoin(Target, SecKey).
sign(bitcoin, Target, SecKey) -> sign_message(Target, SecKey).
-spec sign_message(Message, SecKey) -> Sig
when Message :: binary(),
SecKey :: binary(),
+1 -1
View File
@@ -1,5 +1,5 @@
-module(hz_fetcher).
-vsn("0.7.0").
-vsn("0.8.2").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("MIT").
+179 -94
View File
@@ -21,13 +21,15 @@
%%% @end
-module(hz_format).
-vsn("0.7.0").
-vsn("0.8.2").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").
-export([amount/1, amount/2, amount/3, amount/4,
-export([amount/1, amount/2, amount/3,
approx_amount/2, approx_amount/3,
read/1,
one/1, mark/1,
price_to_string/1, string_to_price/1]).
-spec amount(Pucks) -> Formatted
@@ -35,7 +37,20 @@
Formatted :: string().
%% @doc
%% A convenience formatting function.
%% @equiv amount(us, Pucks).
%% ```
%% hz_format:amount(1) ->
%% 0.000,000,000,000,000,001
%%
%% hz_format:amount(5000) ->
%% 0.000,000,000,000,005
%%
%% hz_format:amount(5000000000000000000) ->
%% 5
%%
%% hz_format:amount(500000123000000000000000) ->
%% 500,000.123
%% '''
%% @equiv amount(us, Pucks)
amount(Pucks) ->
amount(us, Pucks).
@@ -49,7 +64,26 @@ amount(Pucks) ->
Formatted :: string().
%% @doc
%% A money formatting function.
%% @equiv amount(gaju, Style, Pucks).
%% ```
%% hz_format:amount(us, 100500040123000000000000000) ->
%% 100,500,040.123
%%
%% hz_format:amount(jp, 100500040123000000000000000) ->
%% 15040 123000
%%
%% hz_format:amount(metric, 100500040123000000000000000) ->
%% 100m 500k 40 G 123p P
%%
%% hz_format:amount(legacy, 100500040123000000000000000) ->
%% 100m 500k 40 G 123q P
%%
%% hz_format:amount({$_, 3}, 100500040123000000000000000) ->
%% 100_500_040.123
%%
%% hz_format:amount({$_, 4}, 100500040123000000000000000) ->
%% 1_0050_0040.123
%% '''
%% @equiv amount(gaju, Style, Pucks)
amount(Style, Pucks) ->
amount(gaju, Style, Pucks).
@@ -64,6 +98,31 @@ amount(Style, Pucks) ->
Formatted :: string().
%% @doc
%% A simplified format function covering the most common formats desired.
%% ```
%% hz_format:amount(gaju, us, 100500040123000004500000000) ->
%% 100,500,040.123,000,004,5
%%
%% hz_format:amount(puck, us, 100500040123000004500000000) ->
%% 100,500,040,123,000,004,500,000,000
%%
%% hz_format:amount(gaju, jp, 100500040123000004500000000) ->
%% 15040 12300045
%%
%% hz_format:amount(puck, jp, 100500040123000004500000000) ->
%% 10050004012300045
%%
%% hz_format:amount(gaju, metric, 100500040123000004500000000) ->
%% 100m 500k 40 G 123p 4g 500m P
%%
%% hz_format:amount(puck, metric, 100500040123000004500000000) ->
%% 100y 500z 40e 123p 4g 500m P
%%
%% hz_format:amount(gaju, legacy, 100500040123000004500000000) ->
%% 100m 500k 40 G 123q 4b 500m P
%%
%% hz_format:amount(puck, legacy, 100500040123000004500000000) ->
%% 100y 500z 40e 123q 4b 500m P
%% '''
amount(gaju, us, Pucks) ->
western($,, $., 3, all, Pucks);
@@ -81,66 +140,51 @@ amount(puck, {Separator, Span}, Pucks) ->
western(Separator, Span, Pucks).
-spec amount(Unit, Style, Precision, Pucks) -> Serialized
when Unit :: gaju | puck,
Style :: us | jp | metric | legacy | {Separator, Span},
-spec approx_amount(Precision, Pucks) -> Serialized
when Precision :: all | 0..18,
Pucks :: integer(),
Serialized :: string().
%% A formatter for decimal notation which permits a precision
%% value to be applied to the puck side of the format.
%% ```
%% hz_format:approx_amount(3, 100500040123000004500000001) ->
%% 100,500,040.123
%%
%% hz_format:approx_amount(13, 100500040123000004500000001) ->
%% 100,500,040.123,000,004,5...
%%
%% hz_format:approx_amount(all, 100500040123000004500000001) ->
%% 100,500,040.123,000,004,500,000,001
%% '''
%% @equiv approx_amount(us, Precision, Pucks)
approx_amount(Precision, Pucks) ->
approx_amount(us, Precision, Pucks).
-spec approx_amount(Style, Precision, Pucks) -> Serialized
when Style :: us | {Separator, Span},
Precision :: all | 0..18,
Separator :: $, | $_,
Span :: 3 | 4,
Pucks :: integer(),
Serialized :: string().
%% @doc
%% A flexible, if annoyingly complex, formatting function.
%%
%% A formatter for decimal notation which permits a precision
%% value to be applied to the puck side of the format.
%% ```
%% amount(gaju, us, 3, 123456789123456789123456789) ->
%% "木123,456,789.123...".
%% hz_format:approx_amount({$_, 3}, 3, 100500040123000004500000001) ->
%% 100_500_040.123...
%%
%% amount(gaju, us, 3, 123456789123000000000000000) ->
%% "木123,456,789.123".
%%
%% amount(gaju, {$,, 3}, 3, 123456789123456789123456789) ->
%% "木123,456,789.123...".
%%
%% amount(gaju, {$,, 3}, 6, 123456789123000000000000000) ->
%% "木123,456,789.123"
%%
%% amount(gaju, {$_, 4}, 10, 123456789123456789123456789) ->
%% "木1_2345_6789.1234_5678_91..."
%%
%% amount(gaju, jp, 3, 123456789123456789123456789) ->
%% "1億2345万6789木 12京3000兆本"
%%
%% amount(gaju, jp, 6, 123456789123456789123456789) ->
%% "1億2345万6789木 12京3456兆本"
%%
%% amount(gaju, jp, 0, 123456789123456789123456789) ->
%% "1億2345万6789木"
%%
%% amount(puck, jp, all, 123456789123456789123456789) ->
%% "123秭4567垓8912京3456兆7891億2345万6789本"
%% hz_format:approx_amount({$_, 4}, 12, 100500040123000004500000001) ->
%% 1_0050_0040.1230_0000_45...
%% '''
amount(gaju, us, Precision, Pucks) ->
approx_amount(us, Precision, Pucks) ->
western($,, $., 3, Precision, Pucks);
amount(gaju, jp, Precision, Pucks) ->
jp(gaju, Precision, Pucks);
amount(gaju, metric, Precision, Pucks) ->
bestern(gaju, ranks(metric), Precision, Pucks);
amount(gaju, legacy, Precision, Pucks) ->
bestern(gaju, ranks(heresy), Precision, Pucks);
amount(gaju, {Separator, Span}, Precision, Pucks) ->
western(Separator, $., Span, Precision, Pucks);
amount(puck, us, _, Pucks) ->
western($,, 3, Pucks);
amount(puck, jp, _, Pucks) ->
jp(puck, all, Pucks);
amount(puck, metric, _, Pucks) ->
bestern(puck, ranks(metric), all, Pucks);
amount(puck, legacy, _, Pucks) ->
bestern(puck, ranks(heresy), all, Pucks);
amount(puck, {Separator, Span}, _, Pucks) ->
western(Separator, Span, Pucks).
approx_amount({Separator, Span}, Precision, Pucks) ->
western(Separator, $., Span, Precision, Pucks).
western(Separator, Span, Pucks) when Pucks >= 0 ->
@@ -150,7 +194,7 @@ western(Separator, Span, Pucks) when Pucks < 0 ->
western2(Separator, Span, Pucks) ->
P = lists:reverse(integer_to_list(Pucks)),
[puck_mark() | separate(Separator, Span, P)].
[mark(puck) | separate(Separator, Span, P)].
western(Separator, Break, Span, Precision, Pucks) when Pucks >= 0 ->
@@ -160,30 +204,30 @@ western(Separator, Break, Span, Precision, Pucks) when Pucks < 0 ->
western2(Separator, _, Span, 0, Pucks) ->
G = lists:reverse(integer_to_list(Pucks div one_gaju())),
[gaju_mark() | separate(Separator, Span, G)];
G = lists:reverse(integer_to_list(Pucks div one(gaju))),
[mark(gaju) | separate(Separator, Span, G)];
western2(Separator, Break, Span, Precision, Pucks) ->
SP = integer_to_list(Pucks),
Length = length(SP),
Over18 = Length > 18,
NoPucks = (Pucks rem one_gaju()) =:= 0,
NoPucks = (Pucks rem one(gaju)) =:= 0,
case {Over18, NoPucks} of
{true, true} ->
Gs = lists:reverse(lists:sublist(SP, Length - 18)),
[gaju_mark() | separate(Separator, Span, Gs)];
[mark(gaju) | separate(Separator, Span, Gs)];
{true, false} ->
{PChars, GChars} = lists:split(18, lists:reverse(SP)),
H = [gaju_mark() | separate(Separator, Span, GChars)],
H = [mark(gaju) | separate(Separator, Span, GChars)],
{P, E} = decimal_pucks(Precision, lists:reverse(PChars)),
T = lists:reverse(separate(Separator, Span, P)),
lists:flatten([H, Break, T, E]);
{false, true} ->
[gaju_mark(), $0];
[mark(gaju), $0];
{false, false} ->
PChars = lists:flatten(string:pad(SP, 18, leading, $0)),
{P, E} = decimal_pucks(Precision, PChars),
T = lists:reverse(separate(Separator, Span, P)),
lists:flatten([gaju_mark(), $0, Break, T, E])
lists:flatten([mark(gaju), $0, Break, T, E])
end.
decimal_pucks(all, PChars) ->
@@ -212,13 +256,13 @@ separate(S, P, N, [H | T], A) ->
bestern(gaju, Ranks, Precision, Pucks) when Pucks >= 0 ->
[gaju_mark(), bestern2(gaju, Ranks, 3, Precision, Pucks)];
[mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks)];
bestern(gaju, Ranks, Precision, Pucks) when Pucks < 0 ->
[$-, gaju_mark(), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)];
[$-, mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)];
bestern(puck, Ranks, Precision, Pucks) when Pucks >= 0 ->
[puck_mark(), bestern2(puck, Ranks, 3, Precision, Pucks)];
[mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks)];
bestern(puck, Ranks, Precision, Pucks) when Pucks < 0 ->
[$-, puck_mark(), bestern2(puck, Ranks, 3, Precision, Pucks * -1)].
[$-, mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks * -1)].
jp(Unit, Precision, Pucks) when Pucks >= 0 ->
bestern2(Unit, ranks(jp), 4, Precision, Pucks);
@@ -226,24 +270,24 @@ jp(Unit, Precision, Pucks) when Pucks < 0 ->
[$, bestern2(Unit, ranks(jp), 4, Precision, Pucks * -1)].
bestern2(gaju, Ranks, Span, 0, Pucks) ->
G = lists:reverse(integer_to_list(Pucks div one_gaju())),
G = lists:reverse(integer_to_list(Pucks div one(gaju))),
case Span of
3 -> period("G", Ranks, G);
4 -> myriad(gaju_mark(), Ranks, G)
4 -> myriad(mark(gaju), Ranks, G)
end;
bestern2(gaju, Ranks, Span, all, Pucks) ->
P = lists:flatten(string:pad(integer_to_list(Pucks rem one_gaju()), 18, leading, $0)),
P = lists:flatten(string:pad(integer_to_list(Pucks rem one(gaju)), 18, leading, $0)),
Zilch = lists:all(fun(C) -> C =:= $0 end, P),
{H, T} =
case {Span, Zilch} of
{3, false} -> {bestern2(gaju, Ranks, 3, 0, Pucks), period("P", Ranks, lists:reverse(P))};
{4, false} -> {jp(gaju, 0, Pucks), myriad(puck_mark(), Ranks, lists:reverse(P))};
{4, false} -> {jp(gaju, 0, Pucks), myriad(mark(puck), Ranks, lists:reverse(P))};
{3, true} -> {bestern2(gaju, Ranks, 3, 0, Pucks), ""};
{4, true} -> {jp(gaju, 0, Pucks), ""}
end,
lists:flatten([H, " ", T]);
bestern2(gaju, Ranks, Span, Precision, Pucks) ->
P = lists:flatten(string:pad(integer_to_list(Pucks rem one_gaju()), 18, leading, $0)),
P = lists:flatten(string:pad(integer_to_list(Pucks rem one(gaju)), 18, leading, $0)),
H =
case Span of
3 -> bestern2(gaju, Ranks, 3, 0, Pucks);
@@ -259,7 +303,7 @@ bestern2(gaju, Ranks, Span, Precision, Pucks) ->
false ->
case Span of
3 -> period("P", Ranks, PuckingString);
4 -> myriad(puck_mark(), Ranks, PuckingString)
4 -> myriad(mark(puck), Ranks, PuckingString)
end;
true ->
""
@@ -274,12 +318,12 @@ bestern2(puck, Ranks, Span, all, Pucks) ->
false ->
case Span of
3 -> period("P", Ranks, P);
4 -> myriad(puck_mark(), Ranks, P)
4 -> myriad(mark(puck), Ranks, P)
end;
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, puck_mark()]
4 -> [$0, mark(puck)]
end
end;
bestern2(puck, Ranks, Span, Precision, Pucks) ->
@@ -289,7 +333,7 @@ bestern2(puck, Ranks, Span, Precision, Pucks) ->
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, puck_mark()]
4 -> [$0, mark(puck)]
end;
false ->
PucksToGive = lists:sublist(P, Digits),
@@ -298,12 +342,12 @@ bestern2(puck, Ranks, Span, Precision, Pucks) ->
false ->
case Span of
3 -> period("P", Ranks, PuckingString);
4 -> myriad(puck_mark(), Ranks, PuckingString)
4 -> myriad(mark(puck), Ranks, PuckingString)
end;
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, puck_mark()]
4 -> [$0, mark(puck)]
end
end
end.
@@ -418,11 +462,11 @@ ranks(heresy) ->
["k ", "m ", "b ", "t ", "q ", "e ", "z ", "y ", "r ", "Q "].
gaju_mark() -> $木.
mark(gaju) -> $木;
mark(puck) -> $本.
puck_mark() -> $本.
one_gaju() -> 1_000_000_000_000_000_000.
one(gaju) -> 1_000_000_000_000_000_000;
one(puck) -> 1.
-spec read(Format) -> Result
@@ -430,10 +474,26 @@ one_gaju() -> 1_000_000_000_000_000_000.
Result :: {ok, Pucks} | error,
Pucks :: integer().
%% @doc
%% Convery any valid string formatted representation and output a value in pucks.
%% This routine can fail in the special case of `ch' style formatting with a single
%% comma and/or a single period in it, as this can trigger misinterpretation as `us'
%% style. When in doubt, always call `read/2' with a style specified.
%% Convert any valid string formatted representation and output a value in pucks.
%% NOTE: This function does not accept approximated values.
%% ```
%% 1> hz_format:read("木100,500,040.123,000,004,5").
%% {ok,100500040123000004500000000}
%% 2> hz_format:read("本100,500,040,123,000,004,500,000,000").
%% {ok,100500040123000004500000000}
%% 3> hz_format:read("1億50万40木 12京3000兆45億本").
%% {ok,100500040123000004500000000}
%% 4> hz_format:read("100秭5000垓4012京3000兆45億本").
%% {ok,100500040123000004500000000}
%% 5> hz_format:read("木100m 500k 40 G 123p 4g 500m P").
%% {ok,100500040123000004500000000}
%% 6> hz_format:read("本100y 500z 40e 123p 4g 500m P").
%% {ok,100500040123000004500000000}
%% 7> hz_format:read("木100m 500k 40 G 123q 4b 500m P").
%% {ok,100500040123000004500000000}
%% 8> hz_format:read("本100y 500z 40e 123q 4b 500m P").
%% {ok,100500040123000004500000000}
%% '''
read([$木 | Rest]) ->
read_w_gajus(Rest, []);
@@ -459,6 +519,8 @@ read([C | Rest]) when $0 =< C andalso C =< $9 ->
read([C | Rest]) when $ =< C andalso C =< $ ->
NumC = C - $ + $0,
read(Rest, [NumC], []);
read(String) when is_binary(String) ->
read(binary_to_list(String));
read(_) ->
error.
@@ -474,13 +536,13 @@ read_w_gajus([$_ | Rest], A) ->
read_w_gajus([$. | Rest], A) ->
case read_w_pucks(Rest, []) of
{ok, P} ->
G = list_to_integer(lists:reverse(A)) * one_gaju(),
G = list_to_integer(lists:reverse(A)) * one(gaju),
{ok, G + P};
Error ->
Error
end;
read_w_gajus([], A) ->
G = list_to_integer(lists:reverse(A)) * one_gaju(),
G = list_to_integer(lists:reverse(A)) * one(gaju),
{ok, G};
read_w_gajus([C, 32 | Rest], A) ->
read(Rest, [], [{C, A}]);
@@ -488,6 +550,8 @@ read_w_gajus([32, $G, 32 | Rest], A) ->
read(Rest, [], [{$G, A}], []);
read_w_gajus([32, $G], A) ->
calc([{$G, A}], []);
read_w_gajus([32, $P], A) ->
calc([], [{$P, A}]);
read_w_gajus(_, _) ->
error.
@@ -500,6 +564,10 @@ read_w_pucks([$, | Rest], A) ->
read_w_pucks(Rest, A);
read_w_pucks([$_ | Rest], A) ->
read_w_pucks(Rest, A);
read_w_pucks([C, 32 | Rest], A) ->
read(Rest, [], [], [{C, A}]);
read_w_pucks([32, $P], A) ->
calc([], [{$P, A}]);
read_w_pucks([], A) ->
Padded = lists:flatten(string:pad(lists:reverse(A), 18, trailing, $0)),
{ok, list_to_integer(Padded)}.
@@ -514,6 +582,10 @@ read([$木], A, G) ->
calc([{$G, A} | G], []);
read([$G], A, G) ->
calc([{$G, A} | G], []);
read([32, $G], A, G) ->
calc([{$G, A} | G], []);
read([32, $G, 32 | Rest], A, G) ->
read(Rest, [], [{$G, A} | G], []);
read([$木, 32 | Rest], A, G) ->
read(Rest, [], [{$G, A} | G], []);
read([$G, 32 | Rest], A, G) ->
@@ -522,12 +594,27 @@ read([$本], A, P) ->
calc([], [{$P, A} | P]);
read([$P], A, P) ->
calc([], [{$P, A} | P]);
read([32, $P], A, P) ->
calc([], [{$P, A} | P]);
read([C, 32 | Rest], A, G) ->
read(Rest, [], [{C, A} | G]);
read([$, | Rest], A, []) ->
read_w_gajus(Rest, A);
read([$_ | Rest], A, []) ->
read_w_gajus(Rest, A);
read([$. | Rest], A, []) ->
case read_w_pucks(Rest, []) of
{ok, P} ->
G = list_to_integer(lists:reverse(A)) * one(gaju),
{ok, G + P};
Error ->
Error
end;
read([C | Rest], A, G) ->
read(Rest, [], [{C, A} | G]);
read(Rest, A, G) ->
io:format("read(\"~ts\", ~tp, ~tp) -> died!~n", [Rest, A, G]),
read([], A, []) ->
read_w_gajus([], A);
read(_, _, _) ->
error.
@@ -540,12 +627,13 @@ read([$本], A, G, P) ->
calc(G, [{$P, A} | P]);
read([$P], A, G, P) ->
calc(G, [{$P, A} | P]);
read([32, $P], A, G, P) ->
calc(G, [{$P, A} | P]);
read([C, 32 | Rest], A, G, P) ->
read(Rest, [], G, [{C, A} | P]);
read([C | Rest], A, G, P) ->
read(Rest, [], G, [{C, A} | P]);
read(_, _, _, _) ->
io:format("read/4 died!~n"),
error.
calc(G, P) ->
@@ -564,7 +652,7 @@ calc(U, [{_, []} | S], A) ->
calc(U, [{M, Cs} | S], A) ->
case magnitude(M) of
{ok, J} ->
N = list_to_integer(lists:reverse(Cs)) * J * unit(U),
N = list_to_integer(lists:reverse(Cs)) * J * one(U),
calc(U, S, A + N);
Error ->
Error
@@ -572,9 +660,6 @@ calc(U, [{M, Cs} | S], A) ->
calc(_, [], A) ->
{ok, A}.
unit(gaju) -> one_gaju();
unit(puck) -> 1.
magnitude($G) ->
{ok, 1};
@@ -607,7 +692,7 @@ rank(_, [], _, _) ->
%% in Gajus. Useful for formatting generic output for UI elements
price_to_string(Pucks) ->
Gaju = 1_000_000_000_000_000_000,
Gaju = one(gaju),
H = integer_to_list(Pucks div Gaju),
R = Pucks rem Gaju,
case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of
+97 -6
View File
@@ -37,15 +37,15 @@
%%% @end
-module(hz_grids).
-vsn("0.7.0").
-export([url/2, parse/1, req/2, req/3]).
-vsn("0.8.2").
-export([url/2, url/3, url/4, parse/1, req/2, req/3]).
-spec url(Instruction, HTTP) -> Result
when Instruction :: spend | transfer | sign,
HTTP :: uri_string:uri_string(),
GRIDS :: uri_string:uri_string(),
Result :: {ok, GRIDS} | uri_string:uri_error().
Result :: {ok, GRIDS} | uri_string:uri_error(),
GRIDS :: uri_string:uri_string().
%% @doc
%% Takes
@@ -66,6 +66,63 @@ url2(Instruction, URL = #{path := Path}) ->
{ok, uri_string:recompose(GRIDS)}.
-spec url(Instruction, Recipient, Amount) -> GRIDS
when Instruction :: {spend, Network} | {transfer, Node},
Network :: string(),
Node :: {inet:ip_address() | inet:hostname(), inet:port_number()}
| uri_string:uri_string(),
Recipient :: string(),
Amount :: non_neg_integer(),
GRIDS :: uri_string:uri_string().
%% @doc
%% Forms a GRIDS URL for spends or transfers.
%% @equiv uri(Instruction, Recipient, Amount, "")
url(Instruction, Recipient, Amount) ->
url(Instruction, Recipient, Amount, "").
-spec url(Instruction, Recipient, Amount, Payload) -> GRIDS
when Instruction :: {spend, Network} | {transfer, Node},
Network :: string(),
Node :: {inet:ip_address() | inet:hostname(), inet:port_number()}
| uri_string:uri_string(), % "http://..." | "https://..."
Recipient :: string(),
Amount :: non_neg_integer() | none,
Payload :: binary(),
GRIDS :: uri_string:uri_string().
%% @doc
%% Forms a GRIDS URL for spends or transfers.
url({spend, Network}, Recipient, Amount, Payload) ->
Elements = ["grids://", Network, "/1/s/", Recipient, qwargs(Amount, Payload)],
unicode:characters_to_list(Elements);
url({transfer, Node}, Recipient, Amount, Payload) ->
Prefix =
case Node of
{H, P} -> ["grid://", h_to_s(H), ":", integer_to_list(P)];
"https://" ++ H -> ["grids://", H];
"http://" ++ H -> ["grid://", H];
<<"https://", H/binary>> -> ["grids://", H];
<<"http://", H/binary>> -> ["grid://", H]
end,
unicode:characters_to_list([Prefix, "/1/t/", Recipient, qwargs(Amount, Payload)]).
h_to_s(Host) when is_list(Host) -> Host;
h_to_s(Host) when is_binary(Host) -> Host;
h_to_s(Host) when is_tuple(Host) -> inet:ntoa(Host);
h_to_s(Host) when is_atom(Host) -> atom_to_list(Host).
qwargs(none, "") ->
[];
qwargs(Amount, "") ->
["?a=", integer_to_list(Amount)];
qwargs(none, Payload) ->
[$? | uri_string:compose_query([{"p", Payload}])];
qwargs(Amount, Payload) ->
[$? | uri_string:compose_query([{"a", integer_to_list(Amount)}, {"p", Payload}])].
-spec parse(GRIDS) -> Result
when GRIDS :: string(),
Result :: {ok, Instruction} | uri_string:error(),
@@ -133,16 +190,48 @@ l_to_i(S) ->
end.
-spec req(Type, Message) -> RequestMap
when Type :: {sign, message | binary | bitcoin}
| tx
| ack
| sign,
Message :: binary(),
RequestMap :: map().
%% @doc
%% GRIDS maps always contain the following keys:
%% ```
%% #{"grids" => 1,
%% "chain" => "gajumaru",
%% "network_id" => "groot.mainnet.gajumaru.io",
%% "type" => "message" | "binary" | "binary" | "tx" | "ack"
%% "public_id" => term(),
%% "payload" => string()};
%% '''
req(Type, Message) ->
req(Type, Message, false).
req(sign, Message, ID) ->
req({sign, message}, Message, ID) ->
#{"grids" => 1,
"chain" => "gajumaru",
"network_id" => hz:network_id(),
"type" => "message",
"public_id" => ID,
"payload" => Message};
req({sign, binary}, Binary, ID) ->
#{"grids" => 1,
"chain" => "gajumaru",
"network_id" => hz:network_id(),
"type" => "binary",
"public_id" => ID,
"payload" => base64:encode(Binary)};
req({sign, bitcoin}, Binary, ID) ->
#{"grids" => 1,
"chain" => "gajumaru",
"network_id" => hz:network_id(),
"type" => "bitcoin",
"public_id" => ID,
"payload" => base64:encode(Binary)};
req(tx, Data, ID) ->
#{"grids" => 1,
"chain" => "gajumaru",
@@ -156,4 +245,6 @@ req(ack, Message, ID) ->
"network_id" => hz:network_id(),
"type" => "ack",
"public_id" => ID,
"payload" => Message}.
"payload" => Message};
req(sign, Message, ID) ->
req({sign, message}, Message, ID).
+1 -1
View File
@@ -8,7 +8,7 @@
%%% @end
-module(hz_key_master).
-vsn("0.7.0").
-vsn("0.8.2").
-export([make_key/1, encode/1, decode/1]).
+37 -2
View File
@@ -9,7 +9,7 @@
%%% @end
-module(hz_man).
-vsn("0.7.0").
-vsn("0.8.2").
-behavior(gen_server).
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
@@ -20,6 +20,9 @@
chain_nodes/0, chain_nodes/1,
timeout/0, timeout/1]).
%% Contract caching
-export([cache_aaci/2, lookup_aaci/1]).
%% The whole point of this module:
-export([request_sticky/1, request_sticky/2, request/1, request/2]).
@@ -44,7 +47,8 @@
chain_nodes = {[], []} :: {[hz:chain_node()], [hz:chain_node()]},
sticky = none :: none | hz:chain_node(),
fetchers = [] :: [#fetcher{}],
timeout = 5000 :: pos_integer()}).
timeout = 5000 :: pos_integer(),
cache = #{} :: #{Label :: term() := AACI :: hz:aaci()}}).
-type state() :: #s{}.
@@ -94,6 +98,22 @@ timeout(Value) when 0 < Value, Value =< 120000 ->
gen_server:cast(?MODULE, {timeout, Value}).
-spec cache_aaci(Label, AACI) -> ok
when Label :: term(),
AACI :: hz:aaci().
cache_aaci(Label, AACI) ->
gen_server:call(?MODULE, {cache, Label, AACI}).
-spec lookup_aaci(Label) -> Result
when Label :: term(),
Result :: {ok, hz:aaci()} | error.
lookup_aaci(Label) ->
gen_server:call(?MODULE, {lookup, Label}).
-spec request_sticky(Path) -> {ok, Value} | {error, Reason}
when Path :: unicode:charlist(),
Value :: map(),
@@ -167,6 +187,12 @@ handle_call({request, Request}, From, State) ->
handle_call({request_sticky, Request}, From, State) ->
NewState = do_request_sticky(Request, From, State),
{noreply, NewState};
handle_call({lookup, Label}, _, State) ->
Result = do_lookup(Label, State),
{reply, Result, State};
handle_call({cache, Label, AACI}, _, State) ->
NewState = do_cache_aaci(Label, AACI, State),
{reply, ok, NewState};
handle_call(tls, _, State = #s{tls = TLS}) ->
{reply, TLS, State};
handle_call(chain_nodes, _, State) ->
@@ -265,6 +291,15 @@ do_tls(_, State) ->
State.
do_cache_aaci(Label, AACI, State = #s{cache = Cache}) ->
NewCache = maps:put(Label, AACI, Cache),
State#s{cache = NewCache}.
do_lookup(Label, #s{cache = Cache}) ->
maps:find(Label, Cache).
do_request_sticky(_, From, State = #s{sticky = none}) ->
ok = gen_server:reply(From, {error, no_nodes}),
State;
+1 -1
View File
@@ -9,7 +9,7 @@
%%% @end
-module(hz_sup).
-vsn("0.7.0").
-vsn("0.8.2").
-behaviour(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
+3 -3
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,8,2}}}.
{deps,[{"otpr","sophia",{9,0,0}},
{"otpr","gmserialization",{0,1,3}},
{"otpr","gmbytecode",{3,4,1}},
@@ -19,6 +19,6 @@
{copyright,"Craig Everett"}.
{file_exts,[]}.
{license,"MIT"}.
{repo_url,"https://gitlab.com/ioecs/hakuzaru"}.
{repo_url,"https://git.qpq.swiss/QPQ-AG/hakuzaru"}.
{tags,["qpq","gajumaru","blockchain","hakuzaru","crypto","defi"]}.
{ws_url,"https://gitlab.com/ioecs/hakuzaru"}.
{ws_url,"https://git.qpq.swiss/QPQ-AG/hakuzaru"}.