Skip to content

Commit

Permalink
Merge pull request #2716 from aeternity/PT-168026587-ga_in_dry_run
Browse files Browse the repository at this point in the history
PT-168026587 GA in dry run
  • Loading branch information
hanssv committed Sep 4, 2019
2 parents 3d102bc + 94e1ab9 commit 6fc08e5
Show file tree
Hide file tree
Showing 12 changed files with 510 additions and 39 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -393,10 +393,13 @@ swagger: config/swagger.yaml $(SWAGGER_CODEGEN_CLI) $(SWAGGER_ENDPOINTS_SPEC)
@( mkdir -p $(HTTP_APP)/priv && cp $(SWTEMP)/priv/swagger.json $(HTTP_APP)/priv/; )
@( cd $(HTTP_APP) && $(MAKE) updateswagger; )
@rm -fr $(SWTEMP)
@$(SWAGGER_CODEGEN) generate -i $< -l python -o $(SWTEMP) --import-mappings GAObject="from swagger_client.models.hack_ga_object import GAObject"
@$(SWAGGER_CODEGEN) generate -i $< -l python -o $(SWTEMP) \
--import-mappings GAObject="from swagger_client.models.hack_ga_object import GAObject" \
--import-mappings DryRunInput="from swagger_client.models.hack_dry_run_input import DryRunInput"
@echo "Swagger python tempdir: $(SWTEMP)"
@cp -r $(SWTEMP)/swagger_client $(PYTHON_TESTS)
@cp $(PYTHON_DIR)/hack_ga_object.py $(PYTHON_TESTS)/swagger_client/models
@cp $(PYTHON_DIR)/hack_dry_run_input.py $(PYTHON_TESTS)/swagger_client/models
@rm -fr $(SWTEMP)

swagger-docs:
Expand Down
107 changes: 89 additions & 18 deletions apps/aecore/src/aec_dry_run.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,41 @@
-export([dry_run/3]).

-include("blocks.hrl").
-include("../../aecontract/include/aecontract.hrl").

-define(MR_MAGIC, <<1:32/unit:8>>).
-define(BIG_AMOUNT, 1000000000000000000000). %% 1000 AE

dry_run(TopHash, Accounts, Txs) ->
try
{Env, Trees} = aetx_env:tx_env_and_trees_from_hash('aetx_transaction', TopHash),
Trees1 = add_accounts(Trees, Accounts),
dry_run_(Txs, Trees1, Env)
try setup_dry_run(TopHash, Accounts) of
{Env, Trees} -> dry_run_(Txs, Trees, Env)
catch _E:_R ->
{error, <<"Failed to get block state and environment">>}
end.

setup_dry_run(TopHash, Accounts) ->
{Env, Trees} = aetx_env:tx_env_and_trees_from_hash('aetx_transaction', TopHash),
Trees1 = add_accounts(Trees, [#{pub_key => ?MR_MAGIC, amount => ?BIG_AMOUNT} | Accounts]),
Env1 = aetx_env:set_dry_run(Env, true),
{Env1, Trees1}.

dry_run_(Txs, Trees, Env) ->
STxs = dummy_sign_txs(Txs),
{ok, dry_run(STxs, Trees, Env, [])}.
try
STxs = prepare_txs(Txs),
{ok, dry_run(STxs, Trees, Env, [])}
catch _E:R ->
{error, iolist_to_binary(io_lib:format("Internal error ~120p", [R]))}
end.

dry_run([], _Trees, _Env, Acc) ->
lists:reverse(Acc);
dry_run([Tx | Txs], Trees, Env, Acc) ->
case aec_trees:apply_txs_on_state_trees([Tx], Trees, Env,
[strict, dont_verify_signature]) of
{ok, [Tx], [], Trees1, _Env1} ->
dry_run([{tx, Opts, Tx} | Txs], Trees, Env, Acc) ->
Stateless = proplists:get_value(stateless, Opts, false),
Env1 = prepare_env(Env, Opts),
case aec_trees:apply_txs_on_state_trees([Tx], Trees, Env1, [strict, dont_verify_signature]) of
{ok, [Tx], [], Trees1, _Env} when Stateless ->
dry_run(Txs, Trees, Env, [dry_run_res(Tx, Trees1, ok) | Acc]);
{ok, [Tx], [], Trees1, _Env} ->
dry_run(Txs, Trees1, Env, [dry_run_res(Tx, Trees1, ok) | Acc]);
Err = {error, _Reason} ->
dry_run(Txs, Trees, Env, [dry_run_res(Tx, Trees, Err) | Acc])
Expand All @@ -37,12 +52,12 @@ dry_run_res(STx, Trees, ok) ->
{Type, _} = aetx:specialize_type(Tx),
case Type of
_ when Type =:= contract_call_tx;
Type =:= contract_create_tx ->
Type =:= contract_create_tx;
Type =:= ga_attach_tx ->
{CB, CTx} = aetx:specialize_callback(Tx),
Contract = CB:contract_pubkey(CTx),
CallId = CB:call_id(CTx),
CallTree = aec_trees:calls(Trees),
{value, CallObj} = aect_call_state_tree:lookup_call(Contract, CallId, CallTree),
CallObj = lookup_call_object(Contract, CallId, Trees),
{Type, {ok, CallObj}};
spend_tx ->
{Type, ok}
Expand All @@ -51,7 +66,6 @@ dry_run_res(STx, _Trees, Err) ->
{Type, _} = aetx:specialize_type(aetx_sign:tx(STx)),
{Type, Err}.


add_accounts(Trees, Accounts) ->
AccountsTree = lists:foldl(fun add_account/2, aec_trees:accounts(Trees), Accounts),
aec_trees:set_accounts(Trees, AccountsTree).
Expand All @@ -64,8 +78,65 @@ add_account(#{pub_key := PK, amount := A}, AccountsTree) ->
end,
aec_accounts_trees:enter(Account, AccountsTree).

dummy_sign_txs(Txs) ->
[ aetx_sign:new(Tx, [dummy_sign()]) || Tx <- Txs ].
prepare_txs([]) -> [];
prepare_txs([{tx, Tx} | Txs]) ->
[{tx, [], dummy_sign(Tx)} | prepare_txs(Txs)];
prepare_txs([{call_req, Req} | Txs]) ->
[prepare_call_req(Req) | prepare_txs(Txs)].

dummy_sign(Tx) ->
aetx_sign:new(Tx, [<<0:(?BLOCK_SIGNATURE_BYTES*8)>>]).

prepare_call_req(ReqMap) ->
try %% Required
{ok, CallData} = aeser_api_encoder:safe_decode(contract_bytearray, maps:get(<<"calldata">>, ReqMap)),
{ok, CtPub} = aeser_api_encoder:safe_decode(contract_pubkey, maps:get(<<"contract">>, ReqMap)),

%% Optional
Amount = maps:get(<<"amount">>, ReqMap, 0),
Caller = case maps:get(<<"caller">>, ReqMap, undefined) of
undefined -> ?MR_MAGIC;
EncCaller ->
{ok, CallerX} = aeser_api_encoder:safe_decode(account_pubkey, EncCaller),
CallerX
end,
Gas = maps:get(<<"gas">>, ReqMap, 1000000),
ABI = maps:get(<<"abi_version">>, ReqMap, ?ABI_AEVM_SOPHIA_1),
Nonce = maps:get(<<"nonce">>, ReqMap, 1),

{ok, CallTx} = aect_call_tx:new(#{caller_id => aeser_id:create(account, Caller),
nonce => Nonce,
contract_id => aeser_id:create(contract, CtPub),
abi_version => ABI,
fee => 1000000 * 1000000,
amount => Amount,
gas => Gas,
gas_price => 1000000,
call_data => CallData}),

%% Other options
ContextMap = maps:get(<<"context">>, ReqMap, #{}),
TxHashCtxt = case maps:get(<<"tx_hash">>, ContextMap, none) of
none -> [];
TxHashEnc ->
{ok, TxHash} = aeser_api_encoder:safe_decode(tx_hash, TxHashEnc),
[{auth_tx, TxHash}]
end,
StateCtxt = [ stateless || maps:get(<<"stateful">>, ContextMap, false) /= true ],
Context = TxHashCtxt ++ StateCtxt,
{tx, Context, dummy_sign(CallTx)}
catch _:_R ->
error({bad_dry_run_call_request, ReqMap})
end.

prepare_env(Env, Opts) ->
case proplists:get_value(auth_tx, Opts, undefined) of
undefined -> Env;
TxHash -> aetx_env:set_ga_tx_hash(Env, TxHash)
end.

lookup_call_object(Key, CallId, Trees) ->
CallTree = aec_trees:calls(Trees),
{value, CallObj} = aect_call_state_tree:lookup_call(Key, CallId, CallTree),
CallObj.

dummy_sign() ->
<<0:(?BLOCK_SIGNATURE_BYTES*8)>>.
1 change: 0 additions & 1 deletion apps/aega/src/aega_meta_tx.erl
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ process(#ga_meta_tx{} = Tx, Trees, Env0) ->
set_meta_result(ok, _Tx, Trees, _Env) ->
Trees;
set_meta_result(Err = {error, _}, Tx, Trees, Env) ->
%% ct:pal("Setting error: ~p\n", [Err]),
SetInstructions =
aeprimop:ga_set_meta_tx_res_instructions(
ga_pubkey(Tx), auth_data(Tx), Err),
Expand Down
48 changes: 44 additions & 4 deletions apps/aehttp/priv/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3691,9 +3691,7 @@
"txs" : {
"type" : "array",
"description" : "Txs",
"items" : {
"$ref" : "#/definitions/EncodedByteArray"
}
"items" : { }
}
},
"example" : {
Expand All @@ -3705,7 +3703,49 @@
"amount" : { },
"pub_key" : { }
} ],
"txs" : [ { }, { } ]
"txs" : [ "", "" ]
}
},
"DryRunCallReq" : {
"type" : "object",
"required" : [ "calldata", "contract" ],
"properties" : {
"calldata" : {
"$ref" : "#/definitions/EncodedByteArray"
},
"contract" : {
"$ref" : "#/definitions/EncodedPubkey"
},
"amount" : {
"$ref" : "#/definitions/UInt"
},
"gas" : {
"$ref" : "#/definitions/UInt"
},
"caller" : {
"$ref" : "#/definitions/EncodedPubkey"
},
"nonce" : {
"$ref" : "#/definitions/UInt64"
},
"abi_version" : {
"$ref" : "#/definitions/UInt16"
},
"context" : {
"$ref" : "#/definitions/DryRunCallContext"
}
}
},
"DryRunCallContext" : {
"type" : "object",
"properties" : {
"tx_hash" : {
"$ref" : "#/definitions/EncodedHash"
},
"stateful" : {
"type" : "boolean",
"description" : "This call will have effects on the next call in this dry-run (or not)"
}
}
},
"DryRunAccount" : {
Expand Down
18 changes: 12 additions & 6 deletions apps/aehttp/src/aehttp_helpers.erl
Original file line number Diff line number Diff line change
Expand Up @@ -565,18 +565,22 @@ dry_run_accounts_([Account | Accounts], Acc) ->

dry_run_txs_([], Txs) ->
{ok, lists:reverse(Txs)};
dry_run_txs_([ETx | Txs], Acc) ->
dry_run_txs_([ETx | Txs], Acc) when is_binary(ETx) ->
case aeser_api_encoder:safe_decode(transaction, ETx) of
{ok, DTx} ->
Tx = aetx:deserialize_from_binary(DTx),
{Type, _} = aetx:specialize_type(Tx),
case lists:member(Type, [spend_tx, contract_create_tx, contract_call_tx]) of
true -> dry_run_txs_(Txs, [Tx | Acc]);
case lists:member(Type, [spend_tx, contract_create_tx, contract_call_tx,
ga_attach_tx]) of
true -> dry_run_txs_(Txs, [{tx, Tx} | Acc]);
false -> {error, lists:concat(["Unsupported transaction type ", Type])}
end;
Err = {error, _Reason} ->
Err
end.
end;
dry_run_txs_([CallReq | Txs], Acc) when is_map(CallReq) ->
dry_run_txs_(Txs, [{call_req, CallReq} | Acc]).


dry_run_results(Rs) ->
[dry_run_result(R) || R <- Rs].
Expand All @@ -587,7 +591,8 @@ dry_run_result({Type, Res}) ->
dry_run_result(_Type, {error, Reason}, Res) ->
Res#{ reason => list_to_binary(lists:concat(["Error: ", Reason])) };
dry_run_result(Type, {ok, CallObj}, Res) when Type =:= contract_call_tx;
Type =:= contract_create_tx ->
Type =:= contract_create_tx;
Type =:= ga_attach_tx ->
Res#{ call_obj => aect_call:serialize_for_client(CallObj) };
dry_run_result(_Type, ok, Res) ->
Res.
Expand All @@ -597,7 +602,8 @@ ok_err(_) -> <<"ok">>.

type(spend_tx) -> <<"spend">>;
type(contract_create_tx) -> <<"contract_create">>;
type(contract_call_tx) -> <<"contract_call">>.
type(contract_call_tx) -> <<"contract_call">>;
type(ga_attach_tx) -> <<"ga_attach">>.

get_transaction(TxKey, TxStateKey) ->
fun(_Req, State) ->
Expand Down
69 changes: 68 additions & 1 deletion apps/aehttp/test/aehttp_dryrun_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
-include_lib("aecontract/include/hard_forks.hrl").

%% common_test exports
-export([
Expand All @@ -13,6 +14,7 @@

-export([ spend_txs/1
, identity_contract/1
, authenticate_contract/1
, accounts/1
]).

Expand All @@ -31,6 +33,7 @@ groups() ->
[ {dry_run, [],
[ spend_txs
, identity_contract
, authenticate_contract
, accounts
]}
].
Expand Down Expand Up @@ -152,6 +155,68 @@ identity_contract(Config) ->

ok.

authenticate_contract(Config) ->
case aect_test_utils:latest_protocol_version() of
?ROMA_PROTOCOL_VSN -> {skip, generalized_accounts_not_in_roma};
?MINERVA_PROTOCOL_VSN -> {skip, generalized_accounts_not_in_minerva};
?FORTUNA_PROTOCOL_VSN -> {skip, generalized_accounts_in_dry_run_not_in_fortuna};
_ -> authenticate_contract_(Config)
end.

authenticate_contract_(Config) ->
#{acc_a := #{pub_key := APub}} = proplists:get_value(accounts, Config),
TopHash = proplists:get_value(top_hash, Config),

{ok, Code} = aect_test_utils:compile_contract(basic_auth),

InitCallData = make_call_data(basic_auth, "init", []),
CallCallData = make_call_data(basic_auth, "get_auth_tx_hash", []),

CreateTx = create_contract_tx(APub, 1, Code, InitCallData),
CPub = contract_id(CreateTx),
CallTx = call_contract_tx(APub, CPub, 2, CallCallData),

DecodeOption =
fun(SerRVal) ->
{ok, RValBin} = aeser_api_encoder:safe_decode(contract_bytearray, SerRVal),
{ok, RVal} = aeb_heap:from_binary({option, word}, RValBin),
RVal
end,

{ok, 200, #{ <<"results">> := [_CreateRes,
#{ <<"result">> := <<"ok">>,
<<"type">> := <<"contract_call">>,
<<"call_obj">> := CallObj }
] }} =
dry_run(TopHash, [CreateTx, CallTx]),

?assertEqual(none, DecodeOption(maps:get(<<"return_value">>, CallObj))),

CallReq = #{<<"contract">> => aeser_api_encoder:encode(contract_pubkey, CPub),
<<"calldata">> => aeser_api_encoder:encode(contract_bytearray, CallCallData)},
CallReq1 = CallReq#{<<"context">> => #{tx_hash => aeser_api_encoder:encode(tx_hash, <<12345:32/unit:8>>),
stateful => true}},
CallReq2 = CallReq#{<<"context">> => #{tx_hash => aeser_api_encoder:encode(tx_hash, <<12345:32/unit:8>>),
stateful => false},
<<"nonce">> => 2},
{ok, 200, #{ <<"results">> := [_CreateRes,
#{ <<"result">> := <<"ok">>,
<<"type">> := <<"contract_call">>,
<<"call_obj">> := CallObj2 },
#{ <<"result">> := <<"ok">>,
<<"type">> := <<"contract_call">>,
<<"call_obj">> := CallObj3 },
#{ <<"result">> := <<"ok">>,
<<"type">> := <<"contract_call">>,
<<"call_obj">> := CallObj3 }
] }} =
dry_run(TopHash, [CreateTx, CallReq1, CallReq2, CallReq2]),

?assertEqual({some, 12345}, DecodeOption(maps:get(<<"return_value">>, CallObj2))),
?assertEqual({some, 12345}, DecodeOption(maps:get(<<"return_value">>, CallObj3))),

ok.

accounts(Config) ->
#{acc_a := #{pub_key := APub}} = proplists:get_value(accounts, Config),
TopHash = proplists:get_value(top_hash, Config),
Expand Down Expand Up @@ -198,11 +263,13 @@ dry_run(TopHash, Txs) ->
dry_run(TopHash, Txs, []).

dry_run(TopHash, Txs, Accounts) ->
EncTx = fun(Tx) -> try aeser_api_encoder:encode(transaction, aetx:serialize_to_binary(Tx))
catch _:_ -> Tx end end,
http_request(internal_address(), post, "debug/transactions/dry-run",
#{ top => aeser_api_encoder:encode(key_block_hash, TopHash),
accounts => [ A#{pub_key => aeser_api_encoder:encode(account_pubkey, PK)}
|| A = #{pub_key := PK } <- Accounts ],
txs => [aeser_api_encoder:encode(transaction, aetx:serialize_to_binary(Tx)) || Tx <- Txs] }).
txs => [EncTx(Tx) || Tx <- Txs] }).

get_genesis_hash() ->
{ok, 200, #{<<"genesis_key_block_hash">> := EncGenesisHash}} = get_status(),
Expand Down

0 comments on commit 6fc08e5

Please sign in to comment.