diff --git a/priv/perf/apps/load_test/lib/runner/utxos.ex b/priv/perf/apps/load_test/lib/runner/utxos.ex new file mode 100644 index 0000000000..a73d760281 --- /dev/null +++ b/priv/perf/apps/load_test/lib/runner/utxos.ex @@ -0,0 +1,26 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule LoadTest.Runner.Utxos do + @moduledoc """ + Utxos tests runner. + """ + use Chaperon.LoadTest + + def scenarios do + [ + {{1, LoadTest.Scenario.Utxos}, %{}} + ] + end +end diff --git a/priv/perf/apps/load_test/lib/scenario/deposits.ex b/priv/perf/apps/load_test/lib/scenario/deposits.ex index eaa24f23a8..cdcb04569a 100644 --- a/priv/perf/apps/load_test/lib/scenario/deposits.ex +++ b/priv/perf/apps/load_test/lib/scenario/deposits.ex @@ -63,7 +63,7 @@ defmodule LoadTest.Scenario.Deposits do session |> Session.add_metric("error_rate", 1) - |> Session.add_error(:test, error) + |> Session.add_error(:error, error) end end diff --git a/priv/perf/apps/load_test/lib/scenario/utxos.ex b/priv/perf/apps/load_test/lib/scenario/utxos.ex new file mode 100644 index 0000000000..c4b4243b35 --- /dev/null +++ b/priv/perf/apps/load_test/lib/scenario/utxos.ex @@ -0,0 +1,141 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule LoadTest.Scenario.Utxos do + @moduledoc """ + The scenario for utxos tests: + + 1. It creates two accounts: the sender and the receiver. + 2. It funds sender with the specified amount on the childchain, checks utxos and balance. + 3. The sender account sends the specifed amount on the childchain to the receiver, + checks its balance on the childchain and utxos for both accounts. + + """ + + use Chaperon.Scenario + + alias Chaperon.Session + alias ExPlasma.Encoding + alias LoadTest.ChildChain.Transaction + alias LoadTest.Ethereum.Account + alias LoadTest.Service.Faucet + alias LoadTest.WatcherInfo.Balance + alias LoadTest.WatcherInfo.Utxo + + @spec run(Session.t()) :: Session.t() + def run(session) do + tps = config(session, [:run_config, :tps]) + period_in_seconds = config(session, [:run_config, :period_in_seconds]) + + total_number_of_transactions = tps * period_in_seconds + period_in_mseconds = period_in_seconds * 1_000 + + session + |> cc_spread( + :create_utxos_and_make_assertions, + total_number_of_transactions, + period_in_mseconds + ) + |> await_all(:create_utxos_and_make_assertions) + end + + def create_utxos_and_make_assertions(session) do + with {:ok, sender, receiver} <- create_accounts(), + {:ok, utxo} <- fund_account(session, sender), + :ok <- spend_utxo(session, utxo, sender, receiver) do + Session.add_metric(session, "error_rate", 0) + else + error -> + log_error(session, "#{__MODULE__} failed with #{inspect(error)}") + + session + |> Session.add_metric("error_rate", 1) + |> Session.add_error(:error, error) + end + end + + defp create_accounts() do + {:ok, sender_address} = Account.new() + {:ok, receiver_address} = Account.new() + + {:ok, sender_address, receiver_address} + end + + defp fund_account(session, account) do + initial_amount = config(session, [:chain_config, :initial_amount]) + token = config(session, [:chain_config, :token]) + + with {:ok, utxo} <- fund_childchain_account(account, initial_amount, token), + :ok <- + fetch_childchain_balance(account, + amount: initial_amount, + token: Encoding.to_binary(token), + error: :wrong_childchain_after_funding + ), + :ok <- validate_utxos(account, %{utxo | owner: account.addr}) do + {:ok, utxo} + end + end + + defp validate_utxos(account, utxo) do + utxo_with_owner = + case utxo do + :empty -> :empty + _ -> %{utxo | owner: account.addr} + end + + case Utxo.get_utxos(account, utxo_with_owner) do + {:ok, _} -> :ok + _other -> :invalid_utxos + end + end + + defp fund_childchain_account(address, amount, token) do + case Faucet.fund_child_chain_account(address, amount, token) do + {:ok, utxo} -> {:ok, utxo} + _ -> :failed_to_fund_childchain_account + end + end + + defp spend_utxo(session, utxo, sender, receiver) do + amount = config(session, [:chain_config, :initial_amount]) + token = config(session, [:chain_config, :token]) + fee = config(session, [:chain_config, :fee]) + amount_to_transfer = amount - fee + + with [new_utxo] <- Transaction.spend_utxo(utxo, amount_to_transfer, fee, sender, receiver, token), + :ok <- validate_utxos(sender, :empty), + :ok <- validate_utxos(receiver, %{new_utxo | owner: receiver.addr}), + :ok <- + fetch_childchain_balance(sender, amount: 0, token: Encoding.to_binary(token), error: :wrong_sender_balance), + :ok <- + fetch_childchain_balance(receiver, + amount: amount_to_transfer, + token: Encoding.to_binary(token), + error: :wrong_sender_balance + ) do + :ok + end + end + + defp fetch_childchain_balance(account, amount: amount, token: token, error: error) do + childchain_balance = Balance.fetch_balance(account.addr, amount, token) + + case childchain_balance do + nil when amount == 0 -> :ok + %{"amount" => ^amount} -> :ok + _ -> error + end + end +end diff --git a/priv/perf/apps/load_test/lib/test_runner/config.ex b/priv/perf/apps/load_test/lib/test_runner/config.ex index 857d666974..939c5aa586 100644 --- a/priv/perf/apps/load_test/lib/test_runner/config.ex +++ b/priv/perf/apps/load_test/lib/test_runner/config.ex @@ -19,7 +19,8 @@ defmodule LoadTest.TestRunner.Config do alias ExPlasma.Encoding @tests %{ - "deposits" => LoadTest.Runner.Deposits + "deposits" => LoadTest.Runner.Deposits, + "utxos" => LoadTest.Runner.Utxos } @configs %{ @@ -29,6 +30,11 @@ defmodule LoadTest.TestRunner.Config do deposited_amount: 200_000_000_000_000_000, transferred_amount: 100_000_000_000_000_000, gas_price: 2_000_000_000 + }, + "utxos" => %{ + token: "0x0000000000000000000000000000000000000000", + initial_amount: 760, + fee: 75 } } diff --git a/priv/perf/apps/load_test/lib/watcher_info/balance.ex b/priv/perf/apps/load_test/lib/watcher_info/balance.ex index 2383c70de5..9aed23d8b9 100644 --- a/priv/perf/apps/load_test/lib/watcher_info/balance.ex +++ b/priv/perf/apps/load_test/lib/watcher_info/balance.ex @@ -14,7 +14,7 @@ defmodule LoadTest.WatcherInfo.Balance do @moduledoc """ - Functions related to balanes on the childchain + Functions related to balances on the childchain """ require Logger diff --git a/priv/perf/apps/load_test/lib/watcher_info/utxo.ex b/priv/perf/apps/load_test/lib/watcher_info/utxo.ex new file mode 100644 index 0000000000..da3280c068 --- /dev/null +++ b/priv/perf/apps/load_test/lib/watcher_info/utxo.ex @@ -0,0 +1,100 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License.w + +defmodule LoadTest.WatcherInfo.Utxo do + @moduledoc """ + Functions for retrieving utxos through WatcherInfo API. + """ + require Logger + + alias LoadTest.Connection.WatcherInfo + alias LoadTest.Ethereum.Account + alias LoadTest.Service.Sync + alias LoadTest.Utils.Encoding + + @poll_timeout 60_000 + + @spec get_utxos(Account.addr_t(), ExPlasma.Utxo.t() | nil | :empty) :: {:ok, [] | ExPlasma.Utxo.t()} | no_return + def get_utxos(sender, utxo \\ nil) do + Sync.repeat_until_success( + fn -> + fetch_utxos(sender, utxo) + end, + @poll_timeout, + "Failed to fetch utxos" + ) + end + + defp fetch_utxos(sender, utxo) do + client = WatcherInfo.client() + body = %WatcherInfoAPI.Model.AddressBodySchema1{address: Encoding.to_hex(sender.addr)} + account_utxos = WatcherInfoAPI.Api.Account.account_get_utxos(client, body) + + case account_utxos do + {:ok, result} -> + result.body + |> Jason.decode!() + |> find_utxo(utxo) + + other -> + other + end + end + + defp find_utxo(decoded_response, nil) do + {:ok, decoded_response} + end + + defp find_utxo(%{"data" => []}, :empty) do + {:ok, []} + end + + defp find_utxo(decoded_response, :empty) do + {:error, decoded_response} + end + + defp find_utxo(decoded_response, utxo) do + do_find_utxo(decoded_response, utxo) + end + + defp do_find_utxo(response, utxo) do + found_utxo = + Enum.find(response["data"], fn %{ + "amount" => amount, + "blknum" => blknum, + "currency" => currency, + "oindex" => oindex, + "otype" => otype, + "owner" => owner, + "txindex" => txindex + } -> + current_utxo = %ExPlasma.Utxo{ + amount: amount, + blknum: blknum, + currency: Encoding.to_binary(currency), + oindex: oindex, + output_type: otype, + owner: Encoding.to_binary(owner), + txindex: txindex + } + + current_utxo == utxo + end) + + case found_utxo do + nil -> {:error, response} + _ -> {:ok, found_utxo} + end + end +end diff --git a/priv/perf/apps/load_test/test/load_tests/runner/utxos_test.exs b/priv/perf/apps/load_test/test/load_tests/runner/utxos_test.exs new file mode 100644 index 0000000000..7a352feb57 --- /dev/null +++ b/priv/perf/apps/load_test/test/load_tests/runner/utxos_test.exs @@ -0,0 +1,42 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule LoadTest.Runner.UtxosTest do + use ExUnit.Case + + @moduletag :utxos + + test "deposits test" do + token = "0x0000000000000000000000000000000000000000" + initial_amount = 760 + fee = 75 + + config = %{ + chain_config: %{ + token: token, + initial_amount: initial_amount, + fee: fee + }, + run_config: %{ + tps: 1, + period_in_seconds: 20 + }, + timeout: :infinity + } + + result = Chaperon.run_load_test(LoadTest.Runner.Utxos, config: config) + + assert result.metrics["error_rate"][:mean] == 0.0 + end +end diff --git a/priv/perf/config/config.exs b/priv/perf/config/config.exs index aad0cb0595..f9502051ec 100644 --- a/priv/perf/config/config.exs +++ b/priv/perf/config/config.exs @@ -14,7 +14,7 @@ config :ethereumex, config :load_test, pool_size: 5000, max_connection: 5000, - retry_sleep: System.get_env("RETRY_SLEEP") || 1_000, + retry_sleep: "RETRY_SLEEP" |> System.get_env("1000") |> String.to_integer(), child_chain_url: System.get_env("CHILD_CHAIN_URL") || "http://localhost:9656", watcher_security_url: System.get_env("WATCHER_SECURITY_URL") || "http://localhost:7434", watcher_info_url: System.get_env("WATCHER_INFO_URL") || "http://localhost:7534", @@ -23,14 +23,14 @@ config :load_test, "0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce", eth_vault_address: System.get_env("CONTRACT_ADDRESS_ETH_VAULT"), contract_address_payment_exit_game: System.get_env("CONTRACT_ADDRESS_PAYMENT_EXIT_GAME"), - child_block_interval: 1000, + child_block_interval: "CHILD_BLOCK_INTERVAL" |> System.get_env("1000") |> String.to_integer(), contract_address_plasma_framework: System.get_env("CONTRACT_ADDRESS_PLASMA_FRAMEWORK"), erc20_vault_address: System.get_env("CONTRACT_ADDRESS_ERC20_VAULT"), test_currency: "0x0000000000000000000000000000000000000000", faucet_deposit_amount: trunc(:math.pow(10, 14)), - fee_amount: 1, - deposit_finality_margin: 10, - gas_price: 2_000_000_000 + fee_amount: "FEE_AMOUNT" |> System.get_env("75") |> String.to_integer(), + deposit_finality_margin: "DEPOSIT_FINALITY_MARGIN" |> System.get_env("10") |> String.to_integer(), + gas_price: "GAS_PRICE" |> System.get_env("2000000000") |> String.to_integer() config :ex_plasma, eip_712_domain: [