diff --git a/ebin/hakuzaru.beam b/ebin/hakuzaru.beam index ee022be..1342f29 100644 Binary files a/ebin/hakuzaru.beam and b/ebin/hakuzaru.beam differ diff --git a/ebin/hz.beam b/ebin/hz.beam index 95f036e..90c802e 100644 Binary files a/ebin/hz.beam and b/ebin/hz.beam differ diff --git a/ebin/hz_fetcher.beam b/ebin/hz_fetcher.beam index 02f0ae3..3cfdbbd 100644 Binary files a/ebin/hz_fetcher.beam and b/ebin/hz_fetcher.beam differ diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index 74e9cf5..10adf29 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,6 +6,10 @@ %%% @end -module(hakuzaru). +-vsn("0.4.1"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). -behavior(application). diff --git a/src/hz.erl b/src/hz.erl index f9e28c5..241beaa 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -4,24 +4,21 @@ %%% This module is the high-level interface to the Gajumaru blockchain system. %%% The interface is split into three main sections: %%% - Get/Set admin functions -%%% - AE node JSON query interface functions -%%% - AE contract call and serialization interface functions +%%% - Node JSON query interface functions +%%% - Contract call and serialization interface functions %%% %%% The get/set admin functions are for setting or checking things like the Gajumaru -%%% "network ID" and list of addresses of AE nodes you want to use for answering -%%% queries to the blockchain (usually you will run these nodes in your own back end). +%%% "network ID" and list of addresses of nodes you want to use for answering +%%% queries to the blockchain. %%% %%% The JSON query interface functions are the blockchain query functions themselves %%% which are translated to network queries and return Erlang messages as responses. %%% %%% The contract call and serialization interface are the functions used to convert %%% a desired call to a smart contract on the chain to call data serialized in a form -%%% that an Gajumaru compatible wallet, SDK or (in the case of a web service) in-page -%%% code based on a JS library such as Sidekick (another component of Hakuzaru) can -%%% use to generate signature requests and signed transaction objects for submission -%%% to an Gajumaru network node for inclusion in the transaction mempool. +%%% that a Gajumaru compatible wallet or library can sign and submit to a Gajumaru node. %%% -%%% This module also includes the standard OTP "application" interface and start/stop +%%% This module does not implement the OTP application behavior. %%% helper functions. %%% @end @@ -37,7 +34,7 @@ tls/0, tls/1, timeout/0, timeout/1]). -% AE node JSON query interface functions +% Node JSON query interface functions -export([top_height/0, top_block/0, kb_current/0, kb_current_hash/0, kb_current_height/0, kb_pending/0, @@ -53,18 +50,16 @@ post_tx/1, contract/1, contract_code/1, contract_poi/1, -% oracle/1, oracle_queries/1, oracle_queries_by_id/2, name/1, % channel/1, peer_pubkey/0, status/0, status_chainends/0]). -% AE contract call and serialization interface functions +% Contract call and serialization interface functions -export([read_aci/1, min_gas/0, min_gas_price/0, - min_fee/0, contract_create/3, contract_create/8, prepare_contract/1, @@ -154,7 +149,6 @@ % "call_data" => contract_byte_array(), % "code" => contract_byte_array(), % "deposit" => non_neg_integer(), -% "fee" => pos_integer(), % "gas" => pos_integer(), % "gas_price" => pos_integer(), % "nonce" => pos_integer(), @@ -646,7 +640,7 @@ dry_run(TX, Accounts, KBHash) -> txs => [#{tx => TXB}], tx_events => true}, JSON = zj:binary_encode(DryData), - request("/v3/dry-run", JSON). + request("/v3/dry_run", JSON). -spec decode_bytearray_fate(EncodedStr) -> {ok, Result} | {error, Reason} when EncodedStr :: binary() | string(), @@ -718,15 +712,17 @@ tx_info(ID) -> result(request(["/v3/transactions/", ID, "/info"])). -spec post_tx(Data) -> {ok, Result} | {error, Reason} - when Data :: term(), % FIXME + when Data :: string() | binary(), Result :: term(), % FIXME Reason :: chain_error() | string(). %% @doc %% Post a transaction to the chain. -post_tx(Data) -> +post_tx(Data) when is_binary(Data) -> JSON = zj:binary_encode(#{tx => Data}), - request("/v3/transactions", JSON). + request("/v3/transactions", JSON); +post_tx(Data) when is_list(Data) -> + post_tx(list_to_binary(Data)). -spec contract(ID) -> {ok, ContractData} | {error, Reason} @@ -864,11 +860,12 @@ contract_create(CreatorID, Path, InitArgs) -> case next_nonce(CreatorID) of {ok, Nonce} -> Amount = 0, - Gas = 100000, + {ok, Height} = top_height(), + TTL = Height + 262980, + Gas = 500000, GasPrice = min_gas_price(), - Fee = min_fee(), contract_create(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, + Amount, TTL, Gas, GasPrice, Path, InitArgs); Error -> Error @@ -876,14 +873,14 @@ contract_create(CreatorID, Path, InitArgs) -> -spec contract_create(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, + Amount, TTL, Gas, GasPrice, Path, InitArgs) -> Result when CreatorID :: pubkey(), Nonce :: pos_integer(), Amount :: non_neg_integer(), + TTL :: pos_integer(), Gas :: pos_integer(), GasPrice :: pos_integer(), - Fee :: non_neg_integer(), Path :: file:filename(), InitArgs :: [string()], Result :: {ok, CreateTX} | {error, Reason}, @@ -927,6 +924,12 @@ 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. %% +%% TTL: +%% This stands for "Time-To-Live", meaning the height beyond which this element is +%% considered to be eligible for garbage collection (and therefore inaccessible!). +%% The TTL can be extended by a "live extension" transaction (basically pay for the +%% data to remain alive longer). +%% %%
  • %% Gas: %% This number sets a limit on the maximum amount of computation the caller is willing @@ -960,13 +963,6 @@ contract_create(CreatorID, Path, InitArgs) -> %% transaction, thus making miners more likely to prioritize the high value ones. %%
  • %%
  • -%% Fee: -%% This value should really be caled `Bribe' or `Tip'. -%% This is a flat fee in aettos that is paid into the block reward, thereby allowing -%% an additional way to prioritize a given transaction above others, even if the -%% transaction will not consume much gas. -%%
  • -%%
  • %% ACI: %% This is the compiled contract's metadata. It provides the information necessary %% for the contract call data to be formed in a way that the Gajumaru runtime will @@ -999,77 +995,81 @@ contract_create(CreatorID, Path, InitArgs) -> %% if you do not already have a copy, and can check the spec of a function before %% trying to form a contract call. -contract_create(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, - Path, InitArgs) -> - case aeso_compiler:file(Path, [{aci, json}]) of - {ok, Compiled} -> - contract_create2(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, - Compiled, InitArgs); +contract_create(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Path, InitArgs) -> + case file:read_file(Path) of + {ok, Source} -> + Dir = filename:dirname(Path), + {ok, CWD} = file:get_cwd(), + SrcDir = aeso_utils:canonical_dir(Path), + Options = + [{aci, json}, + {src_file, Path}, + {src_dir, SrcDir}, + {include, {file_system, [CWD, aeso_utils:canonical_dir(Dir)]}}], + contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, + Source, Options, InitArgs); Error -> Error end. -contract_create2(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, - Compiled, InitArgs) -> +contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options, InitArgs) -> + case aeso_compiler:from_string(Source, Options) of + {ok, Compiled} -> + contract_create3(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, + Source, Compiled, InitArgs); + Error -> + Error + end. + +contract_create3(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Compiled, InitArgs) -> AACI = prepare_aaci(maps:get(aci, Compiled)), case encode_call_data(AACI, "init", InitArgs) of {ok, CallData} -> - contract_create3(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, - Compiled, CallData); + contract_create4(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, + Source, Compiled, CallData); Error -> Error end. -contract_create3(CreatorID, Nonce, - Amount, Gas, GasPrice, Fee, - Compiled, CallData) -> +contract_create4(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Compiled, CallData) -> PK = unicode:characters_to_binary(CreatorID), try {account_pubkey, OwnerID} = aeser_api_encoder:decode(PK), - contract_create4(OwnerID, Nonce, - Amount, Gas, GasPrice, Fee, - Compiled, CallData) + contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Source, Compiled, CallData) catch Error:Reason -> {Error, Reason} end. -contract_create4(OwnerID, Nonce, - Amount, Gas, GasPrice, Fee, - Compiled, CallData) -> +contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Source, Compiled, CallData) -> Code = aeser_contract_code:serialize(Compiled), - VM = 7, - ABI = 3, + VM = 1, + ABI = 1, <> = <>, ContractCreateVersion = 1, - TTL = 0, Type = contract_create_tx, Fields = [{owner_id, aeser_id:create(account, OwnerID)}, {nonce, Nonce}, {code, Code}, + {source, Source}, {ct_version, CTVersion}, - {fee, Fee}, {ttl, TTL}, {deposit, 0}, {amount, Amount}, - {gas, Gas}, {gas_price, GasPrice}, + {gas, Gas}, {call_data, CallData}], Template = [{owner_id, id}, {nonce, int}, {code, binary}, + {source, binary}, {ct_version, int}, - {fee, int}, {ttl, int}, {deposit, int}, {amount, int}, - {gas, int}, {gas_price, int}, + {gas, int}, {call_data, binary}], TXB = aeser_chain_objects:serialize(Type, ContractCreateVersion, Template, Fields), try @@ -1121,9 +1121,9 @@ read_aci(Path) -> CallTX :: binary(), Reason :: term(). %% @doc -%% Form a contract call using hardcoded default values for `Gas', `GasPrice', `Fee', +%% Form a contract call using hardcoded default values for `Gas', `GasPrice', %% and `Amount' to simplify the call (10 args is a bit much for normal calls!). -%% The values used are 20k for `Gas' and `Fee', the `GasPrice' is fixed at 1b (the +%% The values used are 20k for `Gas', the `GasPrice' is fixed at 1b (the %% default "miner minimum" defined in default configs), and the `Amount' is 0. %% %% For details on the meaning of these and other argument values see the doc comment @@ -1134,10 +1134,11 @@ contract_call(CallerID, AACI, ConID, Fun, Args) -> {ok, Nonce} -> Gas = min_gas(), GasPrice = min_gas_price(), - Fee = min_fee(), Amount = 0, + {ok, Height} = top_height(), + TTL = Height + 262980, contract_call(CallerID, Nonce, - Gas, GasPrice, Fee, Amount, + Gas, GasPrice, Amount, TTL, AACI, ConID, Fun, Args); Error -> Error @@ -1165,10 +1166,11 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> case next_nonce(CallerID) of {ok, Nonce} -> GasPrice = min_gas_price(), - Fee = min_fee(), Amount = 0, + {ok, Height} = top_height(), + TTL = Height + 262980, contract_call(CallerID, Nonce, - Gas, GasPrice, Fee, Amount, + Gas, GasPrice, Amount, TTL, AACI, ConID, Fun, Args); Error -> Error @@ -1176,14 +1178,14 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> -spec contract_call(CallerID, Nonce, - Gas, GasPrice, Fee, Amount, + Gas, GasPrice, Amount, TTL, AACI, ConID, Fun, Args) -> Result when CallerID :: unicode:chardata(), Nonce :: pos_integer(), Gas :: pos_integer(), GasPrice :: pos_integer(), - Fee :: non_neg_integer(), Amount :: non_neg_integer(), + TTL :: pos_integer(), AACI :: map(), ConID :: unicode:chardata(), Fun :: string(), @@ -1250,13 +1252,6 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> %% transaction, thus making miners more likely to prioritize the high value ones. %%
  • %%
  • -%% Fee: -%% This value should really be caled `Bribe' or `Tip'. -%% This is a flat fee in aettos that is paid into the block reward, thereby allowing -%% an additional way to prioritize a given transaction above others, even if the -%% transaction will not consume much gas. -%%
  • -%%
  • %% Amount: %% All Gajumaru transactions can carry an "amount" spent from the origin account %% (in this case the `CallerID') to the destination. In a "Spend" transaction this @@ -1300,33 +1295,32 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> %% if you do not already have a copy, and can check the spec of a function before %% trying to form a contract call. -contract_call(CallerID, Nonce, Gas, GP, Fee, Amount, AACI, ConID, Fun, Args) -> +contract_call(CallerID, Nonce, Gas, GP, Amount, TTL, AACI, ConID, Fun, Args) -> case encode_call_data(AACI, Fun, Args) of - {ok, CD} -> contract_call2(CallerID, Nonce, Gas, GP, Fee, Amount, ConID, CD); + {ok, CD} -> contract_call2(CallerID, Nonce, Gas, GP, Amount, TTL, ConID, CD); Error -> Error end. -contract_call2(CallerID, Nonce, Gas, GasPrice, Fee, Amount, ConID, CallData) -> +contract_call2(CallerID, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) -> CallerBin = unicode:characters_to_binary(CallerID), try {account_pubkey, PK} = aeser_api_encoder:decode(CallerBin), - contract_call3(PK, Nonce, Gas, GasPrice, Fee, Amount, ConID, CallData) + contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) catch Error:Reason -> {Error, Reason} end. -contract_call3(PK, Nonce, Gas, GasPrice, Fee, Amount, ConID, CallData) -> +contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) -> ConBin = unicode:characters_to_binary(ConID), try {contract_pubkey, CK} = aeser_api_encoder:decode(ConBin), - contract_call4(PK, Nonce, Gas, GasPrice, Fee, Amount, CK, CallData) + contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) catch Error:Reason -> {Error, Reason} end. -contract_call4(PK, Nonce, Gas, GasPrice, Fee, Amount, CK, CallData) -> - ABI = 3, - TTL = 0, +contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) -> + ABI = 1, CallVersion = 1, Type = contract_call_tx, Fields = @@ -1334,22 +1328,20 @@ contract_call4(PK, Nonce, Gas, GasPrice, Fee, Amount, CK, CallData) -> {nonce, Nonce}, {contract_id, aeser_id:create(contract, CK)}, {abi_version, ABI}, - {fee, Fee}, {ttl, TTL}, {amount, Amount}, - {gas, Gas}, {gas_price, GasPrice}, + {gas, Gas}, {call_data, CallData}], Template = [{caller_id, id}, {nonce, int}, {contract_id, id}, {abi_version, int}, - {fee, int}, {ttl, int}, {amount, int}, - {gas, int}, {gas_price, int}, + {gas, int}, {call_data, binary}], TXB = aeser_chain_objects:serialize(Type, CallVersion, Template, Fields), try @@ -1988,7 +1980,7 @@ aaci_lookup_spec({aaci, _, FunDefs, _}, Fun) -> %% contention becomes an issue. min_gas_price() -> - 1000000000. + 1_000_000_000. -spec min_gas() -> integer(). @@ -2005,19 +1997,6 @@ min_gas() -> 20000. --spec min_fee() -> integer(). -%% @doc -%% This function always returns 200,000,000,000,000 in the current version. -%% -%% This is the minimum fee amount currently accepted -- it is up to callers whether -%% they want to customize this value higher (or possibly lower, though as things stand -%% that would only work on an independent AE-based network, not the actual Gajumaru -%% mainnet or testnet). - -min_fee() -> - 200000000000000. - - encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) -> case maps:find(Fun, FunDefs) of {ok, {ArgDef, _ResultDef}} -> encode_call_data2(ArgDef, Fun, Args); @@ -2052,10 +2031,9 @@ verify_signature(Sig, Message, PubKey) -> end. verify_signature2(Sig, Message, PK) -> - % Superhero salts/hashes the message before signing it, in order to protect + % Gajumaru signatures require messages to be salted and hashed, then + % the hash is what gets signed in order to protect % the user from accidentally signing a transaction disguised as a message. - % In order to verify the signature, we have to duplicate superhero's - % salt/hash procedure here. % % Salt the message then hash with blake2b. See: % 1. Erlang Blake2 blake2b/2 function: @@ -2064,13 +2042,13 @@ verify_signature2(Sig, Message, PK) -> % https://gitlab.com/ioecs/aepp-sdk-js/blob/370f1e30064ad0239ba59931908d9aba0a2e86b6/src/utils/crypto.ts#L171-L175 % 3. SDK hashing: % https://gitlab.com/ioecs/aepp-sdk-js/blob/370f1e30064ad0239ba59931908d9aba0a2e86b6/src/utils/crypto.ts#L83-L85 - Prefix = <<"aeternity Signed Message:\n">>, -% Prefix = <<"gajumaru Signed Message:\n">>, % TODO: Switch the prefix after we kill Superhero + Prefix = <<"Gajumaru Signed Message:\n">>, {ok, PSize} = vencode(byte_size(Prefix)), {ok, MSize} = vencode(byte_size(Message)), Smashed = iolist_to_binary([PSize, Prefix, MSize, Message]), {ok, Hashed} = eblake2:blake2b(32, Smashed), - Signature = <<(binary_to_integer(Sig, 16)):(64 * 8)>>, +% Signature = <<(binary_to_integer(Sig, 16)):(64 * 8)>>, + Signature = base64:decode(Sig), Result = ecu_eddsa:sign_verify_detached(Signature, Hashed, PK), {ok, Result}. diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index fd9c33d..159711b 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,3 @@ -%%% @private - -module(hz_fetcher). -vsn("0.4.1"). -author("Craig Everett ").