135 Commits

Author SHA1 Message Date
zxq9 75bc52ede3 Doc formatting adjustments 2026-05-27 21:49:30 +09:00
zxq9 29619f08b7 Remove stdout line 2026-05-27 16:50:23 +09:00
zxq9 af46223163 Minor fixes 2026-05-27 16:41:45 +09:00
zxq9 9cafdd2b0f Merge pull request 'Improve specs' (#31) from improve_specs into master
Reviewed-on: #31
2026-05-26 15:38:26 +09:00
zxq9 6d429aa6a4 Merge branch 'master' into improve_specs 2026-05-26 15:38:08 +09:00
zxq9 fcf85077b2 Minor 2026-05-26 15:36:16 +09:00
zxq9 3585dbe534 Merge pull request 'Doc update for hz_sophia and hz_aaci and some minor fixes' (#30) from spivee/docs into master
Reviewed-on: #30
2026-05-26 09:44:53 +09:00
dimitar.p.ivanov 9a7a2a98c4 General polish (#28)
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-authored-by: Dimitar Ivanov <dimitar.p.ivanov@gmail.com>
Reviewed-on: #28
2026-05-22 16:54:16 +09:00
dimitar.p.ivanov 4530fd2e93 Merge pull request 'Fix typespec' (#29) from respec into improve_specs
Reviewed-on: #29
2026-05-22 15:57:11 +09:00
zxq9 2a7079129f Fix typespec
Source needs to be defined as a binary.
2026-05-21 17:07:57 +09:00
Dimitar Ivanov 88aeb39d4a Fix a contract create bug 2026-05-20 17:02:36 +03:00
Jarvis Carroll 9fc89c0c22 Add type information for hz_sophia
This one was much simpler to do than hz_aaci, since it doesn't introduce any new types.

I could have made note of some of the conventions, where a type can be represented in multiple
ways in Sophia syntax, or where these functions are actually more lenient than the compiler, but
it isn't as easy to break those notes up from the basic function usage, like it was in hz_aaci,
where those aforementioned new types are used.
2026-05-19 12:29:30 +00:00
Jarvis Carroll 23c13f607e Document hz_aaci functions
Once the types were documented, the functions were easy to document. Just say "see erlang_expr/0 for details" over and over! ;p
2026-05-19 12:29:30 +00:00
Jarvis Carroll 8bc79d3b3f Fix dialyzer warnings for hz_aaci
A while ago I tried dialyzer and discovered that actually a lot of the AACI generation process
never fails, and whatever the one failure was that was possible, I think I decided was unnecessary,
and made that produce {ok, unknown_type} instead. I then set the types of these functions to be
{ok, Result} | {error, none()}, since there were in fact no errors, but dialyzer still spewed out
warnings for all the case blocks that redundantly check for these impossible error conditions.

Anyway now that is fixed! The behavior and external interface are all the same still, there are just fewer warnings
now.

Also added specs for a couple more internal functions, just because.
So noperhaps realized that actually none of it should fail. I never went in and
removed all the {ok, _} wrappers, but I did at least type the functions with the correct error list
2026-05-19 12:29:30 +00:00
Jarvis Carroll 3fae9a2edd Document hz_aaci types 2026-05-19 12:29:30 +00:00
zxq9 a3b19747b6 Adjust error condition 2026-05-18 12:56:31 +09:00
zxq9 f8e9333b4b Doc update 2026-05-14 11:01:37 +09:00
zxq9 eaccd50764 Fill in the holes in hz.erl docs and make hz_fetcher.erl 2026-05-13 19:48:49 +09:00
zxq9 9fd8dbd1a6 Merge branch 'master' into docs 2026-05-13 19:25:59 +09:00
Dimitar Ivanov f0f86ed36d Improve specs 2026-05-13 10:04:23 +03:00
Jarvis Carroll ed252b4c06 Also note index in record_element
I changed it from noting the index to just noting the field name, but
actually both pieces of information are important, since if there was
a type error, presumably the type information is actually wrong.

Now we put the index first, since that is the part of the FATE tuple
that failed, and then the field name that that would be if the type
information were correct, in case that is useful.
2026-05-12 06:07:58 +00:00
Jarvis Carroll 5dcc05d56a Change fate_to_erlang warning
This warning always confuses me. Usually it is a case I haven't actually implemented,
but I don't need the program to diagnose that for me, I need the program to tell me what the
type was, so that I can work out why it thinks it isn't implemented.

All three terms of the annotated type are relevant, but the annotated version can only differ from the normalized version if
it is a record or variant definition, so we special case those two just to communicate that the fact that it is *some* kind of record
did successfully pass through to the coerce logic, and otherwise we just try and print the opaque and normalized types faithfully.
2026-05-12 06:00:26 +00:00
Jarvis Carroll 2eca3a5338 Handle singleton records in erlang_to_fate
I realized this case needed special handling in hz_sophia, but didn't
get around to covering it properly in the older hz_aaci analogues.

While I was at it, I went and improved the error paths for record elements.
2026-05-12 04:23:21 +00:00
zxq9 e595991616 Correct keyblock() type 2026-05-10 21:02:23 +09:00
zxq9 da92d80334 Merge pull request 'Fix docs' (#24) from contract-spend into master
Reviewed-on: #24
2026-05-10 19:45:08 +09:00
zxq9 f821d57c1c Fix docs 2026-05-10 19:35:20 +09:00
zxq9 e87be689a8 Merge pull request 'Allow spending to contract addresses' (#23) from contract-spend into master
Reviewed-on: #23
2026-05-10 17:57:00 +09:00
zxq9 2a7de4fee1 verup 2026-05-10 17:56:34 +09:00
zxq9 82d08da8ca Allow spending to contract addresses 2026-05-10 16:14:00 +09:00
zxq9 85d0c6fd04 Doc fix 2026-05-10 15:52:39 +09:00
zxq9 d8221e0b25 Doc fix
#20
2026-05-10 15:49:11 +09:00
zxq9 b950bb8a67 Merge pull request 'Make Hakuzaru Great Again' (#22) from parser into master
Reviewed-on: #22
2026-05-10 15:26:44 +09:00
zxq9 a4914c1ad1 Making custom dir. 2026-05-10 15:22:13 +09:00
zxq9 9e6d9ec02e Merge branch 'master' into parser 2026-05-10 15:14:57 +09:00
zxq9 4b9fa65672 Merge down 2026-05-10 15:09:47 +09:00
zxq9 74aaad297a Merge branch 'master' into parser 2026-05-10 15:09:13 +09:00
zxq9 c9ead44aa2 Let non-zx projects call contract_create* 2026-05-10 15:01:50 +09:00
zxq9 c54c0db17a Fix list -> binary arg 2026-05-10 13:16:13 +09:00
zxq9 cd4f6a56a5 Differentiate between source return types 2026-05-09 20:07:52 +09:00
zxq9 fd2158a465 base64 -> bytearray encoding 2026-05-09 15:19:51 +09:00
zxq9 7fc3cd00da WIP 2026-05-08 23:04:56 +09:00
zxq9 02945dd10d derp 2026-05-08 19:47:25 +09:00
zxq9 695e7e4828 WIP 2026-05-08 15:48:05 +09:00
zxq9 9f02f73dbd verup 2026-05-08 08:43:07 +09:00
zxq9 fd8766a249 Unify call arg order between call and create 2026-05-07 19:53:36 +09:00
Jarvis Carroll 540b2c513b Fill AACI and coerce type specs
Any error reasons or paths are just term() still, and ACI doesn't have a defined spec in the compiler, so whatever, but the AACI types, the erlang representation of terms, and the four different kinds of coerce function are all spec'd now.

Also some internal type substitution functions were given types, just in the hopes of catching some errors, but dyalizer doesn't seem to complain at all no matter how badly I break my code. Strange approach to making a type system, but oh well.
2026-02-26 12:57:49 +00:00
zxq9 bda4e89e58 Merge branch 'parser' of ssh://git.qpq.swiss:21203/QPQ-AG/hakuzaru into parser 2026-02-25 16:21:23 +09:00
zxq9 f277e79096 Minor doc and style edits 2026-02-25 16:20:52 +09:00
Jarvis Carroll ddec3bfa74 add more format options to decode_bytearray
I reversed the argument order here, since the Format option is sort of kind of almost optional, but I am not sure if that was a good idea.
2026-02-24 06:12:00 +00:00
Jarvis Carroll a0fbeebcdb Pretty print Sophia expressions.
I think all of the tests roundtrip now, so if my parser was thorough, the pretty printer should be as thorough.
2026-02-17 07:26:42 +00:00
Jarvis Carroll 78c9c67f38 typecheck bits
Sophia bitstrings aren't really something you initialize manually, so we have to make up a literal format for them. Failing that, we just accept arbitrary integers and bytearrays as bitstrings.
2026-02-13 06:25:24 +00:00
Jarvis Carroll 9bc0ffafd1 bool/char literals
Character literals were the main complexity here, but I threw booleans in as well, since that covers all the major literals.
2026-02-13 06:25:24 +00:00
zxq9 a1fc5f19fa Merge branch 'parser' of ssh://git.qpq.swiss:21203/QPQ-AG/hakuzaru into parser 2026-02-13 13:50:57 +09:00
zxq9 efe0a64056 Update comments, add fate and sophia tagged args 2026-02-13 13:50:29 +09:00
Jarvis Carroll 60985130cb Refine tuple parsing errors
There are four major fixes here:
1. some eof tokens were being pattern matched with the wrong arity
2. tuples that are too long actually speculatively parse as an untyped tuple, and then complain that there were too many elements,
3. singleton tuples with a trailing comma are now handled differently to grouping parentheses, consistently between typed and untyped logic
4. the extra return values used to detect untyped singleton tuples are also used to pass the close paren position, so that too_many_elements can report the correct file position too.

Point 4. also completely removes the need for tracking open paren positions that I was doing, and that I thought I would need to do even more of in the ambiguous-open-paren-stack case.
2026-02-13 04:08:58 +00:00
zxq9 6c172c4783 Adjusting a few calls. 2026-02-12 17:44:56 +09:00
Jarvis Carroll 3838a7e3c5 Parse qualified names.
This seemed like it was going to be insanely insanely complex, but
then it turns out the compiler doesn't accept spaces in qualified
names, so I can just dump periods in the lexer and hit it with
string:split/3. Easy.
2026-02-05 07:13:25 +00:00
Jarvis Carroll d014ae0982 Handle token/parse errors more carefully 2026-02-04 07:00:39 +00:00
Jarvis Carroll bb4bcbb7de remove 'tk' atom from file positions 2026-02-03 06:08:54 +00:00
Jarvis Carroll a695c21fc9 Parse address literals.
Also signatures.
2026-02-03 06:00:40 +00:00
Jarvis Carroll 493bdb990c Fix lexer row/column calculations. 2026-02-03 01:42:17 +00:00
Jarvis Carroll 17f635af61 Parse long hex escape codes
This doesn't work super consistently in the compiler, for codepoints above 127, but it should work fine for us, so, oh well!
2026-02-03 00:41:00 +00:00
Jarvis Carroll 272ed01fdc Singleton record/tuple parsing.
Records are a simple case to detect and handle correctly.

Tuples took an entire rewrite of the little tuple parsing bit of the code.
2026-01-30 08:12:32 +00:00
Jarvis Carroll 49cd8b6687 Parse strings 2026-01-29 06:18:06 +00:00
Jarvis Carroll 966b4b2748 Calculate scalar values during lexing
This saves some effort and probably some performance for things like integers, but I'm mainly doing this in anticipation of string literals, because it would just be ridiculous to read code that lexes string literals twice.
2026-01-29 04:06:19 +00:00
Jarvis Carroll fe182a5233 Handle underscores in integers/bytes
This forces us to test for alpha/num/hex enough times that it's now worth making macros for these things.
2026-01-29 03:03:11 +00:00
Jarvis Carroll f1696e2b9e Bytes lexing
I don't handle underscores in bytes correctly... Nor in integers, for that matter.
2026-01-29 02:01:16 +00:00
Jarvis Carroll 2bf384ca82 Infer correct values for tests automatically
Now tests compare the literal parser against the output of the
compiler. The little example contracts we are compiling for the
AACI already had the FATE value in them, in the form of the
instruction
	{'RETURNR', {immediate, FateValue}}
so we just extract that and use it for the tests.
2026-01-27 06:42:55 +00:00
Jarvis Carroll 4f2a3c6c6f Variant parsing 2026-01-23 06:18:39 +00:00
Jarvis Carroll 7df04a81be Tuple parsing 2026-01-23 02:45:23 +00:00
Jarvis Carroll 6f02d4c4e6 Record parsing 2026-01-23 00:48:06 +00:00
zxq9 48bcccdf23 Merge pull request 'Fix hz_grids:req/2,3 and add req/4' (#18) from grids-fix into master
Reviewed-on: #18
2026-01-19 13:26:50 +09:00
zxq9 03b9756066 Fix hz_grids:req/2,3 and add req/4 2026-01-19 12:53:25 +09:00
Jarvis Carroll 56e63051bc Map parsing 2026-01-16 05:46:27 +00:00
Jarvis Carroll 3f1c9bd626 List parsing
Slowly chipping away at cases...
2026-01-15 09:38:04 +00:00
Jarvis Carroll 97e32574c4 set up parsing structure
We tokenize, and then do the simplest possible recursive descent.

We don't want to evaluate anything, so infix operators are out,
meaning no shunting yard or tree rearranging or LR(1) shenanigans
are necessary, just write the code.

If we want to 'peek', just take the next token, and pass it around
from that point on, until it can actually be consumed.
2026-01-15 01:52:30 +00:00
Jarvis Carroll 6f5525afcf Rename get_function_signature
hz_aaci:aaci_get_function_signature is a bit redundant.
2026-01-15 01:50:50 +00:00
Jarvis Carroll 4f1958b210 use lists:unzip/1
Just a little thing I noticed could be improved.
2026-01-13 01:19:29 +00:00
Jarvis Carroll 3da9bd570b split coerce/3 into two functions
Also renamed coerce_bindings to erlang_args_to_fate, to match.
2026-01-09 04:39:58 +00:00
zxq9 d65a048409 Updating eDoc style 2026-01-08 22:14:09 +09:00
zxq9 9280495b18 Adjusting eDoc output 2026-01-08 22:12:43 +09:00
Jarvis Carroll d2163c1ff8 split AACI out of hz.erl
So far the interface to hz.erl is mostly unchanged, apart from prepare_aaci/1

Maybe prepare_aaci should be re-exported, but using it is exactly in line with the
'inconvenient but more flexible primitives' that hz_aaci.erl is meant to represent,
so, maybe that is a fine place to have to go for it, dunno.
2026-01-07 09:40:55 +00: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 e10ef8a460 Remember to wrap final values 2025-12-20 20:38:14 +09:00
zxq9 79eb881208 Formatters work. Still need to write random testers. 2025-12-20 20:22:20 +09:00
zxq9 9ac442ffb4 WIP 2025-12-20 17:26:46 +09:00
zxq9 401f8dec7a WIP 2025-12-20 04:52:42 +09:00
zxq9 81fef99d9c WIP 2025-12-20 04:23:34 +09:00
zxq9 23137a677e WIP 2025-12-20 01:50:15 +09:00
zxq9 b219d0f784 Add "metric" formatting 2025-12-20 00:56:13 +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
zxq9 5a145207da WIP 2025-12-11 17:13:02 +09:00
zxq9 11730de24a WIP 2025-12-05 17:38:57 +09:00
zxq9 975325db14 WIP 2025-12-02 19:40:33 +09:00
zxq9 beed46a38b WIP: omg stuff 2025-12-02 15:22:13 +09:00
zxq9 e4e6e35bf8 WIP: Fix remaining cases 2025-12-01 18:36:50 +09:00
zxq9 7393a02de2 WIP: Moving formatters to HZ 2025-12-01 12:06:05 +09:00
zxq9 c713053efd Merge pull request 'Add binary signatures' (#8) from bin_sig into master
Reviewed-on: #8
2025-11-01 11:30:25 +09:00
zxq9 751c099a44 Merge branch 'master' into bin_sig 2025-10-29 15:53:03 +09:00
zxq9 be0607f7c1 Remove dinkus file 2025-10-29 15:52:36 +09:00
zxq9 823291986e Merge pull request 'Finally implement the "sticky" chain node' (#9) from designated-hitter into master
Reviewed-on: #9
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-10-29 15:50:06 +09:00
zxq9 c5349f5736 Fix silly nodes report bug 2025-10-29 15:35:05 +09:00
zxq9 7252ecd40b spacing 2025-10-25 13:52:05 +09:00
zxq9 e8febcf8d5 Finally implement the "sticky" chain node 2025-10-25 13:45:40 +09:00
zxq9 8a42f4a7a3 verup 2025-10-25 12:49:29 +09:00
zxq9 a305bf3511 Merge branch 'master' into bin_sig 2025-10-25 12:42:03 +09:00
zxq9 f2fa83c215 Add binary signatures 2025-10-25 12:29:49 +09:00
zxq9 4c09490f8a Remove zx lib calls 2025-10-14 16:40:42 +09:00
zxq9 39b92996aa verup 2025-10-14 10:48:20 +09:00
zxq9 d23196e746 Remove final logger include 2025-10-14 10:47:02 +09:00
zxq9 e9b1bccf57 Make hz friendlier to non-zx projects 2025-10-14 10:45:04 +09:00
zxq9 c9cdedf85c Merge pull request 'Complete AACI definition' (#6) from primtypes-squash into master
Reviewed-on: #6
2025-10-11 10:19:49 +09:00
zxq9 11516cb177 Merge pull request 'Patch verup and fix TTL typespec' (#7) from primtypes-specfix into primtypes-squash
Reviewed-on: #7
2025-10-11 10:17:39 +09:00
zxq9 7c2db6eab7 Patch verup and fix TTL typespec 2025-10-11 10:10:39 +09:00
Jarvis Carroll f770bc299e add {raw, binary()} case for all chain objects 2025-10-09 09:46:45 +11:00
Jarvis Carroll c934510859 Complete AACI definition
This commit combines 13 separate commits:

add more atoms to AACI

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

coerce stringy booleans

coerce bytes

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.

coerce character

It's really just an integer... Should we flatten it to an integer
instead? I don't know.

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.

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.

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.

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.

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.

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.

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-30 16:14:11 +10:00
16 changed files with 4787 additions and 1066 deletions
+2 -2
View File
@@ -8,9 +8,9 @@ cancer
erl_crash.dump
ebin/*.beam
doc/*.html
doc/*.css
doc/edoc-info
doc/erlang.png
doc/stylesheet.css
doc/edoc-info
rel/example_project
.concrete/DEV_MODE
.rebar
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+75
View File
@@ -0,0 +1,75 @@
/* standard EDoc style sheet */
body {
font-family: Verdana, Arial, Helvetica, sans-serif;
margin-left: .25in;
margin-right: .2in;
margin-top: 0.2in;
margin-bottom: 0.2in;
color: #696969;
background-color: #ffffff;
}
a:link{
color: #000000;
}
a:visited{
color: #000000;
}
a:hover{
color: #d8613c;
}
h1,h2 {
margin-left: -0.2in;
}
div.navbar {
background-color: #000000;
padding: 0.2em;
}
h2.indextitle {
padding: 0.4em;
color: #dfdfdf;
background-color: #000000;
}
div.navbar a:link {
color: #dfdfdf;
}
div.navbar a:visited {
color: #dfdfdf;
}
div.navbar a:hover {
color: #d8613c;
}
h3.function,h3.typedecl {
background-color: #000000;
color: #dfdfdf;
padding-left: 1em;
}
div.spec {
margin-left: 2em;
background-color: #eeeeee;
}
a.module {
text-decoration:none
}
a.module:hover {
background-color: #eeeeee;
}
ul.definitions {
list-style-type: none;
}
ul.index {
list-style-type: none;
background-color: #eeeeee;
}
/*
* Minor style tweaks
*/
ul {
list-style-type: square;
}
table {
border-collapse: collapse;
}
td {
padding: 3
}
+60 -57
View File
@@ -1,77 +1,80 @@
@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://zxq9.com]
@author Jarvis Carrol <jarviscarrol@qpq.swiss> [https://jarviscarroll.net/]
@version 0.9.2
@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.6.1"},
{modules,[hakuzaru,hz,hz_fetcher,hz_grids,hz_key_master,hz_man,
hz_sup]},
{vsn,"0.9.2"},
{modules,[hakuzaru,hz,hz_aaci,hz_fetcher,hz_format,hz_grids,
hz_key_master,hz_man,hz_sophia,hz_sup]},
{mod,{hakuzaru,[]}}]}.
+1 -1
View File
@@ -6,7 +6,7 @@
%%% @end
-module(hakuzaru).
-vsn("0.6.1").
-vsn("0.9.2").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("GPL-3.0-or-later").
+423 -946
View File
File diff suppressed because it is too large Load Diff
+1673
View File
File diff suppressed because it is too large Load Diff
+56 -8
View File
@@ -1,12 +1,18 @@
%%% @private
%%% Hakuzaru Request Fetcher
%%%
%%% This module defines the request workers.
%%% Each request to a remote chain node is handled by a worker that is spawned
%%% to handle it and terminates on completion.
%%% @end
-module(hz_fetcher).
-vsn("0.6.1").
-vsn("0.9.2").
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
-license("MIT").
-export([connect/4, slowly_connect/4]).
-include("$zx_include/zx_logger.hrl").
-export([connect/4, connect_slowly/4]).
connect(Node = {Host, Port}, Request, From, Timeout) ->
@@ -78,7 +84,7 @@ parse(Received, Sock, From, Timer) ->
<<"HTTP/1.1 500 Internal Server Error\r\n", Tail/binary>> ->
parse2(500, Tail, Sock, From, Timer);
_ ->
ok = zx_net:disconnect(Sock),
ok = disconnect(Sock),
ok = erlang:cancel_timer(Timer, [{async, true}]),
gen_server:reply(From, {error, {received, Received}})
end.
@@ -115,7 +121,7 @@ consume2(Length, Received, Sock, From, Timer) ->
if
Size == Length ->
ok = erlang:cancel_timer(Timer, [{async, true}]),
ok = zx_net:disconnect(Sock),
ok = disconnect(Sock),
Result = zj:decode(Received),
gen_server:reply(From, Result);
Size < Length ->
@@ -208,7 +214,7 @@ read_hval(_, Received, _, _, _) ->
{error, headers}.
slowly_connect(Node, {get, Path}, From, Timeout) ->
connect_slowly(Node, {get, Path}, From, Timeout) ->
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
URL = lists:flatten(url(Node, Path)),
Request = {URL, []},
@@ -219,7 +225,7 @@ slowly_connect(Node, {get, Path}, From, Timeout) ->
BAD -> {error, BAD}
end,
gen_server:reply(From, Result);
slowly_connect(Node, {post, Path, Payload}, From, Timeout) ->
connect_slowly(Node, {post, Path, Payload}, From, Timeout) ->
HttpOptions = [{connect_timeout, 3000}, {timeout, Timeout}],
URL = lists:flatten(url(Node, Path)),
Request = {URL, [], "application/json", Payload},
@@ -236,3 +242,45 @@ 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).
+745
View File
@@ -0,0 +1,745 @@
%%% @doc
%%% Formatting and reading functions for Gaju and Puck quantities
%%%
%%% The numbers involved in dealing with blockchain amounts are enormous
%%% by comparison to legacy forms of currency. It isn't so much that
%%% thousands of Gajus is hard to reason about, but rather that quadrillions
%%% of Pucks is quite hard to even lock on to visually.
%%%
%%% A normal commas and underscores method of decimal formatting is provided, as
%%% `us' formatting along with two additional approaches:
%%% - Japanese traditional myriad structure (`jp' style)
%%% - An internationalized variant inspired by the Japanese technique over periods
%%% (`metric' for SI prefixes, and `legacy' for Anglicized prefixes)
%%%
%%% These are all accessible via the `amount/N' functions.
%%%
%%% The `read/1' function can accept any of the output variants as a string and
%%% will return the number of pucks indicated by the provided string, allowing for
%%% simple copy/paste functionality as well as direct input using any of the
%%% supported notations.
%%% @end
-module(hz_format).
-vsn("0.9.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,
approx_amount/2, approx_amount/3,
read/1,
one/1, mark/1,
price_to_string/1, string_to_price/1]).
-spec amount(Pucks) -> Formatted
when Pucks :: integer(),
Formatted :: string().
%% @doc
%% A convenience formatting function.
%% ```
%% 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).
-spec amount(Style, Pucks) -> Formatted
when Style :: us | jp | metric | legacy | {Separator, Span},
Separator :: $, | $_,
Span :: 3 | 4,
Pucks :: integer(),
Formatted :: string().
%% @doc
%% A money formatting function.
%% ```
%% hz_format:amount(us, 100500040123000000000000000) ->
%% 木100,500,040.123
%%
%% hz_format:amount(jp, 100500040123000000000000000) ->
%% 1億50万40木 12京3000兆本
%%
%% 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).
-spec amount(Unit, Style, Pucks) -> Formatted
when Unit :: gaju | puck,
Style :: us | jp | metric | legacy | {Separator, Span},
Separator :: $, | $_,
Span :: 3 | 4,
Pucks :: integer(),
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) ->
%% 1億50万40木 12京3000兆45億本
%%
%% hz_format:amount(puck, jp, 100500040123000004500000000) ->
%% 100秭5000垓4012京3000兆45億本
%%
%% 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);
amount(puck, us, Pucks) ->
western($,, 3, Pucks);
amount(Unit, jp, Pucks) ->
jp(Unit, all, Pucks);
amount(Unit, metric, Pucks) ->
bestern(Unit, ranks(metric), all, Pucks);
amount(Unit, legacy, Pucks) ->
bestern(Unit, ranks(heresy), all, Pucks);
amount(gaju, {Separator, Span}, Pucks) ->
western(Separator, $., Span, all, Pucks);
amount(puck, {Separator, Span}, Pucks) ->
western(Separator, Span, Pucks).
-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 formatter for decimal notation which permits a precision
%% value to be applied to the puck side of the format.
%% ```
%% hz_format:approx_amount({$_, 3}, 3, 100500040123000004500000001) ->
%% 木100_500_040.123...
%%
%% hz_format:approx_amount({$_, 4}, 12, 100500040123000004500000001) ->
%% 木1_0050_0040.1230_0000_45...
%% '''
approx_amount(us, Precision, Pucks) ->
western($,, $., 3, Precision, Pucks);
approx_amount({Separator, Span}, Precision, Pucks) ->
western(Separator, $., Span, Precision, Pucks).
western(Separator, Span, Pucks) when Pucks >= 0 ->
western2(Separator, Span, Pucks);
western(Separator, Span, Pucks) when Pucks < 0 ->
[$- | western2(Separator, Span, Pucks * -1)].
western2(Separator, Span, Pucks) ->
P = lists:reverse(integer_to_list(Pucks)),
[mark(puck) | separate(Separator, Span, P)].
western(Separator, Break, Span, Precision, Pucks) when Pucks >= 0 ->
western2(Separator, Break, Span, Precision, Pucks);
western(Separator, Break, Span, Precision, Pucks) when Pucks < 0 ->
[$- | western2(Separator, Break, Span, Precision, Pucks * -1)].
western2(Separator, _, Span, 0, Pucks) ->
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,
case {Over18, NoPucks} of
{true, true} ->
Gs = lists:reverse(lists:sublist(SP, Length - 18)),
[mark(gaju) | separate(Separator, Span, Gs)];
{true, false} ->
{PChars, GChars} = lists:split(18, lists:reverse(SP)),
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} ->
[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([mark(gaju), $0, Break, T, E])
end.
decimal_pucks(all, PChars) ->
RTrailing = lists:reverse(PChars),
{lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), ""};
decimal_pucks(Precision, PChars) ->
{Significant, Rest} = lists:split(min(Precision, 18), PChars),
RTrailing = lists:reverse(Significant),
Trailing = lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)),
case lists:all(fun(C) -> C =:= $0 end, Rest) of
true -> {Trailing, ""};
false -> {Trailing, "..."}
end.
separate(_, _, "") ->
"";
separate(S, P, G) ->
separate(S, P, 1, G, []).
separate(_, _, _, [H], A) ->
[H | A];
separate(S, P, P, [H | T], A) ->
separate(S, P, 1, T, [S, H | A]);
separate(S, P, N, [H | T], A) ->
separate(S, P, N + 1, T, [H | A]).
bestern(gaju, Ranks, Precision, Pucks) when Pucks >= 0 ->
[mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks)];
bestern(gaju, Ranks, Precision, Pucks) when Pucks < 0 ->
[$-, mark(gaju), bestern2(gaju, Ranks, 3, Precision, Pucks * -1)];
bestern(puck, Ranks, Precision, Pucks) when Pucks >= 0 ->
[mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks)];
bestern(puck, Ranks, Precision, Pucks) when Pucks < 0 ->
[$-, mark(puck), bestern2(puck, Ranks, 3, Precision, Pucks * -1)].
jp(Unit, Precision, Pucks) when Pucks >= 0 ->
bestern2(Unit, ranks(jp), 4, Precision, Pucks);
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))),
case Span of
3 -> period("G", 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)),
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(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)),
H =
case Span of
3 -> bestern2(gaju, Ranks, 3, 0, Pucks);
4 -> jp(gaju, 0, Pucks)
end,
Digits = min(Precision, 18),
T =
case length(P) < Digits of
false ->
ReverseP = lists:reverse(lists:sublist(P, Digits)),
PuckingString = lists:flatten(string:pad(ReverseP, 18, leading, $0)),
case lists:all(fun(C) -> C =:= $0 end, PuckingString) of
false ->
case Span of
3 -> period("P", Ranks, PuckingString);
4 -> myriad(mark(puck), Ranks, PuckingString)
end;
true ->
""
end;
true ->
[]
end,
lists:flatten([H, " ", T]);
bestern2(puck, Ranks, Span, all, Pucks) ->
P = lists:reverse(integer_to_list(Pucks)),
case lists:all(fun(C) -> C =:= $0 end, P) of
false ->
case Span of
3 -> period("P", Ranks, P);
4 -> myriad(mark(puck), Ranks, P)
end;
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, mark(puck)]
end
end;
bestern2(puck, Ranks, Span, Precision, Pucks) ->
Digits = min(Precision, 18),
P = lists:flatten(string:pad(integer_to_list(Pucks), 18, leading, $0)),
case length(P) < Digits of
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, mark(puck)]
end;
false ->
PucksToGive = lists:sublist(P, Digits),
PuckingString = lists:flatten(string:pad(lists:reverse(PucksToGive), 18, leading, $0)),
case lists:all(fun(C) -> C =:= $0 end, PuckingString) of
false ->
case Span of
3 -> period("P", Ranks, PuckingString);
4 -> myriad(mark(puck), Ranks, PuckingString)
end;
true ->
case Span of
3 -> [$0, " P"];
4 -> [$0, mark(puck)]
end
end
end.
period(Symbol, Ranks, [$0, $0, $0 | PT]) ->
rank3(Ranks, PT, [Symbol]);
period(Symbol, Ranks, [P3, $0, $0 | PT]) ->
rank3(Ranks, PT, [P3, 32, Symbol]);
period(Symbol, Ranks, [P3, P2, $0 | PT]) ->
rank3(Ranks, PT, [P2, P3, 32, Symbol]);
period(Symbol, Ranks, [P3, P2, P1 | PT]) ->
rank3(Ranks, PT, [P1, P2, P3, 32, Symbol]);
period(Symbol, _, [P3]) ->
[P3, 32, Symbol];
period(Symbol, _, [P3, P2]) ->
[P2, P3, 32, Symbol].
rank3([_ | RT], [$0, $0, $0 | PT], A) ->
rank3(RT, PT, A);
rank3([RH | RT], [P3, $0, $0 | PT], A) ->
rank3(RT, PT, [P3, RH | A]);
rank3([RH | RT], [P3, P2, $0 | PT], A) ->
rank3(RT, PT, [P2, P3, RH | A]);
rank3([RH | RT], [P3, P2, P1 | PT], A) ->
rank3(RT, PT, [P1, P2, P3, RH | A]);
rank3(_, [$0, $0, $0], A) ->
A;
rank3(_, [$0, $0], A) ->
A;
rank3(_, [$0], A) ->
A;
rank3(_, [], A) ->
A;
rank3([RH | _], [P3, $0, $0], A) ->
[P3, RH | A];
rank3([RH | _], [P3, $0], A) ->
[P3, RH | A];
rank3([RH | _], [P3], A) ->
[P3, RH | A];
rank3([RH | _], [P3, P2, $0], A) ->
[P2, P3, RH | A];
rank3([RH | _], [P3, P2], A) ->
[P2, P3, RH | A];
rank3([RH | _], [P3, P2, P1], A) ->
[P1, P2, P3, RH | A].
myriad(Symbol, Ranks, [$0, $0, $0, $0 | PT]) ->
rank4(Ranks, PT, [Symbol]);
myriad(Symbol, Ranks, [P4, $0, $0, $0 | PT]) ->
rank4(Ranks, PT, [P4, Symbol]);
myriad(Symbol, Ranks, [P4, P3, $0, $0 | PT]) ->
rank4(Ranks, PT, [P3, P4, Symbol]);
myriad(Symbol, Ranks, [P4, P3, P2, $0 | PT]) ->
rank4(Ranks, PT, [P2, P3, P4, Symbol]);
myriad(Symbol, Ranks, [P4, P3, P2, P1 | PT]) ->
rank4(Ranks, PT, [P1, P2, P3, P4, Symbol]);
myriad(Symbol, _, [P4]) ->
[P4, Symbol];
myriad(Symbol, _, [P4, P3]) ->
[P3, P4, Symbol];
myriad(Symbol, _, [P4, P3, P2]) ->
[P2, P3, P4, Symbol].
rank4([_ | RT], [$0, $0, $0, $0 | PT], A) ->
rank4(RT, PT, A);
rank4([RH | RT], [P4, $0, $0, $0 | PT], A) ->
rank4(RT, PT, [P4, RH | A]);
rank4([RH | RT], [P4, P3, $0, $0 | PT], A) ->
rank4(RT, PT, [P3, P4, RH | A]);
rank4([RH | RT], [P4, P3, P2, $0 | PT], A) ->
rank4(RT, PT, [P2, P3, P4, RH | A]);
rank4([RH | RT], [P4, P3, P2, P1 | PT], A) ->
rank4(RT, PT, [P1, P2, P3, P4, RH | A]);
rank4(_, [$0, $0, $0, $0], A) ->
A;
rank4(_, [$0, $0, $0], A) ->
A;
rank4(_, [$0, $0], A) ->
A;
rank4(_, [$0], A) ->
A;
rank4(_, [], A) ->
A;
rank4([RH | _], [P4, $0, $0, $0], A) ->
[P4, RH | A];
rank4([RH | _], [P4, $0, $0], A) ->
[P4, RH | A];
rank4([RH | _], [P4, $0], A) ->
[P4, RH | A];
rank4([RH | _], [P4], A) ->
[P4, RH | A];
rank4([RH | _], [P4, P3, $0, $0], A) ->
[P3, P4, RH | A];
rank4([RH | _], [P4, P3, $0], A) ->
[P3, P4, RH | A];
rank4([RH | _], [P4, P3], A) ->
[P3, P4, RH | A];
rank4([RH | _], [P4, P3, P2, $0], A) ->
[P2, P3, P4, RH | A];
rank4([RH | _], [P4, P3, P2], A) ->
[P2, P3, P4, RH | A];
rank4([RH | _], [P4, P3, P2, P1], A) ->
[P1, P2, P3, P4, RH | A].
ranks(jp) ->
"万億兆京垓秭穣溝澗正載極";
ranks(metric) ->
["k ", "m ", "g ", "t ", "p ", "e ", "z ", "y ", "r ", "Q "];
ranks(heresy) ->
["k ", "m ", "b ", "t ", "q ", "e ", "z ", "y ", "r ", "Q "].
-spec mark(Unit) -> Mark
when Unit :: gaju | puck,
Mark :: $木 | $本.
%% @doc
%% Retrieve the unicode codepoint for the `gaju' mark (木) or the `puck' mark (本).
mark(gaju) -> $木;
mark(puck) -> $本.
-spec one(Unit) -> Pucks
when Unit :: gaju | puck,
Pucks :: 1_000_000_000_000_000_000 | 1.
%% @doc
%% Quickly resolve the number of pucks in a given unit.
%%
%% The number of pucks in a gaju is so large that it can be a little bit annoying
%% to remember the exact amount. This is a helper to simplify this when writing
%% an app against the hakuzaru library when dealing in either unit.
one(gaju) -> 1_000_000_000_000_000_000;
one(puck) -> 1.
-spec read(Format) -> Result
when Format :: string(),
Result :: {ok, Pucks} | error,
Pucks :: integer().
%% @doc
%% 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, []);
read([$本 | Rest]) ->
read_w_pucks(Rest, []);
read([C | Rest])
when C =:= $- orelse
C =:= $ orelse
C =:= $ ->
case read(Rest) of
{ok, Pucks} -> {ok, Pucks * -1};
Error -> Error
end;
read([C | Rest])
when C =:= 32 orelse % ASCII space
C =:= 12288 orelse % full-width space
C =:= $\t orelse
C =:= $\r orelse
C =:= $\n ->
read(Rest);
read([C | Rest]) when $0 =< C andalso C =< $9 ->
read(Rest, [C], []);
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.
read_w_gajus([C | Rest], A) when $0 =< C andalso C =< $9 ->
read_w_gajus(Rest, [C | A]);
read_w_gajus([C | Rest], A) when $ =< C andalso C =< $ ->
NumC = C - $ + $0,
read_w_gajus(Rest, [NumC | A]);
read_w_gajus([$, | Rest], A) ->
read_w_gajus(Rest, A);
read_w_gajus([$_ | Rest], A) ->
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),
{ok, G + P};
Error ->
Error
end;
read_w_gajus([], A) ->
G = list_to_integer(lists:reverse(A)) * one(gaju),
{ok, G};
read_w_gajus([C, 32 | Rest], A) ->
read(Rest, [], [{C, A}]);
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.
read_w_pucks([C | Rest], A) when $0 =< C andalso C =< $9 ->
read_w_pucks(Rest, [C | A]);
read_w_pucks([C | Rest], A) when $ =< C andalso C =< $ ->
NumC = C - $ + $0,
read_w_pucks(Rest, [NumC | A]);
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)}.
read([C | Rest], A, G) when $0 =< C andalso C =< $9 ->
read(Rest, [C | A], G);
read([C | Rest], A, G) when $ =< C andalso C =< $ ->
NumC = C - $ + $0,
read(Rest, [NumC | A], G);
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) ->
read(Rest, [], [{$G, A} | G], []);
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([], A, []) ->
read_w_gajus([], A);
read(_, _, _) ->
error.
read([C | Rest], A, G, P) when $0 =< C andalso C =< $9 ->
read(Rest, [C | A], G, P);
read([C | Rest], A, G, P) when $ =< C andalso C =< $ ->
NumC = C - $ + $0,
read(Rest, [NumC | A], G, P);
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(_, _, _, _) ->
error.
calc(G, P) ->
case calc(gaju, G, 0) of
{ok, Gajus} ->
case calc(puck, P, 0) of
{ok, Pucks} -> {ok, Gajus + Pucks};
error -> error
end;
error ->
error
end.
calc(U, [{_, []} | S], A) ->
calc(U, S, A);
calc(U, [{M, Cs} | S], A) ->
case magnitude(M) of
{ok, J} ->
N = list_to_integer(lists:reverse(Cs)) * J * one(U),
calc(U, S, A + N);
Error ->
Error
end;
calc(_, [], A) ->
{ok, A}.
magnitude($G) ->
{ok, 1};
magnitude($P) ->
{ok, 1};
magnitude(Mark) ->
case rank(Mark, ranks(jp), 1_0000, 1) of
{ok, J} ->
{ok, J};
error ->
case rank([Mark, 32], ranks(metric), 1_000, 1) of
{ok, J} -> {ok, J};
error -> rank([Mark, 32], ranks(heresy), 1_000, 1)
end
end.
rank(Mark, [Mark | _], Magnitude, Sum) ->
{ok, Sum * Magnitude};
rank(Mark, [_ | Rest], Magnitude, Sum) ->
rank(Mark, Rest, Magnitude, Sum * Magnitude);
rank(_, [], _, _) ->
error.
-spec price_to_string(Pucks) -> Gajus
when Pucks :: integer(),
Gajus :: string().
%% @doc
%% A simplified formatting function that converts an integer value in Pucks to a string representation
%% in Gajus. Useful for formatting generic output for UI elements
price_to_string(Pucks) ->
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
[] -> H;
T -> string:join([H, T], ".")
end.
-spec string_to_price(Gajus) -> Pucks
when Gajus :: string(),
Pucks :: integer().
%% @doc
%% A simplified formatting function that converts a Gaju value represented as a string to an
%% integer value in Pucks.
string_to_price(String) ->
case string:split(String, ".") of
[H] -> join_price(H, "0");
[H, T] -> join_price(H, T);
_ -> {error, bad_price}
end.
join_price(H, T) ->
try
Parts = [H, string:pad(T, 18, trailing, $0)],
Price = list_to_integer(unicode:characters_to_list(Parts)),
case Price < 0 of
false -> {ok, Price};
true -> {error, negative_price}
end
catch
error:R -> {error, R}
end.
+104 -11
View File
@@ -37,17 +37,17 @@
%%% @end
-module(hz_grids).
-vsn("0.6.1").
-export([url/2, parse/1, req/2, req/3]).
-vsn("0.9.2").
-export([url/2, url/3, url/4, parse/1, req/2, req/3, req/4]).
-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
%% Takes an instruction and an HTTP endpoint location and forms a GRIDS URL.
url(Instruction, HTTP) ->
case uri_string:parse(HTTP) of
@@ -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(),
@@ -77,6 +134,8 @@ url2(Instruction, URL = #{path := Path}) ->
Amount :: non_neg_integer(),
Payload :: binary(),
URL :: string().
%% @doc
%% Translate a GRIDS URL into an Erlang terms instruction.
parse(GRIDS) ->
case uri_string:parse(GRIDS) of
@@ -133,27 +192,61 @@ l_to_i(S) ->
end.
-spec req(Type, Message) -> Format
when Type :: sign | tx | ack,
Message :: string() | binary(),
Format :: map().
%% @doc
%% @equiv req(Type, Message, false)
req(Type, Message) ->
req(Type, Message, false).
req(sign, Message, ID) ->
-spec req(Type, Message, ID) -> Format
when Type :: sign | tx | ack,
Message :: string() | binary(),
ID :: false | string() | binary(),
Format :: map().
%% @doc
%% Creates a GRIDS message format with the current `NetworkID'.
%%
%% The `ID' parameter indicates which key the requestee should sign with or
%% is `false' to indicate that which key to sign with is up to the requestee.
%% @equiv req(Type, Message, ID, CurrentNetworkID)
req(Type, Message, ID) ->
{ok, NetworkID} = hz:network_id(),
req(Type, Message, ID, NetworkID).
-spec req(Type, Message, ID, NetworkID) -> Format
when Type :: sign | tx | ack,
Message :: string() | binary(),
ID :: false | string() | binary(),
NetworkID :: string() | binary(),
Format :: map().
%% @doc
%% Creates a GRIDS message format.
req(sign, Message, ID, NetworkID) ->
#{"grids" => 1,
"chain" => "gajumaru",
"network_id" => hz:network_id(),
"network_id" => NetworkID,
"type" => "message",
"public_id" => ID,
"payload" => Message};
req(tx, Data, ID) ->
req(tx, Data, ID, NetworkID) ->
#{"grids" => 1,
"chain" => "gajumaru",
"network_id" => hz:network_id(),
"network_id" => NetworkID,
"type" => "tx",
"public_id" => ID,
"payload" => Data};
req(ack, Message, ID) ->
req(ack, Message, ID, NetworkID) ->
#{"grids" => 1,
"chain" => "gajumaru",
"network_id" => hz:network_id(),
"network_id" => NetworkID,
"type" => "ack",
"public_id" => ID,
"payload" => Message}.
+3 -5
View File
@@ -8,8 +8,7 @@
%%% @end
-module(hz_key_master).
-vsn("0.6.1").
-vsn("0.9.2").
-export([make_key/1, encode/1, decode/1]).
-export([lcg/1]).
@@ -91,9 +90,8 @@ chunksize(N, C, A) -> chunksize(N div C, C, A + 1).
read_words() ->
{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"]),
ModPath = code:which(?MODULE),
Path = filename:join([filename:dirname(filename:dirname(ModPath)), "priv", "words4096.txt"]),
{ok, Bin} = file:read_file(Path),
string:lexemes(Bin, "\n").
+116 -28
View File
@@ -9,7 +9,7 @@
%%% @end
-module(hz_man).
-vsn("0.6.1").
-vsn("0.9.2").
-behavior(gen_server).
-author("Craig Everett <ceverett@tsuriai.jp>").
-copyright("Craig Everett <ceverett@tsuriai.jp>").
@@ -20,17 +20,17 @@
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/1, request/2]).
-export([request_sticky/1, request_sticky/2, 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
@@ -43,11 +43,12 @@
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(),
cache = #{} :: #{Label :: term() := AACI :: hz:aaci()}}).
-type state() :: #s{}.
@@ -97,6 +98,41 @@ 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(),
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(),
@@ -136,7 +172,6 @@ start_link() ->
%% preparatory work necessary for proper function.
init(none) ->
ok = io:format("hz_man starting.~n"),
State = #s{},
{ok, State}.
@@ -148,10 +183,19 @@ 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({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 = #s{chain_nodes = {Wait, Used}}) ->
Nodes = lists:append(Wait, Used),
handle_call(chain_nodes, _, State) ->
Nodes = do_chain_nodes(State),
{reply, Nodes, State};
handle_call(timeout, _, State = #s{timeout = Value}) ->
{reply, Value, State};
@@ -163,10 +207,9 @@ handle_call(Unexpected, From, State) ->
handle_cast({tls, Boolean}, State) ->
NewState = do_tls(Boolean, 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({chain_nodes, List}, State) ->
NewState = do_chain_nodes(List, State),
{noreply, NewState};
handle_cast({timeout, Value}, State) ->
{noreply, State#s{timeout = Value}};
handle_cast(Unexpected, State) ->
@@ -221,6 +264,23 @@ 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};
@@ -230,17 +290,30 @@ do_tls(_, State) ->
State.
do_request(_, From, State = #s{chain_nodes = {[], []}}) ->
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;
do_request(Request,
From,
State = #s{tls = false,
fetchers = Fetchers,
chain_nodes = {[Node | Rest], Used},
timeout = Timeout}) ->
do_request_sticky(Request,
From,
State = #s{tls = TLS,
fetchers = Fetchers,
sticky = Node,
timeout = Timeout}) ->
Now = erlang:system_time(nanosecond),
Fetcher = fun() -> hz_fetcher:connect(Node, Request, From, Timeout) end,
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,
{PID, Mon} = spawn_monitor(Fetcher),
New = #fetcher{pid = PID,
mon = Mon,
@@ -248,15 +321,24 @@ do_request(Request,
node = Node,
from = From,
req = Request},
State#s{fetchers = [New | Fetchers], chain_nodes = {Rest, [Node | Used]}};
State#s{fetchers = [New | Fetchers]}.
do_request(_, From, State = #s{chain_nodes = {[], []}}) ->
ok = gen_server:reply(From, {error, no_nodes}),
State;
do_request(Request,
From,
State = #s{tls = true,
State = #s{tls = TLS,
fetchers = Fetchers,
chain_nodes = {[Node | Rest], Used},
timeout = Timeout}) ->
Now = erlang:system_time(nanosecond),
Fetcher = fun() -> hz_fetcher:slowly_connect(Node, Request, From, Timeout) end,
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,
{PID, Mon} = spawn_monitor(Fetcher),
New = #fetcher{pid = PID,
mon = Mon,
@@ -268,3 +350,9 @@ 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).
+1521
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@
%%% @end
-module(hz_sup).
-vsn("0.6.1").
-vsn("0.9.2").
-behaviour(supervisor).
-author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>").
+4 -4
View File
@@ -2,9 +2,9 @@
{type,app}.
{modules,[]}.
{prefix,"hz"}.
{desc,"Gajumaru interoperation library"}.
{author,"Craig Everett"}.
{package_id,{"otpr","hakuzaru",{0,6,1}}}.
{desc,"Gajumaru interoperation library"}.
{package_id,{"otpr","hakuzaru",{0,9,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"}.