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).