Skip to content

Commit

Permalink
Merge pull request #2648 from aeternity/PT-167808593-improve-state-ch…
Browse files Browse the repository at this point in the history
…annel-systems-tests

[PT-167808593] Improve state channel systems tests
  • Loading branch information
gorbak25 committed Aug 21, 2019
2 parents d3381c7 + ddfb508 commit af7ace1
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 39 deletions.
9 changes: 9 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,15 @@ jobs:
- *install_otp
- *install_libsodium
- *restore_machine_build_cache
# Git based deps are problematic for rebar3 - even running "./rebar3 as system_test upgrade" doesn't do the job of ensuring these deps are up to date
# Just delete the problematic deps from the cache as rebuilding those deps is faster than invalidating the entire outdated cache
- run:
name: Get rid of problematic cached deps
command: |
rm -rf _build/system_test/lib/aesophia;
rm -rf _build/system_test/lib/aesophia_cli;
rm -rf _build/system_test+test/lib/aesophia;
rm -rf _build/system_test+test/lib/aesophia_cli;
# keep user preparation step after cache restore because of perms
- *prepare_ubuntu_user
- *install_system_test_deps
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ system-test-deps:
docker pull "aeternity/aeternity:v2.1.0"
docker pull "aeternity/aeternity:v2.3.0"
docker pull "aeternity/aeternity:v4.0.0"
docker pull "aeternity/aeternity:v4.2.0"
docker pull "aeternity/aeternity:latest"

system-test: KIND=system_test
Expand Down
4 changes: 3 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@
{deps, [
bbmustache,
{hackney, "1.14.3"},
{websocket_client, ".*", {git, "git://github.com/aeternity/websocket_client", {ref, "a4fb3db"}}}
{websocket_client, ".*", {git, "git://github.com/aeternity/websocket_client", {ref, "a4fb3db"}}},
{aesophia, {git, "https://github.com/aeternity/aesophia.git", {ref,"df12f6a"}}},
{aesophia_cli, {git, "git://github.com/aeternity/aesophia_cli", {tag, "v3.2.0"}}}
]},
{ct_opts, [{create_priv_dir, auto_per_tc}]}
]},
Expand Down
167 changes: 143 additions & 24 deletions system_test/common/aest_channels_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
on_chain_channel/1
]).

%% Helpers
-export([
create_state_channel_perform_operations_leave/2,
reestablish_state_channel_perform_operations/3
]).

-import(aest_nodes, [
spec/3,
setup_nodes/2,
Expand All @@ -32,7 +38,12 @@
-import(aest_api, [
sc_open/2,
sc_withdraw/3,
sc_close_mutual/2
sc_close_mutual/2,
sc_transfer/3,
sc_deploy_contract/4,
sc_call_contract/4,
sc_leave/1,
sc_reestablish/5
]).

%=== INCLUDES ==================================================================
Expand Down Expand Up @@ -100,13 +111,16 @@ all() -> [
].

init_per_suite(Config) ->
[
Config1 = [
{node_startup_time, 20000}, %% Time may take to get the node to respond to http
{node_shutdown_time, 20000}, %% Time it may take to stop node cleanly
%% FIXME: Remove this when this is fixed:
%% https://www.pivotaltracker.com/n/projects/2124891/stories/159293763
{verify_logs, false}
| Config].
| Config],

%% Precompile a simple contract for testing
precompile_identity_contract(Config1).

init_per_testcase(_TC, Config) ->
aest_nodes:ct_setup(Config).
Expand Down Expand Up @@ -163,8 +177,10 @@ test_different_nodes_channel_(InitiatorNodeBaseSpec, ResponderNodeBaseSpec, Cfg)

simple_channel_test(ChannelOpts, InitiatorNodeBaseSpec, ResponderNodeBaseSpec, Cfg) ->
#{
initiator_node := INodeName,
initiator_id := IAccount,
initiator_amount := IAmt,
responder_node := RNodeName,
responder_id := RAccount,
responder_amount := RAmt,
push_amount := PushAmount
Expand All @@ -173,35 +189,23 @@ simple_channel_test(ChannelOpts, InitiatorNodeBaseSpec, ResponderNodeBaseSpec, C
MikePubkey = aeser_api_encoder:encode(account_pubkey, maps:get(pubkey, ?MIKE)),
NodeConfig = #{ beneficiary => MikePubkey },
setup([spec(node1, [], InitiatorNodeBaseSpec), spec(node2, [node1], ResponderNodeBaseSpec)], NodeConfig, Cfg),
NodeNames = [node1, node2],
NodeNames = [INodeName, RNodeName],
start_node(node1, Cfg),
start_node(node2, Cfg),
wait_for_startup([node1, node2], 4, Cfg), %% make sure there is some money in accounts
wait_for_value({balance, maps:get(pubkey, ?MIKE), 1000000}, [node1], 10000, Cfg),

post_spend_tx(node1, ?MIKE, IAccount, 1, #{amount => 200000 * aest_nodes:gas_price()}),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price()}, NodeNames, 10000, Cfg),
populate_accounts_with_funds([IAccount, RAccount], NodeNames, Cfg),
{ok, Chan, OpenFee, WFee1, WFee2} = test_open_and_onchain_operations(ChannelOpts, Cfg),
test_offchain_operations(Chan, Cfg),

post_spend_tx(node1, ?MIKE, RAccount, 2, #{amount => 200000 * aest_nodes:gas_price()}),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price()}, NodeNames, 10000, Cfg),

{ok, Chan, TxHash, OpenFee} = sc_open(ChannelOpts, Cfg),
wait_for_value({txs_on_chain, [TxHash]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price() - IAmt - OpenFee}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price() - RAmt}, NodeNames, 10000, Cfg),
{ok, LatestState} = sc_leave(Chan),
{ok, Chan1} = sc_reestablish(Chan, INodeName, RNodeName, LatestState, Cfg),

{ok, TxHash1, WFee1} = sc_withdraw(Chan, initiator, 20 * aest_nodes:gas_price()),
wait_for_value({txs_on_chain, [TxHash1]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price() - IAmt - OpenFee + 20 - WFee1}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price() - RAmt}, NodeNames, 10000, Cfg),

{ok, TxHash2, WFee2} = sc_withdraw(Chan, responder, 50 * aest_nodes:gas_price()),
wait_for_value({txs_on_chain, [TxHash2]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price() - IAmt - OpenFee + 20 * aest_nodes:gas_price() - WFee1}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price() - RAmt + 50 * aest_nodes:gas_price() - WFee2}, NodeNames, 10000, Cfg),

{ok, CloseTxHash, CloseFee, IChange, RChange} = sc_close_mutual(Chan, initiator),
test_offchain_operations(Chan1, Cfg),

ct:log("Testing mutual close"),
{ok, CloseTxHash, CloseFee, IChange, RChange} = sc_close_mutual(Chan1, initiator),
wait_for_value({txs_on_chain, [CloseTxHash]}, NodeNames, 5000, Cfg),

ISplitCloseFee = trunc(math:ceil(CloseFee / 2)),
Expand All @@ -221,8 +225,64 @@ simple_channel_test(ChannelOpts, InitiatorNodeBaseSpec, ResponderNodeBaseSpec, C
wait_for_value({txs_on_chain, [CloseTxHash]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 - IAmt - OpenFee + 20 - WFee1 + IChange}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 - RAmt + 50 - WFee2 + RChange}, NodeNames, 10000, Cfg),
ok.


%=== HELPERS ===================================================================

precompile_identity_contract(Config1) ->
%% Precompile a simple contract for testing
Config2 = aect_test_utils:init_per_group(aevm, Config1),
aect_test_utils:setup_testcase(Config2),
SimpleContractName = "identity",
{ok, BinSrc} = aect_test_utils:read_contract(aect_test_utils:sophia_version(), SimpleContractName),
{ok, Code} = aect_test_utils:compile_contract(aect_test_utils:sophia_version(), SimpleContractName),

[{simple_contract,
#{ bytecode => aeser_api_encoder:encode(contract_bytearray, Code),
vm => aect_test_utils:vm_version(),
abi => aect_test_utils:abi_version(),
code => Code,
src => binary_to_list(BinSrc)
}
} | Config2].

create_state_channel_perform_operations_leave({INodeName, RNodeName}, Config) ->
Config1 = precompile_identity_contract(Config),
ChannelOpts = #{
initiator_node => INodeName,
initiator_id => ?ALICE,
initiator_amount => 50000 * aest_nodes:gas_price(),
responder_node => RNodeName,
responder_id => ?BOB,
responder_amount => 50000 * aest_nodes:gas_price(),
push_amount => 2
},
IAccount = maps:get(initiator_id, ChannelOpts),
RAccount = maps:get(responder_id, ChannelOpts),
NodeNames = [INodeName, RNodeName],

populate_accounts_with_funds([IAccount, RAccount], NodeNames, Config1),
{ok, Chan, _OpenFee, _WFee1, _WFee2} = test_open_and_onchain_operations(ChannelOpts, Config1),
test_offchain_operations(Chan, Config1),

{ok, LatestState} = sc_leave(Chan),

#{config => Config1, latest_state => LatestState, channel => Chan}.

reestablish_state_channel_perform_operations({INodeName, RNodeName},
#{ config := Config
, latest_state := LatestState
, channel := Chan
}, _Config) ->
NodeNames = [INodeName, RNodeName],

{ok, Chan1} = sc_reestablish(Chan, INodeName, RNodeName, LatestState, Config),
test_offchain_operations(Chan1, Config),

ct:log("Testing mutual close"),
{ok, CloseTxHash, _CloseFee, _IChange, _RChange} = sc_close_mutual(Chan1, initiator),
wait_for_value({txs_on_chain, [CloseTxHash]}, NodeNames, 5000, Config),
ok.

%=== INTERNAL FUNCTIONS ========================================================
Expand Down Expand Up @@ -278,3 +338,62 @@ set_genesis_accounts(Spec) ->
%% have all nodes share the same accounts_test.json
GenesisAccounts = [{PatronAddress, 123400000000000000000000000000}],
Spec#{genesis_accounts => GenesisAccounts}.

encode_calldata(Contract, Fun, Args) ->
{ok, Calldata} = aect_test_utils:encode_call_data(maps:get(src, Contract), Fun, Args),
{ok, aeser_api_encoder:encode(contract_bytearray, Calldata)}.

test_open_and_onchain_operations(#{
initiator_node := INodeName,
initiator_id := IAccount,
initiator_amount := IAmt,
responder_node := RNodeName,
responder_id := RAccount,
responder_amount := RAmt
} = ChannelOpts, Cfg) ->
NodeNames = [INodeName, RNodeName],

ct:log("Opening channel"),
{ok, Chan, TxHash, OpenFee} = sc_open(ChannelOpts, Cfg),
wait_for_value({txs_on_chain, [TxHash]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price() - IAmt - OpenFee}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price() - RAmt}, NodeNames, 10000, Cfg),

ct:log("Testing withdraws"),
{ok, TxHash1, WFee1} = sc_withdraw(Chan, initiator, 20 * aest_nodes:gas_price()),
wait_for_value({txs_on_chain, [TxHash1]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price() - IAmt - OpenFee + 20 - WFee1}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price() - RAmt}, NodeNames, 10000, Cfg),

{ok, TxHash2, WFee2} = sc_withdraw(Chan, responder, 50 * aest_nodes:gas_price()),
wait_for_value({txs_on_chain, [TxHash2]}, NodeNames, 5000, Cfg),
wait_for_value({balance, maps:get(pubkey, IAccount), 200000 * aest_nodes:gas_price() - IAmt - OpenFee + 20 * aest_nodes:gas_price() - WFee1}, NodeNames, 10000, Cfg),
wait_for_value({balance, maps:get(pubkey, RAccount), 200000 * aest_nodes:gas_price() - RAmt + 50 * aest_nodes:gas_price() - WFee2}, NodeNames, 10000, Cfg),
{ok, Chan, OpenFee, WFee1, WFee2}.

test_offchain_operations(Chan, Cfg) ->
ct:log("Testing offchain transfers"),
TransferVolleyF = fun() ->
ok = sc_transfer(Chan, initiator, 1 * aest_nodes:gas_price()),
ok = sc_transfer(Chan, responder, 1 * aest_nodes:gas_price())
end,
[ TransferVolleyF() || _ <- lists:seq(1,4) ],

ct:log("Testing simple contract deployment and calls"),
SimpleContractTestF = fun(Who) ->
SimpleContract = proplists:get_value(simple_contract, Cfg),
{ok, CallData} = encode_calldata(SimpleContract, "init", []),
{ok, SimpleContract1} = sc_deploy_contract(Chan, Who, SimpleContract, CallData),
{ok, CallData1} = encode_calldata(SimpleContract, "main", ["42"]),
{ok, CallRes} = sc_call_contract(Chan, Who, SimpleContract1, CallData1),
#{ <<"return_type">> := <<"ok">>
, <<"return_value">> := _} = CallRes %% TODO: check if return value matches
end,
[ SimpleContractTestF(Role) || Role <- [initiator, responder]].

populate_accounts_with_funds(Accounts, [Node | _] = NodeNames, Cfg) ->
lists:foldl(fun(Account, Nonce) ->
post_spend_tx(Node, ?MIKE, Account, Nonce, #{amount => 200000 * aest_nodes:gas_price()}),
wait_for_value({balance, maps:get(pubkey, Account), 200000 * aest_nodes:gas_price()}, NodeNames, 10000, Cfg),
Nonce + 1
end, 1, Accounts).

0 comments on commit af7ace1

Please sign in to comment.