/ system_test / common / aest_oracles_SUITE.erl
aest_oracles_SUITE.erl
  1  -module(aest_oracles_SUITE).
  2  
  3  %=== EXPORTS ===================================================================
  4  
  5  % Common Test exports
  6  -export([all/0]).
  7  -export([init_per_suite/1]).
  8  -export([init_per_testcase/2]).
  9  -export([end_per_testcase/2]).
 10  -export([end_per_suite/1]).
 11  
 12  % Test cases
 13  -export([
 14      test_simple_same_node_query/1,
 15      test_simple_two_nodes_query/1,
 16      test_oracle_ttl_extension/1,
 17      test_pipelined_same_node_query/1,
 18      test_pipelined_two_nodes_query/1
 19  ]).
 20  
 21  -import(aest_nodes, [
 22      setup_nodes/2,
 23      start_node/2,
 24      wait_for_value/4,
 25      wait_for_startup/3,
 26      request/3,
 27      post_spend_tx/5,
 28      post_oracle_register_tx/3,
 29      post_oracle_extend_tx/3,
 30      post_oracle_query_tx/4,
 31      post_oracle_response_tx/3
 32  ]).
 33  
 34  %=== INCLUDES ==================================================================
 35  
 36  -include_lib("stdlib/include/assert.hrl").
 37  
 38  %=== MACROS ====================================================================
 39  
 40  -define(MINING_TIMEOUT,   3000).
 41  -define(SYNC_TIMEOUT,      100).
 42  
 43  -define(MIKE, #{
 44      pubkey => <<200,171,93,11,3,93,177,65,197,27,123,127,177,165,
 45                  190,211,20,112,79,108,85,78,88,181,26,207,191,211,
 46                  40,225,138,154>>,
 47      privkey => <<237,12,20,128,115,166,32,106,220,142,111,97,141,104,201,130,56,
 48                   100,64,142,139,163,87,166,185,94,4,159,217,243,160,169,200,171,
 49                   93,11,3,93,177,65,197,27,123,127,177,165,190,211,20,112,79,108,
 50                   85,78,88,181,26,207,191,211,40,225,138,154>>
 51  }).
 52  
 53  -define(OLIVIA, #{
 54      pubkey => <<103,28,85,70,70,73,69,117,178,180,148,246,81,104,
 55                  33,113,6,99,216,72,147,205,210,210,54,3,122,84,195,
 56                  62,238,132>>,
 57      privkey => <<59,130,10,50,47,94,36,188,50,163,253,39,81,120,89,219,72,88,68,
 58                   154,183,225,78,92,9,216,215,59,108,82,203,25,103,28,85,70,70,
 59                   73,69,117,178,180,148,246,81,104,33,113,6,99,216,72,147,205,
 60                   210,210,54,3,122,84,195,62,238,132>>
 61  }).
 62  
 63  -define(ALICE, #{
 64      pubkey => <<177,181,119,188,211,39,203,57,229,94,108,2,107,214, 167,74,27,
 65                  53,222,108,6,80,196,174,81,239,171,117,158,65,91,102>>,
 66      privkey => <<145,69,14,254,5,22,194,68,118,57,0,134,66,96,8,20,124,253,238,
 67                   207,230,147,95,173,161,192,86,195,165,186,115,251,177,181,119,
 68                   188,211,39,203,57,229,94,108,2,107,214,167,74,27,53,222,108,6,
 69                   80,196,174,81,239,171,117,158,65,91,102>>
 70  }).
 71  
 72  -define(NODE1, #{
 73      name    => node1,
 74      peers   => [],
 75      backend => aest_docker,
 76      source  => {pull, "aeternity/aeternity:local"}
 77  }).
 78  
 79  -define(NODE2, #{
 80      name    => node2,
 81      peers   => [node1],
 82      backend => aest_docker,
 83      source  => {pull, "aeternity/aeternity:local"}
 84  }).
 85  
 86  %=== COMMON TEST FUNCTIONS =====================================================
 87  
 88  all() -> [
 89      test_simple_same_node_query,
 90      test_simple_two_nodes_query,
 91      test_oracle_ttl_extension,
 92      test_pipelined_same_node_query,
 93      test_pipelined_two_nodes_query
 94  ].
 95  
 96  init_per_suite(Config) ->
 97      [
 98          {node_startup_time, 20000}, %% Time may take to get the node to respond to http
 99          {node_shutdown_time, 20000}, %% Time it may take to stop node cleanly
100          {gas_price, aest_nodes:gas_price()}
101      | Config].
102  
103  init_per_testcase(_TC, Config) ->
104      aest_nodes:ct_setup(Config).
105  
106  end_per_testcase(_TC, Config) ->
107      aest_nodes:ct_cleanup(Config).
108  
109  end_per_suite(_Config) -> ok.
110  
111  %=== TEST CASES ================================================================
112  
113  test_simple_same_node_query(Cfg) ->
114      Opts = #{
115          oracle_node => node1,
116          oracle_id => ?OLIVIA,
117          querier_node => node1,
118          querier_id => ?ALICE
119      },
120      simple_query_test(Opts, Cfg).
121  
122  test_simple_two_nodes_query(Cfg) ->
123      Opts = #{
124          oracle_node => node1,
125          oracle_id => ?OLIVIA,
126          querier_node => node2,
127          querier_id => ?ALICE
128      },
129      simple_query_test(Opts, Cfg).
130  
131  simple_query_test(Opts, Cfg) ->
132      GasPrice = proplists:get_value(gas_price, Cfg),
133      #{
134          oracle_node := ONode,
135          oracle_id := OAccount,
136          querier_node := QNode,
137          querier_id := QAccount
138      } = Opts,
139  
140      MPubKey = maps:get(pubkey, ?MIKE),
141      OPubKey = maps:get(pubkey, OAccount),
142      QPubKey = maps:get(pubkey, QAccount),
143      EncMPubKey = aeser_api_encoder:encode(account_pubkey, MPubKey),
144      EncOPubKey = aeser_api_encoder:encode(oracle_pubkey, OPubKey),
145      EncQPubKey = aeser_api_encoder:encode(account_pubkey, QPubKey),
146  
147      %% Setup nodes
148      NodeConfig = #{ beneficiary => EncMPubKey },
149      setup([?NODE1, ?NODE2], NodeConfig, Cfg),
150      NodeNames = [node1, node2],
151      start_node(node1, Cfg),
152      start_node(node2, Cfg),
153      wait_for_startup([node1, node2], 4, Cfg),
154  
155      %% Generate tokens for Mike
156      wait_for_value({balance, MPubKey, 2000000 * GasPrice}, [node1], 10000, Cfg),
157  
158      %% Give some tokens to the oracle account
159      post_spend_tx(node1, ?MIKE, OAccount, 1, #{ amount => 600000 * GasPrice }),
160      wait_for_value({balance, OPubKey, 600000 * GasPrice }, NodeNames, 10000, Cfg),
161  
162      %% Give some tokens to the querier account
163      post_spend_tx(node1, ?MIKE, QAccount, 2, #{ amount => 600000 * GasPrice }),
164      wait_for_value({balance, QPubKey, 600000 * GasPrice}, NodeNames, 10000, Cfg),
165  
166      %% Register oracle
167      #{ tx_hash := RegTxHash } = post_oracle_register_tx(ONode, OAccount, #{
168          nonce           => 1,
169          query_format    => <<"qspec">>,
170          response_format => <<"rspec">>,
171          query_fee       => 1,
172          fee             => 50000 * GasPrice,
173          oracle_ttl      => {block, 2000}
174      }),
175      aest_nodes:wait_for_value({txs_on_chain, [RegTxHash]}, NodeNames, 10000, []),
176  
177      {ok, 200, OracleInfo} =
178          request(node1, 'GetOracleByPubkey', #{ pubkey => EncOPubKey }),
179      ?assertMatch(#{ id := EncOPubKey }, OracleInfo),
180  
181      %% Start an oracle query
182      #{ tx_hash := QueryTxHash } = post_oracle_query_tx(QNode, QAccount, OAccount, #{
183          nonce        => 1,
184          query        => <<"Hidely-Ho">>,
185          query_fee    => 2,
186          fee          => 50000 * GasPrice,
187          query_ttl    => {delta, 100},
188          response_ttl => {delta, 100}
189      }),
190      aest_nodes:wait_for_value({txs_on_chain, [QueryTxHash]}, NodeNames, 10000, []),
191      QueryId = aeo_query:id(QPubKey, 1, OPubKey),
192      EncQueryId = aeser_api_encoder:encode(oracle_query_id, QueryId),
193  
194      {ok, 200, ClosedQueriesInfo} =
195          request(node1, 'GetOracleQueriesByPubkey', #{ pubkey => EncOPubKey, type => closed }),
196      ?assertMatch(#{ oracle_queries := [] }, ClosedQueriesInfo),
197      {ok, 200, AllQueriesInfo} =
198          request(node1, 'GetOracleQueriesByPubkey', #{ pubkey => EncOPubKey, type => all }),
199      ?assertMatch(#{ oracle_queries := [_] }, AllQueriesInfo),
200      [QueryInfo] = maps:get(oracle_queries, AllQueriesInfo),
201      ?assertMatch(#{ id := EncQueryId, oracle_id := EncOPubKey, sender_id := EncQPubKey }, QueryInfo),
202      ?assertEqual({oracle_query, <<"Hidely-Ho">>}, aeser_api_encoder:decode(maps:get(query, QueryInfo))),
203      ?assertEqual({oracle_response, <<>>}, aeser_api_encoder:decode(maps:get(response, QueryInfo))),
204  
205      %% Respond to the oracle query
206      #{ tx_hash := RespTxHash } = post_oracle_response_tx(ONode, OAccount, #{
207          nonce        => 2,
208          query_id     => QueryId,
209          response     => <<"D'oh!">>,
210          response_ttl => {delta, 100},
211          fee          => 50000 * GasPrice
212      }),
213      aest_nodes:wait_for_value({txs_on_chain, [RespTxHash]}, NodeNames, 10000, []),
214  
215      {ok, 200, OpenQueriesInfo} =
216          request(node1, 'GetOracleQueriesByPubkey', #{ pubkey => EncOPubKey, type => <<"open">> }),
217      ?assertMatch(#{ oracle_queries := [] }, OpenQueriesInfo),
218      {ok, 200, QueryInfo2} =
219          request(node1, 'GetOracleQueryByPubkeyAndQueryId', #{ pubkey => EncOPubKey, 'query-id' => EncQueryId }),
220      ?assertMatch(#{ id := EncQueryId, oracle_id := EncOPubKey, sender_id := EncQPubKey }, QueryInfo2),
221      ?assertEqual({oracle_response, <<"D'oh!">>}, aeser_api_encoder:decode(maps:get(response, QueryInfo2))),
222  
223      ok.
224  
225  test_oracle_ttl_extension(Cfg) ->
226      GasPrice = proplists:get_value(gas_price, Cfg),
227      MPubKey = maps:get(pubkey, ?MIKE),
228      OPubKey = maps:get(pubkey, ?OLIVIA),
229      EncMPubKey = aeser_api_encoder:encode(account_pubkey, MPubKey),
230      EncOPubKey = aeser_api_encoder:encode(oracle_pubkey, OPubKey),
231  
232      %% Setup nodes
233      NodeConfig = #{ beneficiary => EncMPubKey },
234      setup([?NODE1], NodeConfig, Cfg),
235      start_node(node1, Cfg),
236      wait_for_startup([node1], 4, Cfg),
237  
238      %% Generate tokens for Mike
239      wait_for_value({balance, MPubKey, 400000}, [node1], 10000, Cfg),
240  
241      %% Give some tokens to the oracle account
242      post_spend_tx(node1, ?MIKE, ?OLIVIA, 1, #{ amount => 200000 * GasPrice }),
243      wait_for_value({balance, OPubKey, 200}, [node1], 10000, Cfg),
244  
245      %% Register oracle
246      #{ tx_hash := RegTxHash } = post_oracle_register_tx(node1, ?OLIVIA, #{
247          nonce           => 1,
248          query_format    => <<"qspec">>,
249          response_format => <<"rspec">>,
250          query_fee       => 1,
251          fee             => 50000 * GasPrice,
252          oracle_ttl      => {block, 200}
253      }),
254      aest_nodes:wait_for_value({txs_on_chain, [RegTxHash]}, [node1], 10000, []),
255  
256      {ok, 200, OracleInfo} =
257          request(node1, 'GetOracleByPubkey', #{ pubkey => EncOPubKey }),
258      ?assertMatch(#{ id := EncOPubKey, ttl := 200 }, OracleInfo),
259  
260      %% Extend oracle's TTL
261      #{ tx_hash := ExtTxHash } = post_oracle_extend_tx(node1, ?OLIVIA, #{
262          nonce      => 2,
263          fee        => 50000 * GasPrice,
264          oracle_ttl => {delta, 100}
265      }),
266      aest_nodes:wait_for_value({txs_on_chain, [ExtTxHash]}, [node1], 10000, []),
267  
268      {ok, 200, OracleInfo2} =
269          request(node1, 'GetOracleByPubkey', #{ pubkey => EncOPubKey }),
270      ?assertMatch(#{ id := EncOPubKey, ttl := 300 }, OracleInfo2),
271  
272      ok.
273  
274  test_pipelined_same_node_query(Cfg) ->
275      Opts = #{
276          oracle_node => node1,
277          oracle_id => ?OLIVIA,
278          querier_node => node1,
279          querier_id => ?ALICE
280      },
281      pipelined_query_test(Opts, Cfg).
282  
283  test_pipelined_two_nodes_query(Cfg) ->
284      Opts = #{
285          oracle_node => node1,
286          oracle_id => ?OLIVIA,
287          querier_node => node2,
288          querier_id => ?ALICE
289      },
290      pipelined_query_test(Opts, Cfg).
291  
292  pipelined_query_test(Opts, Cfg) ->
293      GasPrice = proplists:get_value(gas_price, Cfg),
294      #{
295          oracle_node := ONode,
296          oracle_id := OAccount,
297          querier_node := QNode,
298          querier_id := QAccount
299      } = Opts,
300  
301      MPubKey = maps:get(pubkey, ?MIKE),
302      OPubKey = maps:get(pubkey, OAccount),
303      QPubKey = maps:get(pubkey, QAccount),
304      EncMPubKey = aeser_api_encoder:encode(account_pubkey, MPubKey),
305      EncOPubKey = aeser_api_encoder:encode(oracle_pubkey, OPubKey),
306      EncQPubKey = aeser_api_encoder:encode(account_pubkey, QPubKey),
307  
308      %% Setup nodes
309      NodeConfig = #{ beneficiary => EncMPubKey },
310      setup([?NODE1, ?NODE2], NodeConfig, Cfg),
311      NodeNames = [node1, node2],
312      start_node(node1, Cfg),
313      start_node(node2, Cfg),
314      wait_for_startup([node1, node2], 4, Cfg),
315  
316      %% Give tokens away
317      GiveAwayAmount = 600000 * GasPrice,
318      aest_nodes:wait_for_value({balance, MPubKey, 2*GiveAwayAmount}, [node1], 10000, []),
319      %% Give some tokens to the oracle account/OAccount
320      post_spend_tx(node1, ?MIKE, OAccount, 1, #{ amount => GiveAwayAmount }),
321      %% Give some tokens to the querier account
322      post_spend_tx(node1, ?MIKE, QAccount, 2, #{ amount => GiveAwayAmount }),
323  
324      Fee = 50000 * GasPrice,
325      aest_nodes:wait_for_value({balance, OPubKey, 2*Fee}, [ONode], 10000, []),
326      aest_nodes:wait_for_value({balance, QPubKey, Fee}, [QNode], 10000, []),
327  
328      %% Register oracle
329      #{ tx_hash := _ } = post_oracle_register_tx(ONode, OAccount, #{
330          nonce           => 1,
331          query_format    => <<"qspec">>,
332          response_format => <<"rspec">>,
333          query_fee       => 1,
334          fee             => Fee,
335          oracle_ttl      => {block, 2000}
336      }),
337  
338      %% Start an oracle query
339      #{ tx_hash := _ } = post_oracle_query_tx(QNode, QAccount, OAccount, #{
340          nonce        => 1,
341          query        => <<"Hidely-Ho">>,
342          query_fee    => 2,
343          fee          => Fee,
344          query_ttl    => {delta, 100},
345          response_ttl => {delta, 100}
346      }),
347      QueryId = aeo_query:id(QPubKey, 1, OPubKey),
348      EncQueryId = aeser_api_encoder:encode(oracle_query_id, QueryId),
349  
350      %% Respond to the oracle query
351      #{ tx_hash := RespTxHash } = post_oracle_response_tx(ONode, OAccount, #{
352          nonce        => 2,
353          query_id     => QueryId,
354          response     => <<"D'oh!">>,
355          response_ttl => {delta, 100},
356          fee          => Fee
357      }),
358  
359      %% Wait for the response transaction to get in the chain
360      aest_nodes:wait_for_value({txs_on_chain, [RespTxHash]}, NodeNames, 10000, []),
361  
362      {ok, 200, OracleInfo} =
363          request(node1, 'GetOracleByPubkey', #{ pubkey => EncOPubKey }),
364      ?assertMatch(#{ id := EncOPubKey }, OracleInfo),
365      {ok, 200, OpenQueriesInfo} =
366          request(node1, 'GetOracleQueriesByPubkey', #{ pubkey => EncOPubKey, type => <<"open">> }),
367      ?assertMatch(#{ oracle_queries := [] }, OpenQueriesInfo),
368      {ok, 200, QueryInfo} =
369          request(node1, 'GetOracleQueryByPubkeyAndQueryId', #{ pubkey => EncOPubKey, 'query-id' => EncQueryId }),
370      ?assertMatch(#{ id := EncQueryId, oracle_id := EncOPubKey, sender_id := EncQPubKey }, QueryInfo),
371      ?assertEqual({oracle_query, <<"Hidely-Ho">>}, aeser_api_encoder:decode(maps:get(query, QueryInfo))),
372      ?assertEqual({oracle_response, <<"D'oh!">>}, aeser_api_encoder:decode(maps:get(response, QueryInfo))),
373  
374      ok.
375  
376  %=== INTERNAL FUNCTIONS ========================================================
377  
378  setup(NodeSpecs, Config, Cfg) ->
379      setup_nodes([maps:put(config, Config, N) || N <- NodeSpecs], Cfg).