diff --git a/.circleci/config.yml b/.circleci/config.yml index 1e36b810dc..c7ece304b9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,6 +55,43 @@ commands: name: Attach workspace at: . + make_docker_images: + description: Builds docker images + steps: + - run: make docker-child_chain + - run: make docker-watcher + - run: make docker-watcher_info + + install_elixir_and_check_docker_status: + description: Installs elixir and checks if docker is healthy + steps: + - run: + name: Print docker states + command: | + docker image ls + docker-compose ps + - restore_cache: + key: v2-asdf-install + - run: + name: Install Erlang and Elixir + command: | + [ -d ~/.asdf-vm ] || git clone https://github.com/asdf-vm/asdf.git ~/.asdf-vm --branch v0.7.4 + echo 'source ~/.asdf-vm/asdf.sh' >> $BASH_ENV + source $BASH_ENV + asdf plugin-add erlang || asdf plugin-update erlang + asdf plugin-add elixir || asdf plugin-update elixir + asdf install + no_output_timeout: 2400 + - save_cache: + key: v2-asdf-install + paths: + - ~/.asdf + - ~/.asdf-vm + - run: make install-hex-rebar + - run: sh .circleci/status.sh + - restore_cache: + key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + jobs: barebuild: executor: metal @@ -446,40 +483,13 @@ jobs: name: Setup data dir command: | [ -d data ] || mkdir data && chmod 777 data - - run: make docker-child_chain - - run: make docker-watcher - - run: make docker-watcher_info + - make_docker_images - run: name: Start daemon services command: | cd priv/cabbage make start_daemon_services-2 || (START_RESULT=$?; docker-compose logs; exit $START_RESULT;) - - run: - name: Print docker states - command: | - docker image ls - docker-compose ps - - restore_cache: - key: v2-asdf-install - - run: - name: Install Erlang and Elixir - command: | - [ -d ~/.asdf-vm ] || git clone https://github.com/asdf-vm/asdf.git ~/.asdf-vm --branch v0.7.4 - echo 'source ~/.asdf-vm/asdf.sh' >> $BASH_ENV - source $BASH_ENV - asdf plugin-add erlang || asdf plugin-update erlang - asdf plugin-add elixir || asdf plugin-update elixir - asdf install - no_output_timeout: 2400 - - save_cache: - key: v2-asdf-install - paths: - - ~/.asdf - - ~/.asdf-vm - - run: make install-hex-rebar - - run: sh .circleci/status.sh - - restore_cache: - key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + - install_elixir_and_check_docker_status - run: name: Run specs command: | @@ -488,12 +498,6 @@ jobs: make generate_api_code mix deps.get mix test - - run: - name: Run load test - command: | - cd priv/perf - make init - make test - run: name: (Cabbage) Format generated code and check for warnings command: | @@ -504,9 +508,42 @@ jobs: mix format apps/watcher_info_api/lib/watcher_info_api/model/*.ex mix format apps/watcher_security_critical_api/lib/watcher_security_critical_api/model/*.ex MIX_ENV=test mix do compile --warnings-as-errors --ignore-module-conflict --force, test --exclude test + - save_cache: + key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + paths: + - "priv/cabbage/deps" + - run: + name: (Cabbage) Credo and formatting + command: | + cd priv/cabbage + mix do credo, format --check-formatted --dry-run + + test_docker_compose_performance: + machine: + image: ubuntu-1604:201903-01 + steps: + - checkout + - run: + name: Setup data dir + command: | + [ -d data ] || mkdir data && chmod 777 data + - make_docker_images + - run: + name: Start daemon services + command: | + SNAPSHOT=SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120 make init_test && docker-compose -f ./docker-compose.yml up -d || (START_RESULT=$?; docker-compose logs; exit $START_RESULT;) + - install_elixir_and_check_docker_status + - run: + name: Run load test + command: | + export $(cat ./localchain_contract_addresses.env | xargs) + cd priv/perf + make init + make test - run: name: (Perf) Format generated code and check for warnings command: | + export $(cat ./localchain_contract_addresses.env | xargs) cd priv/perf # run format ONLY on formatted code so that it cleans up quoted atoms because # we cannot exclude folders to --warnings-as-errors @@ -515,13 +552,8 @@ jobs: - save_cache: key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum "mix.lock" }} paths: - - "priv/cabbage/deps" - "priv/perf/deps" - - run: - name: (Cabbage) Credo and formatting - command: | - cd priv/cabbage - mix do credo, format --check-formatted --dry-run + - run: name: (Perf) Credo and formatting command: | @@ -546,40 +578,13 @@ jobs: [ -d data1 ] || mkdir data1 && chmod 777 data1 [ -d data2 ] || mkdir data2 && chmod 777 data2 [ -d data ] || mkdir data && chmod 777 data - - run: make docker-child_chain - - run: make docker-watcher - - run: make docker-watcher_info + - make_docker_images - run: name: Start daemon services command: | cd priv/cabbage make start_daemon_services_reorg-2 || (START_RESULT=$?; docker-compose logs; exit $START_RESULT;) - - run: - name: Print docker states - command: | - docker image ls - docker-compose ps - - restore_cache: - key: v2-asdf-install - - run: - name: Install Erlang and Elixir - command: | - [ -d ~/.asdf-vm ] || git clone https://github.com/asdf-vm/asdf.git ~/.asdf-vm --branch v0.7.4 - echo 'source ~/.asdf-vm/asdf.sh' >> $BASH_ENV - source $BASH_ENV - asdf plugin-add erlang || asdf plugin-update erlang - asdf plugin-add elixir || asdf plugin-update elixir - asdf install - no_output_timeout: 2400 - - save_cache: - key: v2-asdf-install - paths: - - ~/.asdf - - ~/.asdf-vm - - run: make install-hex-rebar - - run: sh .circleci/status.sh - - restore_cache: - key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum "mix.lock" }} + - install_elixir_and_check_docker_status - run: name: Print watcher logs command: make cabbage-reorg-watcher-logs @@ -714,12 +719,6 @@ jobs: command: | cd priv/cabbage mix test - - run: - name: Run load test - command: | - cd priv/perf - make init - make test publish_child_chain: machine: @@ -920,6 +919,9 @@ workflows: - test_docker_compose_release: requires: [build] filters: *all_branches_and_tags + - test_docker_compose_performance: + requires: [build] + filters: *all_branches_and_tags - test_docker_compose_reorg: requires: [build] filters: *all_branches_and_tags diff --git a/apps/omg_performance/lib/omg_performance/block_creator.ex b/apps/omg_performance/lib/omg_performance/block_creator.ex deleted file mode 100644 index 9482a1f3b4..0000000000 --- a/apps/omg_performance/lib/omg_performance/block_creator.ex +++ /dev/null @@ -1,60 +0,0 @@ -# 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 OMG.Performance.BlockCreator do - @moduledoc """ - Module simulates forming new block on the child chain at specified time intervals - """ - - use GenServer - use OMG.Utils.LoggerExt - - @initial_block_number 1000 - - @doc """ - Starts the process. Only one process of BlockCreator can be started. - """ - def start_link(block_every_ms) do - GenServer.start_link(__MODULE__, {@initial_block_number, block_every_ms}, name: __MODULE__) - end - - @doc """ - Initializes the process with @initial_block_number stored in the process state. - Reschedules call to itself wchich starts block forming loop. - """ - @spec init({integer, integer}) :: {:ok, {integer, integer}} - def init({blknum, block_every_ms}) do - _ = Logger.debug("init called with args: '#{inspect(blknum)}'") - reschedule_task(block_every_ms) - {:ok, {blknum, block_every_ms}} - end - - @doc """ - Forms new block, reports time consumed by API response and reschedule next call - in @request_block_creation_every_ms milliseconds. - """ - def handle_info(:do, {blknum, block_every_ms}) do - child_block_interval = 1000 - - OMG.State.form_block() - OMG.Performance.SenderManager.block_forming_time(blknum, 0) - - reschedule_task(block_every_ms) - {:noreply, {blknum + child_block_interval, block_every_ms}} - end - - defp reschedule_task(block_every_ms) do - Process.send_after(self(), :do, block_every_ms) - end -end diff --git a/apps/omg_performance/lib/omg_performance/http_rpc/watcher_client.ex b/apps/omg_performance/lib/omg_performance/http_rpc/watcher_client.ex deleted file mode 100644 index a36add723f..0000000000 --- a/apps/omg_performance/lib/omg_performance/http_rpc/watcher_client.ex +++ /dev/null @@ -1,85 +0,0 @@ -# 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 OMG.Performance.HttpRPC.WatcherClient do - @moduledoc """ - Provides access to Watcher's RPC API - """ - - alias OMG.Utils.HttpRPC.Adapter - alias OMG.Utils.HttpRPC.Encoding - - @address_bytes_size 20 - - @doc """ - Gets Watcher status - """ - @spec get_status(binary()) :: OMG.Watcher.HttpRPC.Client.response_t() - def get_status(url), do: call(%{}, "status.get", url) - - @doc """ - Gets standard exit data from Watcher's RPC - """ - @spec get_exit_data(non_neg_integer(), binary()) :: OMG.Watcher.HttpRPC.Client.response_t() - def get_exit_data(encoded_position, url), - do: - %{utxo_pos: encoded_position} - |> call("utxo.get_exit_data", url) - |> decode_response() - - @doc """ - Gets utxo for given address from Watcher's RPC - """ - @spec get_exitable_utxos(OMG.Crypto.address_t(), binary()) :: OMG.Watcher.HttpRPC.Client.response_t() - def get_exitable_utxos(address, url) when is_binary(address) and byte_size(address) == @address_bytes_size, - do: call(%{address: Encoding.to_hex(address)}, "account.get_exitable_utxos", url) - - def get_exit_challenge(utxo_pos, url) do - %{utxo_pos: utxo_pos} - |> call("utxo.get_challenge_data", url) - |> decode_response() - end - - defp call(params, path, url), - do: Adapter.rpc_post(params, path, url) |> Adapter.get_response_body() - - defp decode_response({:ok, %{proof: proof, txbytes: txbytes, utxo_pos: utxo_pos}}) do - {:ok, - %{ - proof: decode16!(proof), - txbytes: decode16!(txbytes), - utxo_pos: utxo_pos - }} - end - - defp decode_response( - {:ok, %{exiting_tx: exiting_tx, txbytes: txbytes, sig: sig, exit_id: exit_id, input_index: input_index}} - ) do - {:ok, - %{ - exit_id: exit_id, - input_index: input_index, - exiting_tx: decode16!(exiting_tx), - txbytes: decode16!(txbytes), - sig: decode16!(sig) - }} - end - - defp decode_response(error), do: error - - defp decode16!(hexstr) do - {:ok, bin} = Encoding.from_hex(hexstr) - bin - end -end diff --git a/apps/omg_performance/lib/omg_performance/simple_perftest.ex b/apps/omg_performance/lib/omg_performance/simple_perftest.ex deleted file mode 100644 index c2f6876409..0000000000 --- a/apps/omg_performance/lib/omg_performance/simple_perftest.ex +++ /dev/null @@ -1,176 +0,0 @@ -# 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 OMG.Performance.SimplePerftest do - @moduledoc """ - The simple performance tests runs the critical transaction processing chunk of the child chain. - - This allows to easily test the critical path of processing transactions, and profile it using `:fprof`. - """ - use OMG.Utils.LoggerExt - require OMG.Utxo - - alias OMG.Eth.Configuration - alias OMG.TestHelper - alias OMG.Utxo - - @eth OMG.Eth.zero_address() - - @doc """ - Runs test with `ntx_to_send` txs for each of the `nspenders` senders with given options. - The test is run on a local limited child chain app instance, not doing any Ethereum connectivity-related activities. - The child chain is setup and torn down as part of the test invocation. - - ## Usage - - From an `iex -S mix run --no-start` shell - - ``` - use OMG.Performance - - Performance.SimplePerftest.start(50, 16) - ``` - - The results are going to be waiting for you in a file within `destdir` and will be logged. - - Options: - - :destdir - directory where the results will be put, relative to `pwd`, defaults to `"."` - - :profile - if `true`, a `:fprof` will profile the test run, defaults to `false` - - :block_every_ms - how often should the artificial block creation be triggered, defaults to `2000` - - :randomized - whether the non-change outputs of the txs sent out will be random or equal to sender (if `false`), - defaults to `true` - - **NOTE**: - - With `profile: :fprof` it will print a warning: - ``` - Warning: {erlang, trace, 3} called in "<0.514.0>" - trace may become corrupt! - ``` - It is caused by using `procs: :all` in options. So far we're not using `:erlang.trace/3` in our code, - so it has been ignored. Otherwise it's easy to reproduce and report - (github.com/erlang/otp and the JIRA it points you to). - """ - @spec start(pos_integer(), pos_integer(), keyword()) :: :ok - def start(ntx_to_send, nspenders, opts \\ []) do - _ = - Logger.info( - "Number of spenders: #{inspect(nspenders)}, number of tx to send per spender: #{inspect(ntx_to_send)}." - ) - - defaults = [destdir: ".", profile: false, block_every_ms: 2000] - opts = Keyword.merge(defaults, opts) - - {:ok, started_apps, simple_perftest_chain} = setup_simple_perftest(opts) - - spenders = create_spenders(nspenders) - utxos = create_deposits(spenders, ntx_to_send) - - :ok = OMG.Performance.Runner.run(ntx_to_send, utxos, opts, opts[:profile]) - - cleanup_simple_perftest(started_apps, simple_perftest_chain) - end - - @spec setup_simple_perftest(keyword()) :: {:ok, list, pid} - defp setup_simple_perftest(opts) do - {:ok, dbdir} = Briefly.create(directory: true, prefix: "perftest_db") - Application.put_env(:omg_db, :path, dbdir, persistent: true) - _ = Logger.info("Perftest rocksdb path: #{inspect(dbdir)}") - - :ok = OMG.DB.init() - - started_apps = ensure_all_started([:omg_db, :omg_bus]) - {:ok, simple_perftest_chain} = start_simple_perftest_chain(opts) - - {:ok, started_apps, simple_perftest_chain} - end - - # Selects and starts just necessary components to run the tests. - # We don't want to start the entire `:omg_child_chain` supervision tree because - # we don't want to start services related to root chain tracking (the root chain contract doesn't exist). - # Instead, we start the artificial `BlockCreator` - defp start_simple_perftest_chain(opts) do - _ = - case :ets.info(OMG.ChildChain.Supervisor.blocks_cache()) do - :undefined -> - :ets.new(OMG.ChildChain.Supervisor.blocks_cache(), [:set, :public, :named_table, read_concurrency: true]) - - _ -> - :ok - end - - children = [ - {OMG.ChildChainRPC.Web.Endpoint, []}, - {OMG.State, - [ - fee_claimer_address: Base.decode16!("DEAD000000000000000000000000000000000000"), - child_block_interval: Configuration.child_block_interval(), - metrics_collection_interval: 60_000 - ]}, - {OMG.ChildChain.API.BlocksCache, [ets: OMG.ChildChain.Supervisor.blocks_cache()]}, - {OMG.ChildChain.FeeServer, OMG.ChildChain.Configuration.fee_server_opts()}, - {OMG.Performance.BlockCreator, opts[:block_every_ms]} - ] - - Supervisor.start_link(children, strategy: :one_for_one) - end - - @spec cleanup_simple_perftest(list(), pid) :: :ok - defp cleanup_simple_perftest(started_apps, simple_perftest_chain) do - :ok = Supervisor.stop(simple_perftest_chain) - started_apps |> Enum.reverse() |> Enum.each(&Application.stop/1) - - :ok = Application.put_env(:omg_db, :path, nil) - :ok - end - - # We're not basing on mix to start all neccessary test's components. - defp ensure_all_started(app_list) do - Enum.reduce(app_list, [], fn app, list -> - {:ok, started_apps} = Application.ensure_all_started(app) - list ++ started_apps - end) - end - - @spec create_spenders(pos_integer()) :: list(TestHelper.entity()) - defp create_spenders(nspenders) do - 1..nspenders - |> Enum.map(fn _nspender -> TestHelper.generate_entity() end) - end - - @spec create_deposits(list(TestHelper.entity()), pos_integer()) :: list(map()) - defp create_deposits(spenders, ntx_to_send) do - spenders - |> Enum.with_index(1) - |> Enum.map(&create_deposit(&1, ntx_to_send * 2)) - end - - defp create_deposit({spender, index}, ntx_to_send) do - {:ok, _} = - OMG.State.deposit([ - %{ - # these two are irrelevant - root_chain_txhash: <<0::256>>, - eth_height: 1, - log_index: 0, - owner: spender.addr, - currency: @eth, - amount: ntx_to_send, - blknum: index - } - ]) - - utxo_pos = Utxo.position(index, 0, 0) |> Utxo.Position.encode() - %{owner: spender, utxo_pos: utxo_pos, amount: ntx_to_send} - end -end diff --git a/apps/omg_performance/lib/performance.ex b/apps/omg_performance/lib/performance.ex deleted file mode 100644 index 8f5f1578d5..0000000000 --- a/apps/omg_performance/lib/performance.ex +++ /dev/null @@ -1,109 +0,0 @@ -# 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 OMG.Performance do - @moduledoc """ - OMG network performance tests. Provides general setup and utilities to do the perf tests. - """ - - defmacro __using__(_opt) do - quote do - alias OMG.Performance - alias OMG.Performance.ByzantineEvents - alias OMG.Performance.ExtendedPerftest - alias OMG.Performance.Generators - - import Performance, only: [timeit: 1] - require Performance - - use OMG.Utils.LoggerExt - :ok - end - end - - @doc """ - Sets up the `OMG.Performance` machinery to a required config. Uses some default values, overridable via: - - `opts` - - system env (some entries) - - `config.exs` - in that order of preference. The configuration chosen is put into `Application`'s environment - - Options: - - :ethereum_rpc_url - URL of the Ethereum node's RPC, default `http://localhost:8545` - - :child_chain_url - URL of the Child Chain server's RPC, default `http://localhost:9656` - - :watcher_url - URL of the Watcher's RPC, default `http://localhost:7434` - - :contract_addr - a map with the root chain contract addresses - - If you're testing against a local child chain/watcher instances, consider setting the following configuration: - ``` - config :omg, - deposit_finality_margin: 1 - config :omg_watcher, - exit_finality_margin: 1 - ``` - in order to prevent the apps from waiting for unnecessary confirmations - - """ - def init(opts \\ []) do - {:ok, _} = Application.ensure_all_started(:briefly) - {:ok, _} = Application.ensure_all_started(:ethereumex) - {:ok, _} = Application.ensure_all_started(:hackney) - {:ok, _} = Application.ensure_all_started(:cowboy) - - ethereum_rpc_url = - System.get_env("ETHEREUM_RPC_URL") || Application.get_env(:ethereumex, :url, "http://localhost:8545") - - child_chain_url = - System.get_env("CHILD_CHAIN_URL") || Application.get_env(:omg_watcher, :child_chain_url, "http://localhost:9656") - - watcher_url = - System.get_env("WATCHER_URL") || Application.get_env(:omg_performance, :watcher_url, "http://localhost:7434") - - defaults = [ - ethereum_rpc_url: ethereum_rpc_url, - child_chain_url: child_chain_url, - watcher_url: watcher_url, - contract_addr: nil - ] - - opts = Keyword.merge(defaults, opts) - - :ok = Application.put_env(:ethereumex, :request_timeout, :infinity) - :ok = Application.put_env(:ethereumex, :http_options, recv_timeout: :infinity) - :ok = Application.put_env(:ethereumex, :url, opts[:ethereum_rpc_url]) - :ok = Application.put_env(:omg_watcher, :child_chain_url, opts[:child_chain_url]) - :ok = Application.put_env(:omg_performance, :watcher_url, opts[:watcher_url]) - - :ok - end - - @doc """ - Utility macro which causes the expression given to be timed, the timing logged (`info`) and the original result of the - call to be returned - - ## Examples - - iex> use OMG.Performance - iex> timeit 1+2 - 3 - """ - defmacro timeit(call) do - quote do - {duration, result} = :timer.tc(fn -> unquote(call) end) - duration_s = duration / 1_000_000 - _ = Logger.info("Lasted #{inspect(duration_s)} seconds") - result - end - end -end diff --git a/apps/omg_performance/mix.exs b/apps/omg_performance/mix.exs deleted file mode 100644 index bd4809a6ad..0000000000 --- a/apps/omg_performance/mix.exs +++ /dev/null @@ -1,44 +0,0 @@ -defmodule OMG.Performance.MixProject do - use Mix.Project - - def project() do - [ - app: :omg_performance, - version: "#{String.trim(File.read!("../../VERSION"))}", - build_path: "../../_build", - config_path: "../../config/config.exs", - deps_path: "../../deps", - lockfile: "../../mix.lock", - elixir: "~> 1.8", - elixirc_paths: elixirc_paths(Mix.env()), - start_permanent: Mix.env() == :prod, - deps: deps(), - test_coverage: [tool: ExCoveralls] - ] - end - - def application() do - [ - extra_applications: [:logger, :tools] - ] - end - - # Specifies which paths to compile per environment. - # We don't need the performance app in a production release - defp elixirc_paths(:prod), do: [] - defp elixirc_paths(:dev), do: ["lib"] - defp elixirc_paths(:test), do: ["lib", "test/support"] - - defp deps() do - [ - # TEST ONLY - {:briefly, "~> 0.3.0", only: [:dev, :test]}, - {:omg_utils, in_umbrella: true, only: [:test], runtime: false}, - {:omg_child_chain, in_umbrella: true, only: [:test], runtime: false}, - {:omg_child_chain_rpc, in_umbrella: true, only: [:test], runtime: false}, - {:omg_eth, in_umbrella: true, only: [:test], runtime: false}, - {:omg_watcher, in_umbrella: true, only: [:test], runtime: false}, - {:omg_status, in_umbrella: true, only: [:test], runtime: false} - ] - end -end diff --git a/apps/omg_performance/test/fixtures.exs b/apps/omg_performance/test/fixtures.exs deleted file mode 100644 index c245da2d17..0000000000 --- a/apps/omg_performance/test/fixtures.exs +++ /dev/null @@ -1,101 +0,0 @@ -# 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. - -# unfortunately something is wrong with the fixtures loading in `test_helper.exs` and the following needs to be done -Code.require_file("#{__DIR__}/../../omg_child_chain/test/omg_child_chain/integration/fixtures.exs") - -defmodule OMG.Performance.Fixtures do - use ExUnitFixtures.FixtureModule - - use OMG.Eth.Fixtures - use OMG.ChildChain.Integration.Fixtures - use OMG.Utils.LoggerExt - - deffixture mix_based_watcher(contract) do - _ = contract - - db_path = Briefly.create!(directory: true) - - exexec_opts_for_mix = [ - stdout: :stream, - cd: [Mix.Project.build_path(), "../../"] |> Path.join() |> Path.expand(), - env: %{"MIX_ENV" => to_string(Mix.env())}, - # group 0 will create a new process group, equal to the OS pid of that process - group: 0, - kill_group: true - ] - - {:ok, _db_proc, _ref, [{:stream, db_out, _stream_server}]} = - Exexec.run_link( - "mix ecto.reset && mix run --no-start -e ':ok = OMG.DB.init(\"#{db_path}\")' 2>&1", - exexec_opts_for_mix - ) - - Enum.each(db_out, &log_output("db_init_watcher", &1)) - - watcher_mix_cmd = "mix xomg.watcher.start 2>&1" - - Logger.info("Starting watcher") - - {:ok, watcher_proc, _ref, [{:stream, watcher_out, _stream_server}]} = - Exexec.run_link(watcher_mix_cmd, exexec_opts_for_mix) - - wait_for_start(watcher_out, "Running OMG.WatcherRPC.Web.Endpoint", 20_000, &log_output("watcher", &1)) - - Task.async(fn -> Enum.each(watcher_out, &log_output("watcher", &1)) end) - - on_exit(fn -> - # NOTE see DevGeth.stop/1 for details - _ = Process.monitor(watcher_proc) - - :ok = - case Exexec.stop_and_wait(watcher_proc) do - :normal -> - :ok - - :shutdown -> - :ok - - :noproc -> - :ok - end - - File.rm_rf(db_path) - end) - - :ok - end - - # NOTE: we could dry or do sth about this (copied from Support.DevNode), but this might be removed soon altogether - defp wait_for_start(outstream, look_for, timeout, logger_fn) do - # Monitors the stdout coming out of a process for signal of successful startup - waiting_task_function = fn -> - outstream - |> Stream.map(logger_fn) - |> Stream.take_while(fn line -> not String.contains?(line, look_for) end) - |> Enum.to_list() - end - - waiting_task_function - |> Task.async() - |> Task.await(timeout) - - :ok - end - - defp log_output(prefix, line) do - Logger.debug("#{prefix}: " <> line) - line - end -end diff --git a/apps/omg_performance/test/omg_performance/simple_perftest_test.exs b/apps/omg_performance/test/omg_performance/simple_perftest_test.exs deleted file mode 100644 index 7633bd24f4..0000000000 --- a/apps/omg_performance/test/omg_performance/simple_perftest_test.exs +++ /dev/null @@ -1,83 +0,0 @@ -# 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 OMG.Performance.SimplePerftestTest do - @moduledoc """ - Simple smoke testing of the performance test - """ - - use ExUnit.Case, async: false - - import ExUnit.CaptureIO - - use OMG.Performance - - @moduletag :integration - @moduletag :common - - setup do - :ok = Performance.init() - {:ok, destdir} = Briefly.create(directory: true, prefix: "temp_results") - {:ok, %{destdir: destdir}} - end - - test "Smoke test - run start_simple_perf and see if it doesn't crash", %{destdir: destdir} do - ntxs = 3000 - nsenders = 2 - assert :ok = Performance.SimplePerftest.start(ntxs, nsenders, destdir: destdir) - - assert ["perf_result" <> _ = perf_result] = File.ls!(destdir) - smoke_test_statistics(Path.join(destdir, perf_result), ntxs * nsenders) - end - - test "Smoke test - run start_simple_perf and see if it doesn't crash - with profiling", %{destdir: destdir} do - ntxs = 3 - nsenders = 2 - - fprof_io = - capture_io(fn -> - assert :ok = Performance.SimplePerftest.start(ntxs, nsenders, destdir: destdir, profile: true) - end) - - assert fprof_io =~ "Done!" - - assert ["perf_result" <> _ = prof_results, "perf_result" <> _ = perf_results] = Enum.sort(File.ls!(destdir)) - smoke_test_profiling(Path.join(destdir, prof_results)) - smoke_test_statistics(Path.join(destdir, perf_results), ntxs * nsenders) - end - - test "Smoke test - run start_simple_perf and see if it doesn't crash - overiding block creation", %{destdir: destdir} do - ntxs = 3000 - nsenders = 2 - assert :ok = Performance.SimplePerftest.start(ntxs, nsenders, destdir: destdir, block_every_ms: 3000) - - assert ["perf_result" <> _ = perf_result] = File.ls!(destdir) - smoke_test_statistics(Path.join(destdir, perf_result), ntxs * nsenders) - end - - defp smoke_test_statistics(path, expected_txs) do - assert {:ok, stats} = Jason.decode(File.read!(path)) - - txs_count = - stats - |> Enum.map(fn entry -> entry["txs"] end) - |> Enum.sum() - - assert txs_count == expected_txs - end - - defp smoke_test_profiling(path) do - String.contains?(File.read!(path), "%% Analysis results:\n{") - end -end diff --git a/apps/xomg_tasks/lib/mix/tasks/watcher.ex b/apps/xomg_tasks/lib/mix/tasks/watcher.ex index 3728322a44..81896d2238 100644 --- a/apps/xomg_tasks/lib/mix/tasks/watcher.ex +++ b/apps/xomg_tasks/lib/mix/tasks/watcher.ex @@ -26,6 +26,7 @@ defmodule Mix.Tasks.Xomg.Watcher.Start do def run(args) do args + |> config_db("--db") |> generic_prepare_args() |> generic_run([:omg_watcher, :omg_watcher_rpc]) end diff --git a/config/config.exs b/config/config.exs index 6bdcf92d7e..84f3ca5d81 100644 --- a/config/config.exs +++ b/config/config.exs @@ -232,7 +232,4 @@ config :spandex_phoenix, tracer: OMG.WatcherRPC.Tracer config :briefly, directory: ["/tmp/omisego"] -# `watcher_url` must match respective `:omg_watcher_rpc, OMG.WatcherRPC.Web.Endpoint` -config :omg_performance, watcher_url: "localhost:7434" - import_config "#{Mix.env()}.exs" diff --git a/config/test.exs b/config/test.exs index 152592cc2e..2dd66b432e 100644 --- a/config/test.exs +++ b/config/test.exs @@ -101,8 +101,6 @@ config :omg_eth, eth_node: :geth, run_test_eth_dev_node: true -config :omg_performance, watcher_url: "localhost:7435" - config :omg_status, metrics: false, environment: :test, diff --git a/coveralls.json b/coveralls.json index 981fd3a4ca..d1ad1dd7f7 100644 --- a/coveralls.json +++ b/coveralls.json @@ -6,7 +6,6 @@ "apps/omg_child_chain_rpc/test/support", "apps/omg_db/test/support", "apps/omg_eth/test/support", - "apps/omg_performance/test/support", "apps/omg_status/test/support", "apps/omg_utils/test/support", "apps/omg_watcher/test/support", diff --git a/dialyzer.ignore-warnings b/dialyzer.ignore-warnings index b427b4692b..03c4a7e5fe 100644 --- a/dialyzer.ignore-warnings +++ b/dialyzer.ignore-warnings @@ -1,4 +1,3 @@ -lib/omg_performance/runner.ex:61: Type specification 'Elixir.OMG.Performance.Runner':wait_for(registry::pid() | atom()) -> 'ok' is a supertype of the success typing: 'Elixir.OMG.Performance.Runner':wait_for(pid()) -> 'ok' test/support/conformance/merkle_proofs.ex:43: Expression produces a value of type <<_:224>>, but this value is unmatched test/support/conformance/merkle_proofs.ex:55: Guard test _@3::<<_:224>> =:= 'false' can never succeed test/support/conformance/signatures_hashes.ex:49: Expression produces a value of type {'error','malformed_address' | 'malformed_inputs' | 'malformed_metadata' | 'malformed_outputs' | 'malformed_transaction' | 'malformed_transaction_rlp' | 'unrecognized_transaction_type'}, but this value is unmatched diff --git a/docs/details.md b/docs/details.md index 827901f030..4e4c6fc85f 100644 --- a/docs/details.md +++ b/docs/details.md @@ -7,7 +7,7 @@ * [Using the child chain server's API](#using-the-child-chain-servers-api) * [HTTP-RPC](#http-rpc) * [Ethereum private key management](#ethereum-private-key-management) - * [geth](#geth) + * [geth](#geth) * [Managing the operator address](#managing-the-operator-address) * [Nonces restriction](#nonces-restriction) * [Funding the operator address](#funding-the-operator-address) @@ -33,7 +33,6 @@ The general idea of the apps responsibilities is: - `omg_child_chain_rpc` - an HTTP-RPC server being the gateway to `omg_child_chain` - `omg_db` - wrapper around the child chain server's database to store the UTXO set and blocks necessary for state persistence - `omg_eth` - wrapper around the [Ethereum RPC client](https://github.com/exthereum/ethereumex) - - `omg_performance` - performance tester for the child chain server - `omg_status` - application monitoring facilities - `omg_utils` - various non-omg-specific shared code - `omg_watcher` - the [Watcher](#watcher-and-watcher-info) @@ -198,7 +197,7 @@ Affects how quick the services reading Ethereum events realize there's a new blo * **`block_queue_eth_height_check_interval_ms`** - polling interval for checking whether the root chain had progressed for the `BlockQueue` exclusively * **`fee_adapter_check_interval_ms`** - interval for checking fees updates from the fee adapter. -* +* * **`fee_buffer_duration_ms`** - duration for which a fee is still valid after beeing updated. * **`block_submit_every_nth`** - how many new Ethereum blocks must be mined, since previous submission **attempt**, before another block is going to be formed and submitted. diff --git a/priv/perf/Makefile b/priv/perf/Makefile index 1cb86b38b8..4c28ec48b4 100644 --- a/priv/perf/Makefile +++ b/priv/perf/Makefile @@ -16,7 +16,7 @@ init: mix deps.get test: # runs test against child chain and geth provided in test docker containers - CONTRACT_ADDRESS_ETH_VAULT=0x4e3aeff70f022a6d4cc5947423887e7152826cf7 LOAD_TEST_FAUCET_PRIVATE_KEY=0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce CONTRACT_ADDRESS_PLASMA_FRAMEWORK=0xc673e4ffcb8464faff908a6804fe0e635af0ea2f mix test + LOAD_TEST_FAUCET_PRIVATE_KEY=0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce mix test --trace format-code-check-warnings: - CONTRACT_ADDRESS_ETH_VAULT=0x4e3aeff70f022a6d4cc5947423887e7152826cf7 LOAD_TEST_FAUCET_PRIVATE_KEY=0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce CONTRACT_ADDRESS_PLASMA_FRAMEWORK=0xc673e4ffcb8464faff908a6804fe0e635af0ea2f MIX_ENV=test mix do compile --warnings-as-errors --ignore-module-conflict --force, test --exclude test + LOAD_TEST_FAUCET_PRIVATE_KEY=0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce MIX_ENV=test mix do compile --warnings-as-errors --ignore-module-conflict --force, test --exclude test diff --git a/priv/perf/apps/load_test/lib/child_chain/abi.ex b/priv/perf/apps/load_test/lib/child_chain/abi.ex new file mode 100644 index 0000000000..fb53d0a59d --- /dev/null +++ b/priv/perf/apps/load_test/lib/child_chain/abi.ex @@ -0,0 +1,107 @@ +# 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.ChildChain.Abi do + @moduledoc """ + Functions that provide ethereum log decoding + """ + alias ExPlasma.Encoding + alias LoadTest.ChildChain.Abi.AbiEventSelector + alias LoadTest.ChildChain.Abi.AbiFunctionSelector + alias LoadTest.ChildChain.Abi.Fields + + def decode_function(enriched_data, signature) do + "0x" <> data = enriched_data + <> = :keccakf1600.hash(:sha3_256, signature) + method_id |> Encoding.to_hex() |> Kernel.<>(data) |> Encoding.to_binary() |> decode_function() + end + + def decode_function(enriched_data) do + function_specs = + Enum.reduce(AbiFunctionSelector.module_info(:exports), [], fn + {:module_info, 0}, acc -> acc + {function, 0}, acc -> [apply(AbiFunctionSelector, function, []) | acc] + _, acc -> acc + end) + + {function_spec, data} = ABI.find_and_decode(function_specs, enriched_data) + decode_function_call_result(function_spec, data) + end + + def decode_log(log) do + event_specs = + Enum.reduce(AbiEventSelector.module_info(:exports), [], fn + {:module_info, 0}, acc -> acc + {function, 0}, acc -> [apply(AbiEventSelector, function, []) | acc] + _, acc -> acc + end) + + topics = + Enum.map(log["topics"], fn + nil -> nil + topic -> Encoding.to_binary(topic) + end) + + data = Encoding.to_binary(log["data"]) + + {event_spec, data} = + ABI.Event.find_and_decode( + event_specs, + Enum.at(topics, 0), + Enum.at(topics, 1), + Enum.at(topics, 2), + Enum.at(topics, 3), + data + ) + + data + |> Enum.into(%{}, fn {key, _type, _indexed, value} -> {key, value} end) + |> Fields.rename(event_spec) + |> common_parse_event(log) + end + + def common_parse_event( + result, + %{"blockNumber" => eth_height, "transactionHash" => root_chain_txhash, "logIndex" => log_index} = event + ) do + # NOTE: we're using `put_new` here, because `merge` would allow us to overwrite data fields in case of conflict + result + |> Map.put_new(:eth_height, Encoding.to_int(eth_height)) + |> Map.put_new(:root_chain_txhash, Encoding.to_binary(root_chain_txhash)) + |> Map.put_new(:log_index, Encoding.to_int(log_index)) + # just copy `event_signature` over, if it's present (could use tidying up) + |> Map.put_new(:event_signature, event[:event_signature]) + end + + defp decode_function_call_result(function_spec, [values]) when is_tuple(values) do + function_spec.input_names + |> Enum.zip(Tuple.to_list(values)) + |> Enum.into(%{}) + |> Fields.rename(function_spec) + end + + # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632 + defp decode_function_call_result(%{function: "startExit"} = function_spec, values) do + function_spec.input_names + |> Enum.zip(values) + |> Enum.into(%{}) + |> Fields.rename(function_spec) + end + + defp decode_function_call_result(function_spec, values) do + function_spec.input_names + |> Enum.zip(values) + |> Enum.into(%{}) + end +end diff --git a/priv/perf/apps/load_test/lib/child_chain/abi/abi_event_selector.ex b/priv/perf/apps/load_test/lib/child_chain/abi/abi_event_selector.ex new file mode 100644 index 0000000000..6ec8b5ed79 --- /dev/null +++ b/priv/perf/apps/load_test/lib/child_chain/abi/abi_event_selector.ex @@ -0,0 +1,34 @@ +# 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.ChildChain.Abi.AbiEventSelector do + @moduledoc """ + We define Solidity Event selectors that help us decode returned values from function calls. + Function names are to be used as inputs to Event Fetcher. + Function names describe the type of the event Event Fetcher will retrieve. + """ + + @spec deposit_created() :: ABI.FunctionSelector.t() + def deposit_created() do + %ABI.FunctionSelector{ + function: "DepositCreated", + input_names: ["depositor", "blknum", "token", "amount"], + inputs_indexed: [true, true, true, false], + method_id: <<24, 86, 145, 34>>, + returns: [], + type: :event, + types: [:address, {:uint, 256}, :address, {:uint, 256}] + } + end +end diff --git a/apps/omg_performance/test/test_helper.exs b/priv/perf/apps/load_test/lib/child_chain/abi/abi_function_selector.ex similarity index 50% rename from apps/omg_performance/test/test_helper.exs rename to priv/perf/apps/load_test/lib/child_chain/abi/abi_function_selector.ex index 465983930c..e3d6f7c362 100644 --- a/apps/omg_performance/test/test_helper.exs +++ b/priv/perf/apps/load_test/lib/child_chain/abi/abi_function_selector.ex @@ -12,8 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -ExUnit.configure(exclude: [integration: true, property: true, wrappers: true]) -ExUnitFixtures.start() -ExUnit.start() -{:ok, _} = Application.ensure_all_started(:briefly) -{:ok, _} = Application.ensure_all_started(:erlexec) +defmodule LoadTest.ChildChain.Abi.AbiFunctionSelector do + @moduledoc """ + + We define Solidity Function selectors that help us decode returned values from function calls + """ + # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632 + def blocks() do + %ABI.FunctionSelector{ + function: "blocks", + input_names: ["block_hash", "block_timestamp"], + inputs_indexed: nil, + method_id: <<242, 91, 63, 153>>, + # returns: [bytes: 32, uint: 256], + type: :function, + # types: [uint: 256] + types: [bytes: 32, uint: 256] + } + end +end diff --git a/priv/perf/apps/load_test/lib/child_chain/abi/fields.ex b/priv/perf/apps/load_test/lib/child_chain/abi/fields.ex new file mode 100644 index 0000000000..73e73ce115 --- /dev/null +++ b/priv/perf/apps/load_test/lib/child_chain/abi/fields.ex @@ -0,0 +1,39 @@ +# 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.ChildChain.Abi.Fields do + @moduledoc """ + Adapt to naming from contracts to elixir-omg. + + I need to do this even though I'm bleeding out of my eyes. + """ + def rename(data, %ABI.FunctionSelector{function: "DepositCreated"}) do + # key is naming coming from plasma contracts + # value is what we use + contracts_naming = [{"token", :currency}, {"depositor", :owner}, {"blknum", :blknum}, {"amount", :amount}] + + reduce_naming(data, contracts_naming) + end + + defp reduce_naming(data, contracts_naming) do + Enum.reduce(contracts_naming, %{}, fn + {old_name, new_name}, acc -> + value = Map.get(data, old_name) + + acc + |> Map.put_new(new_name, value) + |> Map.delete(old_name) + end) + end +end diff --git a/priv/perf/apps/load_test/lib/child_chain/deposit.ex b/priv/perf/apps/load_test/lib/child_chain/deposit.ex index 33fa810fa3..49a34eb8b4 100644 --- a/priv/perf/apps/load_test/lib/child_chain/deposit.ex +++ b/priv/perf/apps/load_test/lib/child_chain/deposit.ex @@ -21,6 +21,8 @@ defmodule LoadTest.ChildChain.Deposit do alias ExPlasma.Encoding alias ExPlasma.Transaction.Deposit alias ExPlasma.Utxo + alias LoadTest.ChildChain.Abi + alias LoadTest.ChildChain.Transaction alias LoadTest.Ethereum alias LoadTest.Ethereum.Account @@ -49,6 +51,52 @@ defmodule LoadTest.ChildChain.Deposit do Utxo.new(%{blknum: deposit_blknum, txindex: 0, oindex: 0, amount: amount}) end + def deposit_to_child_chain(to, value) do + deposit_to_child_chain(to, value, <<0::160>>) + end + + def deposit_to_child_chain(to, value, <<0::160>>) do + {:ok, receipt} = + encode_payment_transaction([], [{to, <<0::160>>, value}]) + |> deposit_transaction(value, to) + |> Ethereum.transact_sync() + + process_deposit(receipt) + end + + def deposit_to_child_chain(to, value, token_addr) do + contract_addr = Application.fetch_env!(:load_test, :erc20_vault_address) + + {:ok, _} = to |> approve_token(contract_addr, value, token_addr) |> Ethereum.transact_sync() + + {:ok, receipt} = + encode_payment_transaction([], [{to, token_addr, value}]) + |> deposit_transaction_from(to) + |> Ethereum.transact_sync() + + process_deposit(receipt) + end + + def approve_token(from, spender, amount, token, opts \\ []) do + opts = Transaction.tx_defaults() |> Keyword.put(:gas, 80_000) |> Keyword.merge(opts) + + Ethereum.contract_transact(from, token, "approve(address,uint256)", [spender, amount], opts) + end + + defp process_deposit(%{"blockNumber" => deposit_eth_height} = receipt) do + _ = wait_deposit_recognized(deposit_eth_height) + + deposit_blknum_from_receipt(receipt) + end + + defp wait_deposit_recognized(deposit_eth_height) do + post_event_block_finality = deposit_eth_height + Application.fetch_env!(:load_test, :deposit_finality_margin) + {:ok, _} = Ethereum.wait_for_root_chain_block(post_event_block_finality + 1) + # sleeping until the deposit is spendable + Process.sleep(800 * 2) + :ok + end + defp send_deposit(deposit, account, value, @eth, gas_price) do vault_address = Application.fetch_env!(:load_test, :eth_vault_address) do_deposit(vault_address, deposit, account, value, gas_price) @@ -112,4 +160,52 @@ defmodule LoadTest.ChildChain.Deposit do Ethereum.send_raw_transaction(tx, account) end + + defp encode_payment_transaction(inputs, outputs, metadata \\ <<0::256>>) do + ExRLP.encode([ + 1, + Enum.map(inputs, fn {blknum, txindex, oindex} -> + ExPlasma.Utxo.to_rlp(%ExPlasma.Utxo{blknum: blknum, txindex: txindex, oindex: oindex}) + end), + Enum.map(outputs, fn {owner, currency, amount} -> + [1, [owner, currency, amount]] + end), + 0, + metadata + ]) + end + + defp deposit_transaction(tx, value, from) do + opts = Transaction.tx_defaults() |> Keyword.put(:gas, 180_000) |> Keyword.put(:value, value) + + contract = :load_test |> Application.fetch_env!(:eth_vault_address) |> Encoding.to_binary() + + {:ok, transaction_hash} = Ethereum.contract_transact(from, contract, "deposit(bytes)", [tx], opts) + + Encoding.to_hex(transaction_hash) + end + + defp deposit_transaction_from(tx, from) do + opts = Keyword.put(Transaction.tx_defaults(), :gas, 250_000) + + contract = Application.fetch_env!(:load_test, :erc20_vault_address) + + {:ok, transaction_hash} = Ethereum.contract_transact(from, contract, "deposit(bytes)", [tx], opts) + + Encoding.to_hex(transaction_hash) + end + + defp deposit_blknum_from_receipt(%{"logs" => logs}) do + topic = + "DepositCreated(address,uint256,address,uint256)" + |> ExthCrypto.Hash.hash(ExthCrypto.Hash.kec()) + |> Encoding.to_hex() + + [%{blknum: deposit_blknum}] = + logs + |> Enum.filter(&(topic in &1["topics"])) + |> Enum.map(&Abi.decode_log/1) + + deposit_blknum + end end diff --git a/priv/perf/apps/load_test/lib/child_chain/exit.ex b/priv/perf/apps/load_test/lib/child_chain/exit.ex new file mode 100644 index 0000000000..13f6eced74 --- /dev/null +++ b/priv/perf/apps/load_test/lib/child_chain/exit.ex @@ -0,0 +1,119 @@ +defmodule LoadTest.ChildChain.Exit do + @moduledoc """ + Utility functions for exits on a child chain. + """ + + require Logger + + alias ExPlasma.Encoding + alias LoadTest.ChildChain.Transaction + alias LoadTest.Ethereum + alias LoadTest.Ethereum.Crypto + + @gas_start_exit 400_000 + @gas_challenge_exit 300_000 + @gas_add_exit_queue 800_000 + @standard_exit_bond 14_000_000_000_000_000 + @token <<0::160>> + @vault_id 1 + + def start_exit(utxo_pos, tx_bytes, proof, from) do + opts = + tx_defaults() + |> Keyword.put(:gas, @gas_start_exit) + |> Keyword.put(:value, @standard_exit_bond) + + {:ok, transaction_hash} = + Ethereum.contract_transact( + from, + contract_address_payment_exit_game(), + "startStandardExit((uint256,bytes,bytes))", + [{utxo_pos, tx_bytes, proof}], + opts + ) + + Encoding.to_hex(transaction_hash) + end + + def add_exit_queue() do + if has_exit_queue?() do + _ = Logger.info("Exit queue was already added.") + else + _ = Logger.info("Exit queue missing. Adding...") + + {:ok, [faucet | _]} = Ethereumex.HttpClient.eth_accounts() + + data = + ABI.encode( + "addExitQueue(uint256,address)", + [@vault_id, @token] + ) + + txmap = %{ + from: faucet, + to: Application.fetch_env!(:load_test, :contract_address_plasma_framework), + value: Encoding.to_hex(0), + data: Encoding.to_hex(data), + gas: Encoding.to_hex(@gas_add_exit_queue) + } + + {:ok, receipt_hash} = Ethereumex.HttpClient.eth_send_transaction(txmap) + Ethereum.transact_sync(receipt_hash) + wait_for_exit_queue(100) + receipt_hash + end + end + + defp wait_for_exit_queue(0), do: exit(1) + + defp wait_for_exit_queue(counter) do + if has_exit_queue?() do + :ok + else + Process.sleep(1_000) + wait_for_exit_queue(counter - 1) + end + end + + defp has_exit_queue?() do + data = + ABI.encode( + "hasExitQueue(uint256,address)", + [@vault_id, @token] + ) + + {:ok, receipt_enc} = + Ethereumex.HttpClient.eth_call(%{ + to: Application.fetch_env!(:load_test, :contract_address_plasma_framework), + data: Encoding.to_hex(data) + }) + + receipt_enc + |> Encoding.to_binary() + |> ABI.TypeDecoder.decode([:bool]) + |> hd() + end + + def challenge_exit(exit_id, exiting_tx, challenge_tx, input_index, challenge_tx_sig, from) do + opts = Keyword.put(tx_defaults(), :gas, @gas_challenge_exit) + sender_data = Crypto.hash(from) + + contract = contract_address_payment_exit_game() + signature = "challengeStandardExit((uint160,bytes,bytes,uint16,bytes,bytes32))" + args = [{exit_id, exiting_tx, challenge_tx, input_index, challenge_tx_sig, sender_data}] + + {:ok, transaction_hash} = Ethereum.contract_transact(from, contract, signature, args, opts) + + Encoding.to_hex(transaction_hash) + end + + def tx_defaults() do + Transaction.tx_defaults() + end + + defp contract_address_payment_exit_game() do + :load_test + |> Application.fetch_env!(:contract_address_payment_exit_game) + |> Encoding.to_binary() + end +end diff --git a/priv/perf/apps/load_test/lib/child_chain/transaction.ex b/priv/perf/apps/load_test/lib/child_chain/transaction.ex index ad40916b91..2a2a2eb1fc 100644 --- a/priv/perf/apps/load_test/lib/child_chain/transaction.ex +++ b/priv/perf/apps/load_test/lib/child_chain/transaction.ex @@ -25,6 +25,9 @@ defmodule LoadTest.ChildChain.Transaction do alias LoadTest.Connection.ChildChain, as: Connection @retry_interval 1_000 + # safe, reasonable amount, equal to the testnet block gas limit + @lots_of_gas 5_712_388 + @gas_price 1_000_000_000 @doc """ Spends a utxo. @@ -57,17 +60,8 @@ defmodule LoadTest.ChildChain.Transaction do spend_utxo(utxo, amount, fee, signer, receiver, Encoding.to_binary(currency), retries) end - defp do_spend(_input, _output, change_amount, _currency, _signer, _retries) when change_amount < 0 do - :error_insufficient_funds - end - - defp do_spend(input, output, 0, _currency, signer, retries) do - submit_tx([input], [output], [signer], retries) - end - - defp do_spend(input, output, change_amount, currency, signer, retries) do - change_output = %Utxo{owner: signer.addr, currency: currency, amount: change_amount} - submit_tx([input], [change_output, output], [signer], retries) + def tx_defaults() do + Enum.map([value: 0, gasPrice: @gas_price, gas: @lots_of_gas], fn {k, v} -> {k, Encoding.to_hex(v)} end) end @doc """ @@ -103,6 +97,113 @@ defmodule LoadTest.ChildChain.Transaction do end) end + def recover(encoded_signed_tx) do + {:ok, trx} = + encoded_signed_tx + |> Encoding.to_binary() + |> ExRLP.decode() + |> reconstruct() + + trx + end + + defp reconstruct([raw_witnesses | typed_tx_rlp_decoded_chunks]) do + with true <- is_list(raw_witnesses) || {:error, :malformed_witnesses}, + true <- Enum.all?(raw_witnesses, &valid_witness?/1) || {:error, :malformed_witnesses}, + {:ok, raw_tx} <- reconstruct_transaction(typed_tx_rlp_decoded_chunks) do + {:ok, %{raw_tx: raw_tx, sigs: raw_witnesses}} + end + end + + defp do_spend(_input, _output, change_amount, _currency, _signer, _retries) when change_amount < 0 do + :error_insufficient_funds + end + + defp do_spend(input, output, 0, _currency, signer, retries) do + submit_tx([input], [output], [signer], retries) + end + + defp do_spend(input, output, change_amount, currency, signer, retries) do + change_output = %Utxo{owner: signer.addr, currency: currency, amount: change_amount} + submit_tx([input], [change_output, output], [signer], retries) + end + + defp valid_witness?(witness) when is_binary(witness), do: byte_size(witness) == 65 + defp valid_witness?(_), do: false + + defp reconstruct_transaction([raw_type, inputs_rlp, outputs_rlp, tx_data_rlp, metadata_rlp]) + when is_binary(raw_type) do + with {:ok, 1} <- parse_uint256(raw_type), + {:ok, inputs} <- parse_inputs(inputs_rlp), + {:ok, outputs} <- parse_outputs(outputs_rlp), + {:ok, tx_data} <- parse_uint256(tx_data_rlp), + 0 <- tx_data, + {:ok, metadata} <- validate_metadata(metadata_rlp) do + {:ok, %{tx_type: 1, inputs: inputs, outputs: outputs, metadata: metadata}} + else + _ -> {:error, :unrecognized_transaction_type} + end + end + + defp reconstruct_transaction([tx_type, outputs_rlp, nonce_rlp]) do + with {:ok, 3} <- parse_uint256(tx_type), + {:ok, outputs} <- parse_outputs(outputs_rlp), + {:ok, nonce} <- reconstruct_nonce(nonce_rlp) do + {:ok, %{tx_type: 3, outputs: outputs, nonce: nonce}} + end + end + + defp reconstruct_nonce(nonce) when is_binary(nonce) and byte_size(nonce) == 32, do: {:ok, nonce} + defp reconstruct_nonce(_), do: {:error, :malformed_nonce} + + defp validate_metadata(metadata) when is_binary(metadata) and byte_size(metadata) == 32, do: {:ok, metadata} + defp validate_metadata(_), do: {:error, :malformed_metadata} + + defp parse_inputs(inputs_rlp) do + with true <- Enum.count(inputs_rlp) <= 4 || {:error, :too_many_inputs}, + # NOTE: workaround for https://github.com/omisego/ex_plasma/issues/19. + # remove, when this is blocked on `ex_plasma` end + true <- Enum.all?(inputs_rlp, &(&1 != <<0::256>>)) || {:error, :malformed_inputs}, + do: {:ok, Enum.map(inputs_rlp, &parse_input!/1)} + rescue + _ -> {:error, :malformed_inputs} + end + + defp parse_input!(encoded) do + {:ok, result} = decode_position(encoded) + + result + end + + defp decode_position(encoded) when is_number(encoded) and encoded <= 0, do: {:error, :encoded_utxo_position_too_low} + defp decode_position(encoded) when is_integer(encoded) and encoded > 0, do: do_decode_position(encoded) + defp decode_position(encoded) when is_binary(encoded) and byte_size(encoded) == 32, do: do_decode_position(encoded) + + defp do_decode_position(encoded) do + ExPlasma.Utxo.new(encoded) + end + + defp parse_outputs(outputs_rlp) do + outputs = Enum.map(outputs_rlp, &parse_output!/1) + + with true <- Enum.count(outputs) <= 4 || {:error, :too_many_outputs}, + nil <- Enum.find(outputs, &match?({:error, _}, &1)), + do: {:ok, outputs} + rescue + _ -> {:error, :malformed_outputs} + end + + defp parse_output!(rlp_data) do + {:ok, result} = ExPlasma.Utxo.new(rlp_data) + + result + end + + defp parse_uint256(<<0>> <> _binary), do: {:error, :leading_zeros_in_encoded_uint} + defp parse_uint256(binary) when byte_size(binary) <= 32, do: {:ok, :binary.decode_unsigned(binary, :big)} + defp parse_uint256(binary) when byte_size(binary) > 32, do: {:error, :encoded_uint_too_big} + defp parse_uint256(_), do: {:error, :malformed_uint256} + defp try_submit_tx(tx, 0), do: do_submit_tx(tx) defp try_submit_tx(tx, retries) do diff --git a/apps/omg_performance/lib/omg_performance/byzantine_events.ex b/priv/perf/apps/load_test/lib/common/byzantine_events.ex similarity index 61% rename from apps/omg_performance/lib/omg_performance/byzantine_events.ex rename to priv/perf/apps/load_test/lib/common/byzantine_events.ex index b557d9ba96..bb9a27f272 100644 --- a/apps/omg_performance/lib/omg_performance/byzantine_events.ex +++ b/priv/perf/apps/load_test/lib/common/byzantine_events.ex @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.ByzantineEvents do +defmodule LoadTest.Common.ByzantineEvents do @moduledoc """ OMG network child chain server byzantine event test entrypoint. Runs performance byzantine tests. @@ -22,7 +22,7 @@ defmodule OMG.Performance.ByzantineEvents do shell do: ``` - use OMG.Performance + use LoadTest.Performance Performance.init() spenders = Generators.generate_users(2) @@ -30,20 +30,18 @@ defmodule OMG.Performance.ByzantineEvents do You probably want to prefill the child chain with transactions, see `OMG.Performance.ExtendedPerftest` or just: ``` - Performance.ExtendedPerftest.start(10_000, 16, randomized: false) + LoadTest.Common.ExtendedPerftest.start(10_000, 16, randomized: false) ``` (`randomized: false` is useful to test massive honest-standard-exiting, since it will create many unspent UTXOs for each of the spenders) """ - use OMG.Utils.LoggerExt + require Logger - alias OMG.Performance.HttpRPC.WatcherClient - alias Support.WaitFor - - alias OMG.Utxo - - require Utxo + alias ExPlasma.Encoding + alias LoadTest.ChildChain.Exit + alias LoadTest.Ethereum + alias LoadTest.Ethereum.Sync @doc """ For given utxo positions shuffle them and ask the watcher for exit data @@ -69,12 +67,22 @@ defmodule OMG.Performance.ByzantineEvents do """ @spec get_many_standard_exits(list(pos_integer())) :: list(map()) def get_many_standard_exits(exit_positions) do - watcher_url = Application.fetch_env!(:omg_performance, :watcher_url) - - exit_positions - |> Enum.shuffle() - |> Enum.map(&WatcherClient.get_exit_data(&1, watcher_url)) - |> only_successes() + result = + exit_positions + |> Enum.shuffle() + |> Enum.map(fn encoded_position -> + client = LoadTest.Connection.WatcherSecurity.client() + params = %WatcherSecurityCriticalAPI.Model.UtxoPositionBodySchema1{utxo_pos: encoded_position} + response_result = WatcherSecurityCriticalAPI.Api.UTXO.utxo_get_exit_data(client, params) + + case response_result do + {:ok, response} -> {:ok, Jason.decode!(response.body)["data"]} + other -> other + end + end) + |> only_successes() + + result end @doc """ @@ -85,13 +93,16 @@ defmodule OMG.Performance.ByzantineEvents do Will send out all transactions concurrently, fail if any of them fails and block till the last gets mined. Returns the receipt of the last transaction sent out. """ - @spec start_many_exits(list(map), OMG.Crypto.address_t()) :: {:ok, map()} | {:error, any()} + @spec start_many_exits(list(map), binary()) :: {:ok, map()} | {:error, any()} def start_many_exits(exit_datas, owner_address) do map_contract_transaction(exit_datas, fn composed_exit -> - Support.RootChainHelper.start_exit( - composed_exit.utxo_pos, - composed_exit.txbytes, - composed_exit.proof, + txbytes = Encoding.to_binary(composed_exit["txbytes"]) + proof = Encoding.to_binary(composed_exit["proof"]) + + Exit.start_exit( + composed_exit["utxo_pos"], + txbytes, + proof, owner_address ) end) @@ -113,11 +124,18 @@ defmodule OMG.Performance.ByzantineEvents do """ @spec get_many_se_challenges(list(pos_integer())) :: list(map()) def get_many_se_challenges(positions) do - watcher_url = Application.fetch_env!(:omg_performance, :watcher_url) - positions |> Enum.shuffle() - |> Enum.map(&WatcherClient.get_exit_challenge(&1, watcher_url)) + |> Enum.map(fn position -> + client = LoadTest.Connection.WatcherSecurity.client() + params = %WatcherSecurityCriticalAPI.Model.UtxoPositionBodySchema{utxo_pos: position} + response_result = WatcherSecurityCriticalAPI.Api.UTXO.utxo_get_challenge_data(client, params) + + case response_result do + {:ok, response} -> {:ok, Jason.decode!(response.body)["data"]} + error -> error + end + end) |> only_successes() end @@ -129,15 +147,15 @@ defmodule OMG.Performance.ByzantineEvents do Will send out all transactions concurrently, fail if any of them fails and block till the last gets mined. Returns the receipt of the last transaction sent out. """ - @spec challenge_many_exits(list(map), OMG.Crypto.address_t()) :: {:ok, map()} | {:error, any()} + @spec challenge_many_exits(list(map), binary()) :: {:ok, map()} | {:error, any()} def challenge_many_exits(challenge_responses, challenger_address) do map_contract_transaction(challenge_responses, fn challenge -> - Support.RootChainHelper.challenge_exit( - challenge.exit_id, - challenge.exiting_tx, - challenge.txbytes, - challenge.input_index, - challenge.sig, + Exit.challenge_exit( + challenge["exit_id"], + Encoding.to_binary(challenge["exiting_tx"]), + Encoding.to_binary(challenge["txbytes"]), + challenge["input_index"], + Encoding.to_binary(challenge["sig"]), challenger_address ) end) @@ -157,13 +175,18 @@ defmodule OMG.Performance.ByzantineEvents do Options: - :take - if not nil, will limit to this many results """ - @spec get_exitable_utxos(OMG.Crypto.address_t(), keyword()) :: list(pos_integer()) + @spec get_exitable_utxos(binary(), keyword()) :: list(pos_integer()) def get_exitable_utxos(addr, opts \\ []) when is_binary(addr) do - watcher_url = Application.fetch_env!(:omg_performance, :watcher_url) - {:ok, utxos} = WatcherClient.get_exitable_utxos(addr, watcher_url) - utxo_positions = Enum.map(utxos, & &1.utxo_pos) + client = LoadTest.Connection.WatcherSecurity.client() + params = %WatcherInfoAPI.Model.AddressBodySchema1{address: Encoding.to_hex(addr)} + {:ok, utxos_response} = WatcherSecurityCriticalAPI.Api.Account.account_get_exitable_utxos(client, params) + utxos = Jason.decode!(utxos_response.body)["data"] + + utxo_positions = Enum.map(utxos, & &1["utxo_pos"]) + + result = if opts[:take], do: Enum.take(utxo_positions, opts[:take]), else: utxo_positions - if opts[:take], do: Enum.take(utxo_positions, opts[:take]), else: utxo_positions + result end @doc """ @@ -176,9 +199,11 @@ defmodule OMG.Performance.ByzantineEvents do @spec watcher_synchronize(keyword()) :: :ok def watcher_synchronize(opts \\ []) do root_chain_height = Keyword.get(opts, :root_chain_height, nil) - watcher_url = Application.fetch_env!(:omg_performance, :watcher_url) + service = Keyword.get(opts, :service, nil) + _ = Logger.info("Waiting for the watcher to synchronize") - :ok = WaitFor.ok(fn -> watcher_synchronized?(root_chain_height, watcher_url) end, :infinity) + + :ok = Sync.repeat_until_success(fn -> watcher_synchronized?(root_chain_height, service) end, 500_000) # NOTE: allowing some more time for the dust to settle on the synced Watcher # otherwise some of the freshest UTXOs to exit will appear as missing on the Watcher # related issue to remove this `sleep` and fix properly is https://github.com/omisego/elixir-omg/issues/1031 @@ -191,9 +216,10 @@ defmodule OMG.Performance.ByzantineEvents do """ @spec get_byzantine_events() :: list(map()) def get_byzantine_events() do - watcher_url = Application.fetch_env!(:omg_performance, :watcher_url) - {:ok, status_response} = WatcherClient.get_status(watcher_url) - status_response[:byzantine_events] + {:ok, status_response} = + WatcherSecurityCriticalAPI.Api.Status.status_get(LoadTest.Connection.WatcherSecurity.client()) + + Jason.decode!(status_response.body)["data"]["byzantine_events"] end @doc """ @@ -216,7 +242,7 @@ defmodule OMG.Performance.ByzantineEvents do defp map_contract_transaction(enumberable, transaction_function) do enumberable |> Enum.map(transaction_function) - |> Task.async_stream(&Support.DevHelper.transact_sync!(&1, timeout: :infinity), + |> Task.async_stream(&Ethereum.transact_sync(&1, 200_000), timeout: :infinity, max_concurrency: 10_000 ) @@ -224,30 +250,47 @@ defmodule OMG.Performance.ByzantineEvents do |> List.last() end - # This function is prepared to be called in `WaitFor.ok`. + # This function is prepared to be called in `Sync`. # It repeatedly ask for Watcher's `/status.get` until Watcher consume mined block - defp watcher_synchronized?(root_chain_height, watcher_url) do - {:ok, status} = WatcherClient.get_status(watcher_url) + defp watcher_synchronized?(root_chain_height, service) do + {:ok, status_response} = + WatcherSecurityCriticalAPI.Api.Status.status_get(LoadTest.Connection.WatcherSecurity.client()) + + status = Jason.decode!(status_response.body)["data"] with true <- watcher_synchronized_to_mined_block?(status), - true <- root_chain_synced?(root_chain_height, status) do + true <- root_chain_synced?(root_chain_height, status, service) do :ok else _ -> :repeat end end - defp root_chain_synced?(nil, _), do: true + defp root_chain_synced?(nil, _, _), do: true - defp root_chain_synced?(root_chain_height, status) do + defp root_chain_synced?(root_chain_height, status, nil) do status - |> Access.get(:services_synced_heights) + |> Map.get("services_synced_heights") + |> Enum.reject(fn height -> + service = height["service"] + # these service heights are stuck on circle ci, but they work fine locally + # I think ci machin is not powerful enough + service == "block_getter" || service == "exit_finalizer" || service == "ife_exit_finalizer" + end) |> Enum.all?(&(&1["height"] >= root_chain_height)) end + defp root_chain_synced?(root_chain_height, status, service) do + heights = Map.get(status, "services_synced_heights") + + found_root_chain_height = Enum.find(heights, fn height -> height["service"] == service end) + + found_root_chain_height && found_root_chain_height["height"] >= root_chain_height + end + defp watcher_synchronized_to_mined_block?(%{ - last_mined_child_block_number: last_mined_child_block_number, - last_validated_child_block_number: last_validated_child_block_number + "last_mined_child_block_number" => last_mined_child_block_number, + "last_validated_child_block_number" => last_validated_child_block_number }) when last_mined_child_block_number == last_validated_child_block_number and last_mined_child_block_number > 0 do @@ -255,5 +298,7 @@ defmodule OMG.Performance.ByzantineEvents do true end - defp watcher_synchronized_to_mined_block?(_), do: false + defp watcher_synchronized_to_mined_block?(_params) do + :not_synchronized + end end diff --git a/apps/omg_performance/lib/omg_performance/extended_perftest.ex b/priv/perf/apps/load_test/lib/common/extended_perftest.ex similarity index 77% rename from apps/omg_performance/lib/omg_performance/extended_perftest.ex rename to priv/perf/apps/load_test/lib/common/extended_perftest.ex index 2ed75566df..8e8b37331c 100644 --- a/apps/omg_performance/lib/omg_performance/extended_perftest.ex +++ b/priv/perf/apps/load_test/lib/common/extended_perftest.ex @@ -12,20 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.ExtendedPerftest do +defmodule LoadTest.Common.ExtendedPerftest do @moduledoc """ This performance test allows to send out many transactions to a child chain instance of choice. - See `OMG.Performance` for configuration within the `iex` shell using `Performance.init()` + See `LoadTest.Performance` for configuration within the `iex` shell using `Performance.init()` """ - use OMG.Utils.LoggerExt + alias LoadTest.ChildChain.Deposit - alias OMG.TestHelper - alias OMG.Utxo - alias Support.Integration.DepositHelper - - require Utxo + require Logger @make_deposit_timeout 600_000 @@ -39,11 +35,11 @@ defmodule OMG.Performance.ExtendedPerftest do Once you have your Ethereum node and a child chain running, from a configured `iex -S mix run --no-start` shell ``` - use OMG.Performance + use LoadTest.Performance Performance.init() spenders = Generators.generate_users(2) - Performance.ExtendedPerftest.start(100, spenders, destdir: destdir) + LoadTest.Common.ExtendedPerftest.start(100, spenders, destdir: destdir) ``` The results are going to be waiting for you in a file within `destdir` and will be logged. @@ -53,7 +49,7 @@ defmodule OMG.Performance.ExtendedPerftest do - :randomized - whether the non-change outputs of the txs sent out will be random or equal to sender (if `false`), defaults to `true` """ - @spec start(pos_integer(), list(TestHelper.entity()), keyword()) :: :ok + @spec start(pos_integer(), list(map()), keyword()) :: :ok def start(ntx_to_send, spenders, opts \\ []) do _ = Logger.info( @@ -67,20 +63,24 @@ defmodule OMG.Performance.ExtendedPerftest do utxos = create_deposits(spenders, ntx_to_send) - OMG.Performance.Runner.run(ntx_to_send, utxos, opts, false) + result = LoadTest.Common.Runner.run(ntx_to_send, utxos, opts, false) + + Process.sleep(20_000) + + result end - @spec create_deposits(list(TestHelper.entity()), pos_integer()) :: list() + @spec create_deposits(list(map()), pos_integer()) :: list() defp create_deposits(spenders, ntx_to_send) do Enum.map(make_deposits(ntx_to_send * 2, spenders), fn {:ok, owner, blknum, amount} -> - utxo_pos = Utxo.Position.encode(Utxo.position(blknum, 0, 0)) + utxo_pos = ExPlasma.Utxo.pos(%{blknum: blknum, txindex: 0, oindex: 0}) %{owner: owner, utxo_pos: utxo_pos, amount: amount} end) end defp make_deposits(value, accounts) do depositing_f = fn account -> - deposit_blknum = DepositHelper.deposit_to_child_chain(account.addr, value) + deposit_blknum = Deposit.deposit_to_child_chain(account.addr, value) {:ok, account, deposit_blknum, value} end diff --git a/apps/omg_performance/lib/omg_performance/generators.ex b/priv/perf/apps/load_test/lib/common/generators.ex similarity index 66% rename from apps/omg_performance/lib/omg_performance/generators.ex rename to priv/perf/apps/load_test/lib/common/generators.ex index f9e3d28a26..85ffa9f890 100644 --- a/apps/omg_performance/lib/omg_performance/generators.ex +++ b/priv/perf/apps/load_test/lib/common/generators.ex @@ -12,19 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.Generators do +defmodule LoadTest.Common.Generators do @moduledoc """ Provides helper functions to generate bundles of various useful entities for performance tests """ - require OMG.Utxo - alias OMG.Eth.Configuration - alias OMG.Eth.RootChain - - alias OMG.State.Transaction - alias OMG.Utxo - alias OMG.Watcher.HttpRPC.Client - alias Support.DevHelper + alias ExPlasma.Encoding + alias LoadTest.ChildChain.Transaction + alias LoadTest.Ethereum + alias LoadTest.Ethereum.Account @generate_user_timeout 600_000 @@ -35,10 +31,10 @@ defmodule OMG.Performance.Generators do - :faucet - the address to send the test ETH from, assumed to be unlocked and have the necessary funds - :initial_funds_wei - the amount of test ETH that will be granted to every generated user """ - @spec generate_users(non_neg_integer, [Keyword.t()]) :: [OMG.TestHelper.entity()] - def generate_users(size, opts \\ []) do + @spec generate_users(non_neg_integer) :: [map()] + def generate_users(size) do 1..size - |> Task.async_stream(fn _ -> generate_user(opts) end, timeout: @generate_user_timeout) + |> Task.async_stream(fn _ -> generate_user() end, timeout: @generate_user_timeout) |> Enum.map(fn {:ok, result} -> result end) end @@ -60,10 +56,10 @@ defmodule OMG.Performance.Generators do if opts[:take], do: Enum.take(utxo_positions, opts[:take]), else: utxo_positions end - @spec stream_blocks() :: [OMG.Block.t()] + @spec stream_blocks() :: [map()] defp stream_blocks() do - child_chain_url = OMG.Watcher.Configuration.child_chain_url() - interval = Configuration.child_block_interval() + child_chain_url = Application.fetch_env!(:load_test, :child_chain_url) + interval = Application.fetch_env!(:load_test, :child_block_interval) Stream.map( Stream.iterate(1, &(&1 + 1)), @@ -71,23 +67,27 @@ defmodule OMG.Performance.Generators do ) end - defp generate_user(opts) do - user = OMG.TestHelper.generate_entity() - {:ok, _user} = DevHelper.import_unlock_fund(user, opts) + defp generate_user() do + {:ok, user} = Account.new() + + {:ok, address} = Ethereum.create_account_from_secret(user.priv, "pass") + {:ok, _} = Ethereum.unlock_account(address, "pass") + {:ok, _} = Ethereum.fund_address_from_default_faucet(user, []) + user end defp get_block!(blknum, child_chain_url) do - {block_hash, _} = RootChain.blocks(blknum) + {block_hash, _} = Ethereum.block_hash(blknum) {:ok, block} = poll_get_block(block_hash, child_chain_url) block end defp to_utxo_position_list(block, opts) do - block.transactions + block["transactions"] |> Stream.with_index() |> Stream.flat_map(fn {tx, index} -> - transaction_to_output_positions(tx, block.number, index, opts) + transaction_to_output_positions(tx, block["blknum"], index, opts) end) end @@ -95,26 +95,31 @@ defmodule OMG.Performance.Generators do filtered_address = opts[:owned_by] tx - |> Transaction.Recovered.recover_from!() - |> Transaction.get_outputs() + |> Transaction.recover() + |> get_outputs() |> Enum.filter(&(is_nil(filtered_address) || &1.owner == filtered_address)) |> Enum.with_index() |> Enum.map(fn {_, oindex} -> - utxo_pos = Utxo.position(blknum, txindex, oindex) - Utxo.Position.encode(utxo_pos) + ExPlasma.Utxo.pos(%{blknum: blknum, txindex: txindex, oindex: oindex}) end) end + defp get_outputs(transaction) do + transaction.raw_tx.outputs + end + defp poll_get_block(block_hash, child_chain_url) do poll_get_block(block_hash, child_chain_url, 50) end - defp poll_get_block(block_hash, child_chain_url, 0), do: Client.get_block(block_hash, child_chain_url) - defp poll_get_block(block_hash, child_chain_url, retry) do - case Client.get_block(block_hash, child_chain_url) do - {:ok, _block} = result -> - result + client = LoadTest.Connection.ChildChain.client() + params = %ChildChainAPI.Model.GetBlockBodySchema{hash: Encoding.to_hex(block_hash)} + response = ChildChainAPI.Api.Block.block_get(client, params) + + case response do + {:ok, block_response} -> + {:ok, Jason.decode!(block_response.body)["data"]} _ -> Process.sleep(10) diff --git a/apps/omg_performance/lib/omg_performance/runner.ex b/priv/perf/apps/load_test/lib/common/runner.ex similarity index 90% rename from apps/omg_performance/lib/omg_performance/runner.ex rename to priv/perf/apps/load_test/lib/common/runner.ex index 4bfc113fb2..a9c4a65546 100644 --- a/apps/omg_performance/lib/omg_performance/runner.ex +++ b/priv/perf/apps/load_test/lib/common/runner.ex @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.Runner do +defmodule LoadTest.Common.Runner do @moduledoc """ Orchestration and running tests """ - use OMG.Utils.LoggerExt + require Logger @doc """ Kicks off the sending of the transactions, with or without profiling depending on the `profile` arg @@ -32,7 +32,7 @@ defmodule OMG.Performance.Runner do {duration, _result} = :timer.tc(fn -> # fire async transaction senders - manager = OMG.Performance.SenderManager.start_link_all_senders(ntx_to_send, utxos, opts) + manager = LoadTest.Common.SenderManager.start_link_all_senders(ntx_to_send, utxos, opts) # Wait all senders do their job, checker will stop when it happens and stops itself wait_for(manager) @@ -49,8 +49,7 @@ defmodule OMG.Performance.Runner do destfile = Path.join(opts[:destdir], "perf_result_profiling_#{:os.system_time(:seconds)}") - [callers: true, sort: :own, totals: true, details: true, dest: String.to_charlist(destfile)] - |> :fprof.analyse() + :fprof.analyse(callers: true, sort: :own, totals: true, details: true, dest: String.to_charlist(destfile)) _ = Logger.info("The :fprof output written to #{inspect(destfile)}.") diff --git a/apps/omg_performance/lib/omg_performance/sender_manager.ex b/priv/perf/apps/load_test/lib/common/sender_manager.ex similarity index 93% rename from apps/omg_performance/lib/omg_performance/sender_manager.ex rename to priv/perf/apps/load_test/lib/common/sender_manager.ex index b022fc16dd..29d2141dfa 100644 --- a/apps/omg_performance/lib/omg_performance/sender_manager.ex +++ b/priv/perf/apps/load_test/lib/common/sender_manager.ex @@ -12,17 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.SenderManager do +defmodule LoadTest.Common.SenderManager do @moduledoc """ Registry-kind module that creates and starts sender processes and waits until all are done """ use GenServer - use OMG.Utils.LoggerExt - alias OMG.Utxo - - require Utxo + require Logger def sender_stats(new_stats) do GenServer.cast(__MODULE__, {:stats, Map.put(new_stats, :timestamp, System.monotonic_time(:millisecond))}) @@ -53,14 +50,13 @@ defmodule OMG.Performance.SenderManager do utxos |> Enum.with_index(1) |> Enum.map(fn {utxo, seqnum} -> - {:ok, pid} = OMG.Performance.SenderServer.start_link({seqnum, utxo, ntx_to_send, opts}) + {:ok, pid} = LoadTest.Common.SenderServer.start_link({seqnum, utxo, ntx_to_send, opts}) {seqnum, pid} end) initial_blknums = - utxos - |> Enum.map(fn %{utxo_pos: utxo_pos} -> - Utxo.position(blknum, _txindex, _oindex) = Utxo.Position.decode!(utxo_pos) + Enum.map(utxos, fn %{utxo_pos: utxo_pos} -> + {:ok, %ExPlasma.Utxo{blknum: blknum}} = ExPlasma.Utxo.new(utxo_pos) blknum end) @@ -127,7 +123,7 @@ defmodule OMG.Performance.SenderManager do # Returns array of tuples, each tuple contains four fields: # * {blknum, total_txs_in_blk, avg_txs_in_sec, time_between_blocks_ms} defp analyze(%{events: events, start_time: start, initial_blknums: initial_blknums}) do - events_by_blknum = events |> Enum.group_by(& &1.blknum) + events_by_blknum = Enum.group_by(events, & &1.blknum) # we don't want the initial blocks that end up in the events ordered_keys = @@ -141,7 +137,7 @@ defmodule OMG.Performance.SenderManager do |> Enum.map(&collect_block/1) |> Enum.reduce({start, []}, &analyze_block/2) - block_stats |> Enum.reverse() + Enum.reverse(block_stats) end # Receives all events from Senders processes related to the same block and computes block's statistics. diff --git a/apps/omg_performance/lib/omg_performance/sender_server.ex b/priv/perf/apps/load_test/lib/common/sender_server.ex similarity index 61% rename from apps/omg_performance/lib/omg_performance/sender_server.ex rename to priv/perf/apps/load_test/lib/common/sender_server.ex index 67e2a8fbac..df8da6f8ba 100644 --- a/apps/omg_performance/lib/omg_performance/sender_server.ex +++ b/priv/perf/apps/load_test/lib/common/sender_server.ex @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.SenderServer do +defmodule LoadTest.Common.SenderServer do @moduledoc """ The SenderServer process synchronously sends requested number of transactions to the blockchain server. """ @@ -22,16 +22,13 @@ defmodule OMG.Performance.SenderServer do @fees_amount 1 use GenServer - use OMG.Utils.LoggerExt - alias OMG.DevCrypto - alias OMG.State.Transaction - alias OMG.TestHelper - alias OMG.Utxo - alias OMG.Watcher.HttpRPC.Client - require Utxo + alias LoadTest.ChildChain.Transaction + alias LoadTest.Ethereum.Account - @eth OMG.Eth.zero_address() + require Logger + + @eth <<0::160>> defmodule LastTx do @moduledoc """ @@ -61,7 +58,6 @@ defmodule OMG.Performance.SenderServer do ntx_to_send: integer, spender: map, last_tx: LastTx.t(), - child_chain_url: binary(), randomized: boolean() } @@ -109,73 +105,58 @@ defmodule OMG.Performance.SenderServer do ) do _ = Logger.info("[#{inspect(seqnum)}] Stoping...") - OMG.Performance.SenderManager.sender_stats(%{seqnum: seqnum, blknum: blknum, txindex: txindex}) + LoadTest.Common.SenderManager.sender_stats(%{seqnum: seqnum, blknum: blknum, txindex: txindex}) {:stop, :normal, state} end - def handle_info(:do, %__MODULE__{} = state) do + def handle_info(:do, state) do newstate = state - |> prepare_new_tx() - |> submit_tx(state) + |> prepare_and_submit_tx() |> update_state_with_tx_submission(state) {:noreply, newstate} end - defp prepare_new_tx(%__MODULE__{seqnum: seqnum, spender: spender, last_tx: last_tx, randomized: randomized}) do + defp prepare_and_submit_tx(state) do to_spend = 1 - new_amount = last_tx.amount - to_spend - @fees_amount - recipient = if randomized, do: TestHelper.generate_entity(), else: spender + new_amount = state.last_tx.amount - to_spend - @fees_amount + + recipient = + if state.randomized do + {:ok, user} = Account.new() + + user + else + state.spender + end _ = Logger.debug( - "[#{inspect(seqnum)}]: Sending Tx to new owner #{Base.encode64(recipient.addr)}, left: #{inspect(new_amount)}" + "[#{inspect(state.seqnum)}]: Sending Tx to new owner #{Base.encode64(recipient.addr)}, left: #{ + inspect(new_amount) + }" ) - recipient_output = [{recipient.addr, @eth, to_spend}] + recipient_output = [%ExPlasma.Utxo{owner: recipient.addr, currency: @eth, amount: to_spend}] # we aren't allowed to create zero-amount outputs, so if this is the last tx and no change is due, leave it out - change_output = if new_amount > 0, do: [{spender.addr, @eth, new_amount}], else: [] + change_output = + if new_amount > 0, do: [%ExPlasma.Utxo{owner: state.spender.addr, currency: @eth, amount: new_amount}], else: [] + + [%{blknum: blknum, txindex: txindex, amount: amount} | _] = + Transaction.submit_tx( + [%ExPlasma.Utxo{blknum: state.last_tx.blknum, txindex: state.last_tx.txindex, oindex: state.last_tx.oindex}], + change_output ++ recipient_output, + [state.spender], + 1_000 + ) - # create and return signed transaction - [{last_tx.blknum, last_tx.txindex, last_tx.oindex}] - |> Transaction.Payment.new(change_output ++ recipient_output) - |> DevCrypto.sign([spender.priv]) - end + _ = + Logger.debug( + "[#{inspect(state.seqnum)}]: Transaction submitted successfully {#{inspect(blknum)}, #{inspect(txindex)}}" + ) - # Submits new transaction to the blockchain server. - @spec submit_tx(Transaction.Signed.t(), __MODULE__.state()) :: - {:ok, blknum :: pos_integer, txindex :: pos_integer, new_amount :: pos_integer} - | {:error, any()} - | :retry - defp submit_tx(tx, %__MODULE__{seqnum: seqnum, child_chain_url: child_chain_url}) do - result = - tx - |> Transaction.Signed.encode() - |> submit_tx_rpc(child_chain_url) - - case result do - {:error, {:client_error, %{"code" => "submit:utxo_not_found"}}} -> - _ = Logger.info("[#{inspect(seqnum)}]: Transaction submission will be retried, utxo not found yet.") - :retry - - {:error, {:client_error, %{"code" => "submit:too_many_transactions_in_block"}}} -> - _ = Logger.info("[#{inspect(seqnum)}]: Transaction submission will be retried, block is full.") - :retry - - {:error, reason} -> - _ = Logger.info("[#{inspect(seqnum)}]: Transaction submission has failed, reason: #{inspect(reason)}") - {:error, reason} - - {:ok, %{blknum: blknum, txindex: txindex}} -> - _ = - Logger.debug( - "[#{inspect(seqnum)}]: Transaction submitted successfully {#{inspect(blknum)}, #{inspect(txindex)}}" - ) - - [%{amount: amount} | _] = Transaction.get_outputs(tx) - {:ok, blknum, txindex, amount} - end + {:ok, blknum, txindex, amount} end # Handles result of successful Tx submission or retry request into new state and sends :do message @@ -183,23 +164,20 @@ defmodule OMG.Performance.SenderServer do tx_submit_result :: {:ok, map} | :retry | {:error, any}, state :: __MODULE__.state() ) :: __MODULE__.state() - defp update_state_with_tx_submission( - tx_submit_result, - %__MODULE__{seqnum: seqnum, last_tx: last_tx} = state - ) do + defp update_state_with_tx_submission(tx_submit_result, state) do case tx_submit_result do {:ok, newblknum, newtxindex, newvalue} -> send(self(), :do) - if newblknum > last_tx.blknum, + if newblknum > state.last_tx.blknum, do: - OMG.Performance.SenderManager.sender_stats(%{ - seqnum: seqnum, - blknum: last_tx.blknum, - txindex: last_tx.txindex + LoadTest.Common.SenderManager.sender_stats(%{ + seqnum: state.seqnum, + blknum: state.last_tx.blknum, + txindex: state.last_tx.txindex }) - state |> next_state(newblknum, newtxindex, newvalue) + next_state(state, newblknum, newtxindex, newvalue) :retry -> Process.send_after(self(), :do, @tx_retry_waiting_time_ms) @@ -207,16 +185,10 @@ defmodule OMG.Performance.SenderServer do end end - # Submits Tx to the child chain server via http (Http-RPC) and translates successful result to atom-keyed map. - @spec submit_tx_rpc(binary, binary()) :: {:ok, map} | {:error, any} - defp submit_tx_rpc(encoded_tx, child_chain_url) do - Client.submit(encoded_tx, child_chain_url) - end - # Generates module's initial state @spec init_state(pos_integer(), map(), pos_integer(), keyword()) :: __MODULE__.state() defp init_state(seqnum, %{owner: spender, utxo_pos: utxo_pos, amount: amount}, ntx_to_send, opts) do - Utxo.position(blknum, txindex, oindex) = Utxo.Position.decode!(utxo_pos) + {:ok, %ExPlasma.Utxo{blknum: blknum, txindex: txindex, oindex: oindex}} = ExPlasma.Utxo.new(utxo_pos) %__MODULE__{ seqnum: seqnum, @@ -229,7 +201,6 @@ defmodule OMG.Performance.SenderServer do oindex: oindex, amount: amount }, - child_chain_url: Application.fetch_env!(:omg_watcher, :child_chain_url), randomized: Keyword.get(opts, :randomized) } end @@ -237,10 +208,10 @@ defmodule OMG.Performance.SenderServer do # Generates next module's state @spec next_state(state :: __MODULE__.state(), blknum :: pos_integer, txindex :: pos_integer, amount :: pos_integer) :: __MODULE__.state() - defp next_state(%__MODULE__{ntx_to_send: ntx_to_send} = state, blknum, txindex, amount) do + defp next_state(state, blknum, txindex, amount) do %__MODULE__{ state - | ntx_to_send: ntx_to_send - 1, + | ntx_to_send: state.ntx_to_send - 1, last_tx: %LastTx{ state.last_tx | blknum: blknum, diff --git a/priv/perf/apps/load_test/lib/ethereum/ethereum.ex b/priv/perf/apps/load_test/lib/ethereum/ethereum.ex index d9ebb64ff1..12660ade40 100644 --- a/priv/perf/apps/load_test/lib/ethereum/ethereum.ex +++ b/priv/perf/apps/load_test/lib/ethereum/ethereum.ex @@ -19,6 +19,7 @@ defmodule LoadTest.Ethereum do require Logger alias ExPlasma.Encoding + alias LoadTest.ChildChain.Abi alias LoadTest.Ethereum.NonceTracker alias LoadTest.Ethereum.Sync alias LoadTest.Ethereum.Transaction @@ -29,6 +30,26 @@ defmodule LoadTest.Ethereum do @type hash_t() :: <<_::256>> + @doc """ + Send transaction to be singed by a key managed by Ethereum node, geth or parity. + For geth, account must be unlocked externally. + If using parity, account passphrase must be provided directly or via config. + """ + @spec contract_transact(<<_::160>>, <<_::160>>, binary, [any]) :: {:ok, <<_::256>>} | {:error, any} + def contract_transact(from, to, signature, args, opts \\ []) do + data = encode_tx_data(signature, args) + + txmap = + %{from: Encoding.to_hex(from), to: Encoding.to_hex(to), data: data} + |> Map.merge(Map.new(opts)) + |> encode_all_integer_opts() + + case Ethereumex.HttpClient.eth_send_transaction(txmap) do + {:ok, receipt_enc} -> {:ok, Encoding.to_binary(receipt_enc)} + other -> other + end + end + @doc """ Waits until transaction is mined Returns transaction receipt updated with Ethereum block number in which the transaction was mined @@ -39,6 +60,14 @@ defmodule LoadTest.Ethereum do {:ok, Map.update!(receipt, "blockNumber", &Encoding.to_int(&1))} end + def create_account_from_secret(secret, passphrase) do + Ethereumex.HttpClient.request("personal_importRawKey", [Base.encode16(secret), passphrase], []) + end + + def unlock_account(addr, passphrase) do + Ethereumex.HttpClient.request("personal_unlockAccount", [addr, passphrase, 0], []) + end + def fund_address_from_default_faucet(account, opts) do {:ok, [default_faucet | _]} = Ethereumex.HttpClient.eth_accounts() defaults = [faucet: default_faucet, initial_funds_wei: @eth_amount_to_fund] @@ -55,6 +84,15 @@ defmodule LoadTest.Ethereum do transact_sync(tx_fund) end + def block_hash(mined_num) do + contract_address = Application.fetch_env!(:load_test, :contract_address_plasma_framework) + + %{"block_hash" => block_hash, "block_timestamp" => block_timestamp} = + get_external_data(contract_address, "blocks(uint256)", [mined_num]) + + {block_hash, block_timestamp} + end + def send_raw_transaction(txmap, sender) do nonce = NonceTracker.get_next_nonce(sender.addr) @@ -78,6 +116,31 @@ defmodule LoadTest.Ethereum do Encoding.to_int(nonce) end + def wait_for_root_chain_block(awaited_eth_height, timeout \\ 600_000) do + f = fn -> + {:ok, eth_height} = + case Ethereumex.HttpClient.eth_block_number() do + {:ok, height_hex} -> + {:ok, Encoding.to_int(height_hex)} + + other -> + other + end + + if eth_height < awaited_eth_height, do: :repeat, else: {:ok, eth_height} + end + + Sync.repeat_until_success(f, timeout) + end + + defp get_external_data(address, signature, params) do + data = signature |> ABI.encode(params) |> Encoding.to_hex() + + {:ok, data} = Ethereumex.HttpClient.eth_call(%{to: address, data: data}) + + Abi.decode_function(data, signature) + end + defp send_transaction(txmap), do: Ethereumex.HttpClient.eth_send_transaction(txmap) defp eth_receipt(txhash, timeout) do @@ -92,4 +155,16 @@ defmodule LoadTest.Ethereum do Sync.repeat_until_success(f, timeout) end + + defp encode_tx_data(signature, args) do + signature + |> ABI.encode(args) + |> Encoding.to_hex() + end + + defp encode_all_integer_opts(opts) do + opts + |> Enum.filter(fn {_k, v} -> is_integer(v) end) + |> Enum.into(opts, fn {k, v} -> {k, Encoding.to_hex(v)} end) + end end diff --git a/priv/perf/apps/load_test/lib/performance.ex b/priv/perf/apps/load_test/lib/performance.ex new file mode 100644 index 0000000000..a7fba64de1 --- /dev/null +++ b/priv/perf/apps/load_test/lib/performance.ex @@ -0,0 +1,54 @@ +# 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.Performance do + @moduledoc """ + OMG network performance tests. Provides general setup and utilities to do the perf tests. + """ + + defmacro __using__(_opt) do + quote do + alias LoadTest.Common.ByzantineEvents + alias LoadTest.Common.ExtendedPerftest + alias LoadTest.Common.Generators + + alias LoadTest.Performance + + import Performance, only: [timeit: 1] + require Performance + require Logger + + :ok + end + end + + @doc """ + Utility macro which causes the expression given to be timed, the timing logged (`info`) and the original result of the + call to be returned + + ## Examples + + iex> use LoadTest.Performance + iex> timeit 1+2 + 3 + """ + defmacro timeit(call) do + quote do + {duration, result} = :timer.tc(fn -> unquote(call) end) + duration_s = duration / 1_000_000 + _ = Logger.info("Lasted #{inspect(duration_s)} seconds") + result + end + end +end diff --git a/priv/perf/apps/load_test/mix.exs b/priv/perf/apps/load_test/mix.exs index 3671de75ae..a7affc9511 100644 --- a/priv/perf/apps/load_test/mix.exs +++ b/priv/perf/apps/load_test/mix.exs @@ -30,10 +30,12 @@ defmodule LoadTest.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:briefly, "~> 0.3"}, {:chaperon, "~> 0.3.1"}, {:tesla, "~> 1.3.0"}, {:httpoison, "~> 1.6.2", override: true}, {:ex_plasma, git: "https://github.com/omisego/ex_plasma.git", override: true}, + {:telemetry, "~> 0.4.1"}, # Better adapter for tesla {:hackney, "~> 1.15.2"}, diff --git a/apps/omg_performance/test/omg_performance/byzantine_event_test.exs b/priv/perf/apps/load_test/test/load_tests/common/byzantine_events_test.exs similarity index 74% rename from apps/omg_performance/test/omg_performance/byzantine_event_test.exs rename to priv/perf/apps/load_test/test/load_tests/common/byzantine_events_test.exs index 5f1c9bf5ff..6009f26a83 100755 --- a/apps/omg_performance/test/omg_performance/byzantine_event_test.exs +++ b/priv/perf/apps/load_test/test/load_tests/common/byzantine_events_test.exs @@ -12,17 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.ByzantineEventsTest do +defmodule LoadTest.Common.ByzantineEventsTest do @moduledoc """ Simple smoke testing of the performance test """ - - use ExUnitFixtures use ExUnit.Case, async: false - use OMG.ChildChain.Integration.Fixtures - use OMG.Watcher.Fixtures + use LoadTest.Performance - use OMG.Performance + alias LoadTest.ChildChain.Exit @moduletag :integration @moduletag timeout: 180_000 @@ -31,29 +28,23 @@ defmodule OMG.Performance.ByzantineEventsTest do @take 3 setup_all do + _ = Exit.add_exit_queue() + # preventing :erlang.binary_to_existing_atom("last_mined_child_block_timestamp", :utf8) exception _ = String.to_atom("last_mined_child_block_timestamp") _ = String.to_atom("last_seen_eth_block_number") _ = String.to_atom("last_seen_eth_block_timestamp") _ = String.to_atom("last_validated_child_block_timestamp") - :ok - end - # NOTE: still bound to fixtures :(, because of the child chain setup, but this will go eventually, so leaving as is - deffixture perf_test(contract) do - _ = contract - :ok = Performance.init() {:ok, destdir} = Briefly.create(directory: true, prefix: "temp_results") {:ok, %{destdir: destdir}} end - @tag fixtures: [:perf_test, :mix_based_child_chain, :mix_based_watcher] - test "can provide timing of response when asking for exit data", %{perf_test: {:ok, %{destdir: destdir}}} do + test "can provide timing of response when asking for exit data", %{destdir: destdir} do spenders = Generators.generate_users(2) alice = Enum.at(spenders, 0) - :ok = - Performance.ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: false, destdir: destdir) + :ok = ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: false, destdir: destdir) :ok = ByzantineEvents.watcher_synchronize() @@ -61,13 +52,13 @@ defmodule OMG.Performance.ByzantineEventsTest do ByzantineEvents.get_many_standard_exits(utxos) end - @tag fixtures: [:perf_test, :mix_based_child_chain, :mix_based_watcher] - test "can provide timing of status.get under many valid SEs", %{perf_test: {:ok, %{destdir: destdir}}} do + # since we're using the same geth node for all tests, this test is not compatible with the test on line 76 + @tag :skip + test "can provide timing of status.get under many valid SEs", %{destdir: destdir} do spenders = Generators.generate_users(2) alice = Enum.at(spenders, 0) - :ok = - Performance.ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: false, destdir: destdir) + :ok = ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: false, destdir: destdir) :ok = ByzantineEvents.watcher_synchronize() @@ -81,13 +72,11 @@ defmodule OMG.Performance.ByzantineEventsTest do assert ByzantineEvents.get_byzantine_events("invalid_exit") == [] end - @tag fixtures: [:perf_test, :mix_based_child_chain, :mix_based_watcher] - test "can provide timing of status.get under many valid/invalid SEs", %{perf_test: {:ok, %{destdir: destdir}}} do + test "can provide timing of status.get under many valid/invalid SEs", %{destdir: destdir} do spenders = Generators.generate_users(2) alice = Enum.at(spenders, 0) - :ok = - Performance.ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: true, destdir: destdir) + :ok = ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: true, destdir: destdir) :ok = ByzantineEvents.watcher_synchronize() @@ -101,13 +90,11 @@ defmodule OMG.Performance.ByzantineEventsTest do assert Enum.count(ByzantineEvents.get_byzantine_events("invalid_exit")) >= @take end - @tag fixtures: [:perf_test, :mix_based_child_chain, :mix_based_watcher] - test "can provide timing of challenging", %{perf_test: {:ok, %{destdir: destdir}}} do + test "can provide timing of challenging", %{destdir: destdir} do spenders = Generators.generate_users(2) alice = Enum.at(spenders, 0) - :ok = - Performance.ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: true, destdir: destdir) + :ok = ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: true, destdir: destdir) :ok = ByzantineEvents.watcher_synchronize() @@ -127,13 +114,11 @@ defmodule OMG.Performance.ByzantineEventsTest do assert Enum.count(challenge_responses) >= @take end - @tag fixtures: [:perf_test, :mix_based_child_chain, :mix_based_watcher] - test "can provide timing of status.get under many challenged SEs", %{perf_test: {:ok, %{destdir: destdir}}} do + test "can provide timing of status.get under many challenged SEs", %{destdir: destdir} do spenders = Generators.generate_users(2) alice = Enum.at(spenders, 0) - :ok = - Performance.ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: true, destdir: destdir) + :ok = ExtendedPerftest.start(@number_of_transactions_to_send, spenders, randomized: true, destdir: destdir) :ok = ByzantineEvents.watcher_synchronize() diff --git a/apps/omg_performance/test/omg_performance/extended_perftest_test.exs b/priv/perf/apps/load_test/test/load_tests/common/extended_perftest_test.exs similarity index 71% rename from apps/omg_performance/test/omg_performance/extended_perftest_test.exs rename to priv/perf/apps/load_test/test/load_tests/common/extended_perftest_test.exs index 7fb7148c26..70c56d843e 100644 --- a/apps/omg_performance/test/omg_performance/extended_perftest_test.exs +++ b/priv/perf/apps/load_test/test/load_tests/common/extended_perftest_test.exs @@ -12,36 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.Performance.ExtendedPerftestTest do +defmodule LoadTest.Common.ExtendedPerftestTest do @moduledoc """ Simple smoke testing of the performance test """ - use ExUnitFixtures use ExUnit.Case, async: false - use OMG.ChildChain.Integration.Fixtures - - use OMG.Performance + use LoadTest.Performance @moduletag :integration @moduletag :common - # NOTE: still bound to fixtures :(, because of the child chain setup, but this will go eventually, so leaving as is - deffixture perf_test(contract) do - _ = contract - :ok = Performance.init() - {:ok, destdir} = Briefly.create(directory: true, prefix: "temp_results") - {:ok, %{destdir: destdir}} - end - - @tag fixtures: [:perf_test, :in_beam_child_chain] @tag timeout: 120_000 - test "Smoke test - run start_extended_perf and see if it doesn't crash", %{perf_test: {:ok, %{destdir: destdir}}} do + test "Smoke test - run start_extended_perf and see if it doesn't crash" do + {:ok, destdir} = Briefly.create(directory: true, prefix: "temp_results") # 3000 txs sending 1 each, plus 1 for fees ntxs = 3000 senders = Generators.generate_users(2) - assert :ok = Performance.ExtendedPerftest.start(ntxs, senders, destdir: destdir) + assert :ok = ExtendedPerftest.start(ntxs, senders, destdir: destdir) assert ["perf_result" <> _ = perf_result] = File.ls!(destdir) smoke_test_statistics(Path.join(destdir, perf_result), ntxs * length(senders)) diff --git a/apps/omg_performance/test/omg_performance/performance_test.exs b/priv/perf/apps/load_test/test/load_tests/common/performance_test.exs similarity index 90% rename from apps/omg_performance/test/omg_performance/performance_test.exs rename to priv/perf/apps/load_test/test/load_tests/common/performance_test.exs index 34d5076e11..f5045a11c6 100644 --- a/apps/omg_performance/test/omg_performance/performance_test.exs +++ b/priv/perf/apps/load_test/test/load_tests/common/performance_test.exs @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule OMG.PerformanceTest do +defmodule LoadTest.PerformanceTest do @moduledoc false use ExUnit.Case, async: false - doctest OMG.Performance + doctest LoadTest.Performance end diff --git a/priv/perf/apps/load_test/test/test_helper.exs b/priv/perf/apps/load_test/test/test_helper.exs index 869559e709..d13e5fc245 100644 --- a/priv/perf/apps/load_test/test/test_helper.exs +++ b/priv/perf/apps/load_test/test/test_helper.exs @@ -1 +1 @@ -ExUnit.start() +ExUnit.start(exclude: [:skip]) diff --git a/priv/perf/config/config.exs b/priv/perf/config/config.exs index 73aecf7810..d0941409c5 100644 --- a/priv/perf/config/config.exs +++ b/priv/perf/config/config.exs @@ -19,6 +19,9 @@ config :load_test, watcher_info_url: System.get_env("WATCHER_INFO_URL"), faucet_private_key: System.get_env("LOAD_TEST_FAUCET_PRIVATE_KEY"), 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, + 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)), diff --git a/priv/perf/mix.lock b/priv/perf/mix.lock index e4b82582b1..a84a4c7b7b 100644 --- a/priv/perf/mix.lock +++ b/priv/perf/mix.lock @@ -1,6 +1,7 @@ %{ "basic_auth": {:hex, :basic_auth, "2.2.4", "d8c748237870dd1df3bc5c0f1ab4f1fad6270c75472d7e62b19302ec59e92a79", [:mix], [{:plug, "~> 0.14 or ~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "a595b5f2a07e94cbde7be3dfeba24573e18655c88e74c8eb118364f856642e62"}, "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm", "ee1e9ebcab703a4e24db554957fbb540642fe9327eb9e295cb3f07dd7c11ddb2"}, + "briefly": {:hex, :briefly, "0.3.0", "16e6b76d2070ebc9cbd025fa85cf5dbaf52368c4bd896fb482b5a6b95a540c2f", [:mix], [], "hexpm", "c6ebf8fc3dcd4950dd10c03e953fb4f553a8bcf0ff4c8c40d71542434cd7e046"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "chaperon": {:hex, :chaperon, "0.3.1", "505a6f4c3ee396d6b33750d24ba46f437b3714700c2c9434199da38ca1af174f", [:mix], [{:basic_auth, "~> 2.2", [hex: :basic_auth, repo: "hexpm", optional: false]}, {:cowboy, "~> 2.6", [hex: :cowboy, repo: "hexpm", optional: false]}, {:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:e_q, "~> 1.0.0", [hex: :e_q, repo: "hexpm", optional: false]}, {:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: false]}, {:histogrex, "~> 0.0.5", [hex: :histogrex, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.5", [hex: :httpoison, repo: "hexpm", optional: false]}, {:instream, "~> 0.21.0", [hex: :instream, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}, {:websockex, "~> 0.4.1", [hex: :websockex, repo: "hexpm", optional: false]}], "hexpm", "dbcf477fe177b9ea91a5f838d5df99cbed1ea4e634f5070a718bbea1767a82ce"},