diff --git a/apps/omg_eth/test/support/dev_geth.ex b/apps/omg_eth/test/support/dev_geth.ex index 2e87139520..0a8cb70895 100644 --- a/apps/omg_eth/test/support/dev_geth.ex +++ b/apps/omg_eth/test/support/dev_geth.ex @@ -103,7 +103,7 @@ defmodule OMG.Eth.DevGeth do waiting_task |> Task.async() - |> Task.await(15_000) + |> Task.await(30_000) pid end diff --git a/docker-compose.feefeed.yml b/docker-compose.feefeed.yml index 562a56263c..7fc624e13d 100644 --- a/docker-compose.feefeed.yml +++ b/docker-compose.feefeed.yml @@ -16,6 +16,7 @@ services: - GITHUB_TOKEN="" - GITHUB_ORGANISATION=omgnetwork - GITHUB_REPO=fee-rules-public + - SENTRY_DSN="" - GITHUB_BRANCH=master - RULES_FETCH_INTERVAL=20 - RATES_FETCH_INTERVAL=20 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..dc9b339b3f 100644 --- a/priv/perf/apps/load_test/lib/child_chain/deposit.ex +++ b/priv/perf/apps/load_test/lib/child_chain/deposit.ex @@ -16,6 +16,7 @@ defmodule LoadTest.ChildChain.Deposit do @moduledoc """ Utility functions for deposits on a child chain """ + require Logger alias ExPlasma.Encoding @@ -23,30 +24,40 @@ defmodule LoadTest.ChildChain.Deposit do alias ExPlasma.Utxo alias LoadTest.Ethereum alias LoadTest.Ethereum.Account + alias LoadTest.Service.Sync @eth <<0::160>> - @poll_interval 5_000 + @doc """ Deposits funds into the childchain. If currency is ETH, funds will be deposited into the EthVault. If currency is ERC20, 'approve()' will be called before depositing funds into the Erc20Vault. - Returns the utxo created by the deposit. + This function accepts three required parameters: + 1. depositor account + 2. the amount to be deposited + 3. currency + 4. the number of verifications + 5. gas price of the transaction + 6. return - it can be :utxo or :txhash + + Returns the utxo created by the deposit or the hash of the the deposit transaction. """ - @spec deposit_from( - LoadTest.Ethereum.Account.t(), - pos_integer(), - LoadTest.Ethereum.Account.t(), - pos_integer(), - pos_integer() - ) :: Utxo.t() - def deposit_from(%Account{} = depositor, amount, currency, deposit_finality_margin, gas_price) do + @spec deposit_from(Account.t(), pos_integer(), Account.t(), non_neg_integer(), non_neg_integer, atom()) :: + Utxo.t() | binary() + def deposit_from(depositor, amount, currency, deposit_finality_margin, gas_price, return) do deposit_utxo = %Utxo{amount: amount, owner: depositor.addr, currency: currency} + {:ok, deposit} = Deposit.new(deposit_utxo) - {:ok, {deposit_blknum, eth_blknum}} = send_deposit(deposit, depositor, amount, currency, gas_price) + {:ok, {deposit_blknum, eth_blknum, eth_txhash}} = send_deposit(deposit, depositor, amount, currency, gas_price) + :ok = wait_deposit_finality(eth_blknum, deposit_finality_margin) - Utxo.new(%{blknum: deposit_blknum, txindex: 0, oindex: 0, amount: amount}) + + case return do + :utxo -> Utxo.new(%{blknum: deposit_blknum, txindex: 0, oindex: 0, amount: amount}) + _ -> eth_txhash + end end defp send_deposit(deposit, account, value, @eth, gas_price) do @@ -84,20 +95,20 @@ defmodule LoadTest.ChildChain.Deposit do %{"topics" => [_topic, _addr, blknum | _]} = Enum.find(logs, fn %{"address" => address} -> address == vault_address end) - {:ok, {Encoding.to_int(blknum), eth_blknum}} + {:ok, {Encoding.to_int(blknum), eth_blknum, tx_hash}} end defp wait_deposit_finality(deposit_eth_blknum, finality_margin) do - {:ok, current_blknum} = Ethereumex.HttpClient.eth_block_number() - current_blknum = Encoding.to_int(current_blknum) - - if current_blknum >= deposit_eth_blknum + finality_margin do - :ok - else - _ = Logger.info("Waiting for deposit finality") - Process.sleep(@poll_interval) - wait_deposit_finality(deposit_eth_blknum, finality_margin) + func = fn -> + {:ok, current_blknum} = Ethereumex.HttpClient.eth_block_number() + current_blknum = Encoding.to_int(current_blknum) + + if current_blknum >= deposit_eth_blknum + finality_margin do + :ok + end end + + Sync.repeat_until_success(func, :infinity, "Waiting for deposit finality") end defp approve(contract, vault_address, account, value, gas_price) do diff --git a/priv/perf/apps/load_test/lib/child_chain/exit.ex b/priv/perf/apps/load_test/lib/child_chain/exit.ex index b6eafe81c4..690a711e24 100644 --- a/priv/perf/apps/load_test/lib/child_chain/exit.ex +++ b/priv/perf/apps/load_test/lib/child_chain/exit.ex @@ -25,12 +25,12 @@ defmodule LoadTest.ChildChain.Exit do alias LoadTest.Ethereum alias LoadTest.Ethereum.Account alias LoadTest.Ethereum.Crypto + alias LoadTest.Service.Sync @gas_start_exit 500_000 @gas_challenge_exit 300_000 @gas_add_exit_queue 800_000 @standard_exit_bond 14_000_000_000_000_000 - @poll_interval 1_000 @doc """ Returns the exit data of a utxo. @@ -63,21 +63,18 @@ defmodule LoadTest.ChildChain.Exit do Retries until the exit data of a utxo is found. """ @spec wait_for_exit_data(Utxo.t(), pos_integer()) :: any() - def wait_for_exit_data(utxo_pos, counter \\ 10) - def wait_for_exit_data(_, 0), do: :error + def wait_for_exit_data(utxo_pos, timeout \\ 100_000) do + func = fn -> + data = get_exit_data(utxo_pos) - def wait_for_exit_data(utxo_pos, counter) do - data = get_exit_data(utxo_pos) + if not is_nil(data.proof) do + {:ok, data} + end + end - case data.proof do - nil -> - _ = Logger.info("Waiting for exit data") - Process.sleep(@poll_interval) - wait_for_exit_data(utxo_pos, counter - 1) + {:ok, result} = Sync.repeat_until_success(func, timeout, "waiting for exit data") - _ -> - data - end + result end @doc """ @@ -130,20 +127,19 @@ defmodule LoadTest.ChildChain.Exit do {:ok, receipt_hash} = Ethereum.send_raw_transaction(tx, from) Ethereum.transact_sync(receipt_hash) - wait_for_exit_queue(vault_id, token, 100) + wait_for_exit_queue(vault_id, token) receipt_hash end end - defp wait_for_exit_queue(_vault_id, _token, 0), do: exit(1) - - defp wait_for_exit_queue(vault_id, token, counter) do - if has_exit_queue?(vault_id, token) do - :ok - else - Process.sleep(1_000) - wait_for_exit_queue(vault_id, token, counter - 1) + defp wait_for_exit_queue(vault_id, token, timeout \\ 100_000) do + func = fn -> + if has_exit_queue?(vault_id, token) do + :ok + end end + + Sync.repeat_until_success(func, timeout, "waiting for exit queue") end defp has_exit_queue?(vault_id, token) do 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 2a2a2eb1fc..06e1c80752 100644 --- a/priv/perf/apps/load_test/lib/child_chain/transaction.ex +++ b/priv/perf/apps/load_test/lib/child_chain/transaction.ex @@ -23,8 +23,8 @@ defmodule LoadTest.ChildChain.Transaction do alias ExPlasma.Transaction alias ExPlasma.Utxo alias LoadTest.Connection.ChildChain, as: Connection + alias LoadTest.Service.Sync - @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 @@ -48,16 +48,16 @@ defmodule LoadTest.ChildChain.Transaction do Utxo.address_binary(), pos_integer() ) :: list(Utxo.t()) - def spend_utxo(utxo, amount, fee, signer, receiver, currency, retries \\ 0) + def spend_utxo(utxo, amount, fee, signer, receiver, currency, retries \\ 120_000) - def spend_utxo(utxo, amount, fee, signer, receiver, currency, retries) when byte_size(currency) == 20 do + def spend_utxo(utxo, amount, fee, signer, receiver, currency, timeout) when byte_size(currency) == 20 do change_amount = utxo.amount - amount - fee receiver_output = %Utxo{owner: receiver.addr, currency: currency, amount: amount} - do_spend(utxo, receiver_output, change_amount, currency, signer, retries) + do_spend(utxo, receiver_output, change_amount, currency, signer, timeout) end - def spend_utxo(utxo, amount, fee, signer, receiver, currency, retries) do - spend_utxo(utxo, amount, fee, signer, receiver, Encoding.to_binary(currency), retries) + def spend_utxo(utxo, amount, fee, signer, receiver, currency, timeout) do + spend_utxo(utxo, amount, fee, signer, receiver, Encoding.to_binary(currency), timeout) end def tx_defaults() do @@ -77,7 +77,7 @@ defmodule LoadTest.ChildChain.Transaction do list(LoadTest.Ethereum.Account.t()), pos_integer() ) :: list(Utxo.t()) - def submit_tx(inputs, outputs, signers, retries \\ 0) do + def submit_tx(inputs, outputs, signers, retries \\ 120_000) do {:ok, tx} = Transaction.Payment.new(%{inputs: inputs, outputs: outputs}) keys = @@ -204,17 +204,11 @@ defmodule LoadTest.ChildChain.Transaction do 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, timeout) do + {:ok, {blknum, txindex}} = + Sync.repeat_until_success(fn -> do_submit_tx(tx) end, timeout, "Failed to submit transaction") - defp try_submit_tx(tx, retries) do - case do_submit_tx(tx) do - {:error, "submit:utxo_not_found"} -> - Process.sleep(@retry_interval) - try_submit_tx(tx, retries - 1) - - result -> - result - end + {:ok, blknum, txindex} end defp do_submit_tx(tx) do @@ -230,7 +224,7 @@ defmodule LoadTest.ChildChain.Transaction do |> case do %{"blknum" => blknum, "txindex" => txindex} -> _ = Logger.debug("[Transaction submitted successfully {#{inspect(blknum)}, #{inspect(txindex)}}") - {:ok, blknum, txindex} + {:ok, {blknum, txindex}} %{"code" => reason} -> _ = diff --git a/priv/perf/apps/load_test/lib/child_chain/utxos.ex b/priv/perf/apps/load_test/lib/child_chain/utxos.ex index 79d6abab75..a1da831d7d 100644 --- a/priv/perf/apps/load_test/lib/child_chain/utxos.ex +++ b/priv/perf/apps/load_test/lib/child_chain/utxos.ex @@ -21,8 +21,7 @@ defmodule LoadTest.ChildChain.Utxos do alias ExPlasma.Encoding alias ExPlasma.Utxo alias LoadTest.ChildChain.Transaction - - @poll_interval 2_000 + alias LoadTest.Service.Sync @doc """ Returns an addresses utxos. @@ -107,18 +106,19 @@ defmodule LoadTest.ChildChain.Utxos do Retries until the utxo is found. """ @spec wait_for_utxo(Utxo.address_binary(), Utxo.t(), pos_integer()) :: :ok - def wait_for_utxo(address, utxo, counter \\ 100) - def wait_for_utxo(_address, _utxo, 0), do: :error + def wait_for_utxo(address, utxo, timeout \\ 100_000) do + func = fn -> + find_utxo(address, utxo) + end + + Sync.repeat_until_success(func, timeout, "waiting for utxo") + end - def wait_for_utxo(address, utxo, counter) do + defp find_utxo(address, utxo) do utxos = get_utxos(address) if Enum.find(utxos, fn x -> Utxo.pos(x) == Utxo.pos(utxo) end) do :ok - else - _ = Logger.info("Waiting for utxo") - Process.sleep(@poll_interval) - wait_for_utxo(address, utxo, counter - 1) end end end diff --git a/priv/perf/apps/load_test/lib/child_chain/watcher_sync.ex b/priv/perf/apps/load_test/lib/child_chain/watcher_sync.ex index f90b04102a..d0978456f7 100644 --- a/priv/perf/apps/load_test/lib/child_chain/watcher_sync.ex +++ b/priv/perf/apps/load_test/lib/child_chain/watcher_sync.ex @@ -19,7 +19,7 @@ defmodule LoadTest.ChildChain.WatcherSync do require Logger - alias LoadTest.Ethereum.Sync + alias LoadTest.Service.Sync @doc """ Blocks the caller until the watcher configured reports to be fully synced up (both child chain blocks and eth events) @@ -35,7 +35,13 @@ defmodule LoadTest.ChildChain.WatcherSync do _ = Logger.info("Waiting for the watcher to synchronize") - :ok = Sync.repeat_until_success(fn -> watcher_synchronized?(root_chain_height, service) end, 500_000) + :ok = + Sync.repeat_until_success( + fn -> watcher_synchronized?(root_chain_height, service) end, + 500_000, + "Failed to sync watcher" + ) + # 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 diff --git a/priv/perf/apps/load_test/lib/ethereum/ethereum.ex b/priv/perf/apps/load_test/lib/ethereum/ethereum.ex index 8aa9fb97df..39795b5daa 100644 --- a/priv/perf/apps/load_test/lib/ethereum/ethereum.ex +++ b/priv/perf/apps/load_test/lib/ethereum/ethereum.ex @@ -20,12 +20,14 @@ defmodule LoadTest.Ethereum do alias ExPlasma.Encoding alias LoadTest.ChildChain.Abi + alias LoadTest.Ethereum.Account alias LoadTest.Ethereum.NonceTracker - alias LoadTest.Ethereum.Sync alias LoadTest.Ethereum.Transaction alias LoadTest.Ethereum.Transaction.Signature + alias LoadTest.Service.Sync @about_4_blocks_time 120_000 + @poll_timeout 60_000 @type hash_t() :: <<_::256>> @@ -49,6 +51,18 @@ defmodule LoadTest.Ethereum do end end + @spec get_gas_used(String.t()) :: non_neg_integer() + def get_gas_used(receipt_hash) do + {{:ok, %{"gasUsed" => gas_used}}, {:ok, %{"gasPrice" => gas_price}}} = + {Ethereumex.HttpClient.eth_get_transaction_receipt(receipt_hash), + Ethereumex.HttpClient.eth_get_transaction_by_hash(receipt_hash)} + + {gas_price_value, ""} = gas_price |> String.replace_prefix("0x", "") |> Integer.parse(16) + {gas_used_value, ""} = gas_used |> String.replace_prefix("0x", "") |> Integer.parse(16) + + gas_price_value * gas_used_value + end + @doc """ Waits until transaction is mined Returns transaction receipt updated with Ethereum block number in which the transaction was mined @@ -105,7 +119,56 @@ defmodule LoadTest.Ethereum do if eth_height < awaited_eth_height, do: :repeat, else: {:ok, eth_height} end - Sync.repeat_until_success(f, timeout) + Sync.repeat_until_success(f, timeout, "Failed to fetch eth block number") + end + + @spec fetch_balance(Account.addr_t(), Account.addr_t()) :: non_neg_integer() | no_return() + def fetch_balance(address, <<0::160>>) do + {:ok, initial_balance} = + Sync.repeat_until_success( + fn -> + address + |> Encoding.to_hex() + |> eth_account_get_balance() + end, + @poll_timeout, + "Failed to fetch eth balance from rootchain" + ) + + {initial_balance, ""} = initial_balance |> String.replace_prefix("0x", "") |> Integer.parse(16) + initial_balance + end + + def fetch_balance(address, currency) do + Sync.repeat_until_success( + fn -> + do_root_chain_get_erc20_balance(address, currency) + end, + @poll_timeout, + "Failed to fetch erc20 balance from rootchain" + ) + end + + defp eth_account_get_balance(address) do + Ethereumex.HttpClient.eth_get_balance(address) + end + + defp do_root_chain_get_erc20_balance(address, currency) do + data = ABI.encode("balanceOf(address)", [Encoding.to_binary(address)]) + + case Ethereumex.HttpClient.eth_call(%{to: Encoding.to_hex(currency), data: Encoding.to_hex(data)}) do + {:ok, result} -> + balance = + result + |> Encoding.to_binary() + |> ABI.TypeDecoder.decode([{:uint, 256}]) + |> hd() + + {:ok, balance} + + error -> + error + end end defp get_external_data(address, signature, params) do @@ -126,7 +189,7 @@ defmodule LoadTest.Ethereum do end end - Sync.repeat_until_success(f, timeout) + Sync.repeat_until_success(f, timeout, "Failed to fetch eth receipt") end defp encode_tx_data(signature, args) do diff --git a/priv/perf/apps/load_test/lib/runner/deposits.ex b/priv/perf/apps/load_test/lib/runner/deposits.ex new file mode 100644 index 0000000000..65b28d879a --- /dev/null +++ b/priv/perf/apps/load_test/lib/runner/deposits.ex @@ -0,0 +1,26 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule LoadTest.Runner.Deposits do + @moduledoc """ + Deposits tests runner. + """ + use Chaperon.LoadTest + + def scenarios do + [ + {{1, LoadTest.Scenario.Deposits}, %{}} + ] + end +end diff --git a/priv/perf/apps/load_test/lib/scenario/deposits.ex b/priv/perf/apps/load_test/lib/scenario/deposits.ex new file mode 100644 index 0000000000..eaa24f23a8 --- /dev/null +++ b/priv/perf/apps/load_test/lib/scenario/deposits.ex @@ -0,0 +1,152 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule LoadTest.Scenario.Deposits do + @moduledoc """ + The scenario for deposits tests: + + 1. It creates two accounts: the depositor and the receiver. + 2. It funds depositor with the specified amount on the rootchain. + 3. It creates deposit for the depositor account on the childchain and + checks balances on the rootchain and the childchain after this deposit. + 4. The depositor account sends the specifed amount on the childchain to the receiver + and checks its balance on the childchain. + + """ + + use Chaperon.Scenario + + alias Chaperon.Session + alias LoadTest.ChildChain.Deposit + alias LoadTest.Ethereum + alias LoadTest.Ethereum.Account + alias LoadTest.Service.Faucet + alias LoadTest.WatcherInfo.Balance + alias LoadTest.WatcherInfo.Transaction + + @spec run(Session.t()) :: Session.t() + def run(session) do + tps = config(session, [:run_config, :tps]) + period_in_seconds = config(session, [:run_config, :period_in_seconds]) + + total_number_of_transactions = tps * period_in_seconds + period_in_mseconds = period_in_seconds * 1_000 + + session + |> cc_spread( + :create_deposit_and_make_assertions, + total_number_of_transactions, + period_in_mseconds + ) + |> await_all(:create_deposit_and_make_assertions) + end + + def create_deposit_and_make_assertions(session) do + with {:ok, from, to} <- create_accounts(session), + :ok <- create_deposit(from, session), + :ok <- send_value_to_receiver(from, to, session) do + Session.add_metric(session, "error_rate", 0) + else + error -> + log_error(session, "#{__MODULE__} failed with #{inspect(error)}") + + session + |> Session.add_metric("error_rate", 1) + |> Session.add_error(:test, error) + end + end + + defp create_accounts(session) do + initial_amount = config(session, [:chain_config, :initial_amount]) + {:ok, from_address} = Account.new() + {:ok, to_address} = Account.new() + + {:ok, _} = Faucet.fund_root_chain_account(from_address.addr, initial_amount) + + {:ok, from_address, to_address} + end + + defp create_deposit(from_address, session) do + token = config(session, [:chain_config, :token]) + deposited_amount = config(session, [:chain_config, :deposited_amount]) + initial_amount = config(session, [:chain_config, :initial_amount]) + gas_price = config(session, [:chain_config, :gas_price]) + + txhash = Deposit.deposit_from(from_address, deposited_amount, token, 10, gas_price, :txhash) + + gas_used = Ethereum.get_gas_used(txhash) + + with :ok <- + fetch_childchain_balance(from_address, + amount: deposited_amount, + token: token, + error: :wrong_childchain_from_balance_after_deposit + ), + :ok <- + fetch_rootchain_balance( + from_address, + amount: initial_amount - deposited_amount - gas_used, + token: token, + error: :wrong_rootchain_balance_after_deposit + ) do + :ok + end + end + + defp send_value_to_receiver(from_address, to_address, session) do + token = config(session, [:chain_config, :token]) + transferred_amount = config(session, [:chain_config, :transferred_amount]) + + with _ <- send_amount_on_childchain(from_address, to_address, token, transferred_amount), + :ok <- + fetch_childchain_balance( + to_address, + amount: transferred_amount, + token: token, + error: :wrong_childchain_to_balance_after_sending_deposit + ) do + :ok + end + end + + defp send_amount_on_childchain(from, to, token, amount) do + {:ok, [sign_hash, typed_data, _txbytes]} = + Transaction.create_transaction( + amount, + from.addr, + to.addr, + token + ) + + Transaction.submit_transaction(typed_data, sign_hash, [from.priv]) + end + + defp fetch_childchain_balance(account, amount: amount, token: token, error: error) do + childchain_balance = Balance.fetch_balance(account.addr, amount, token) + + case childchain_balance["amount"] do + ^amount -> :ok + _ -> error + end + end + + defp fetch_rootchain_balance(account, amount: amount, token: token, error: error) do + rootchain_balance = Ethereum.fetch_balance(account.addr, token) + + case rootchain_balance do + ^amount -> :ok + _ -> error + end + end +end diff --git a/priv/perf/apps/load_test/lib/service/faucet.ex b/priv/perf/apps/load_test/lib/service/faucet.ex index 8b6ec8f3f3..aeb3fa6552 100644 --- a/priv/perf/apps/load_test/lib/service/faucet.ex +++ b/priv/perf/apps/load_test/lib/service/faucet.ex @@ -43,7 +43,7 @@ defmodule LoadTest.Service.Faucet do # Submitting a transaction to the childchain can fail if it is under heavy load, # allow the faucet to retry to avoid failing the test prematurely. - @fund_child_chain_account_retries 100 + @fund_child_chain_account_timeout 100_000 @type state :: %__MODULE__{ faucet_account: Account.t(), @@ -134,7 +134,7 @@ defmodule LoadTest.Service.Faucet do state.faucet_account, receiver, currency, - @fund_child_chain_account_retries + @fund_child_chain_account_timeout ) [next_faucet_utxo, user_utxo] = @@ -200,7 +200,7 @@ defmodule LoadTest.Service.Faucet do defp deposit(faucet_account, amount, currency, deposit_finality_margin, gas_price) do Logger.debug("Not enough funds in the faucet, depositing #{amount} from the root chain") - {:ok, utxo} = Deposit.deposit_from(faucet_account, amount, currency, deposit_finality_margin, gas_price) + {:ok, utxo} = Deposit.deposit_from(faucet_account, amount, currency, deposit_finality_margin, gas_price, :utxo) utxo end end diff --git a/priv/perf/apps/load_test/lib/service/sleeper.ex b/priv/perf/apps/load_test/lib/service/sleeper.ex new file mode 100644 index 0000000000..538b76e179 --- /dev/null +++ b/priv/perf/apps/load_test/lib/service/sleeper.ex @@ -0,0 +1,31 @@ +# 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.Service.Sleeper do + @moduledoc """ + Sleeps the current process for the given timeout and print the given warning. + """ + + require Logger + + @spec sleep(String.t()) :: :ok + def sleep(message) do + _ = Logger.info(message) + Process.sleep(retry_sleep()) + end + + defp retry_sleep() do + Application.fetch_env!(:load_test, :retry_sleep) + end +end diff --git a/priv/perf/apps/load_test/lib/ethereum/sync.ex b/priv/perf/apps/load_test/lib/service/sync.ex similarity index 64% rename from priv/perf/apps/load_test/lib/ethereum/sync.ex rename to priv/perf/apps/load_test/lib/service/sync.ex index 245d8a7153..a85eef8d0a 100644 --- a/priv/perf/apps/load_test/lib/ethereum/sync.ex +++ b/priv/perf/apps/load_test/lib/service/sync.ex @@ -12,11 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule LoadTest.Ethereum.Sync do +defmodule LoadTest.Service.Sync do @moduledoc """ Provides a function for repeating a function call until a given criteria is met. """ - @sleep_interval_ms 100 + + require Logger + + alias LoadTest.Service.Sleeper @doc """ Repeats f until f returns {:ok, ...}, :ok OR exception is raised (see :erlang.exit, :erlang.error) OR timeout @@ -24,24 +27,30 @@ defmodule LoadTest.Ethereum.Sync do Simple throws and :badmatch are treated as signals to repeat """ - def repeat_until_success(f, timeout) do - fn -> do_repeat_until_success(f) end + def repeat_until_success(f, timeout, message) do + fn -> do_repeat_until_success(f, message) end |> Task.async() |> Task.await(timeout) end - defp do_repeat_until_success(f) do - Process.sleep(@sleep_interval_ms) - - try do - case f.() do - :ok = return -> return - {:ok, _} = return -> return - _ -> do_repeat_until_success(f) - end - catch - _ -> do_repeat_until_success(f) - :error, {:badmatch, _} -> do_repeat_until_success(f) + defp do_repeat_until_success(f, message) do + case f.() do + :ok -> + :ok + + {:ok, _} = return -> + return + + result -> + repeat(f, message, result) end + catch + result -> + repeat(f, message, result) + end + + defp repeat(f, message, result) do + Sleeper.sleep(message <> " #{inspect(result)}") + do_repeat_until_success(f, message) end end diff --git a/priv/perf/apps/load_test/lib/test_runner.ex b/priv/perf/apps/load_test/lib/test_runner.ex new file mode 100644 index 0000000000..6299cf3aeb --- /dev/null +++ b/priv/perf/apps/load_test/lib/test_runner.ex @@ -0,0 +1,51 @@ +# 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.TestRunner do + @moduledoc """ + This module runs tests using `mix run`. For example: + + mix run -e "LoadTest.TestRunner.run()" -- "deposits" "1" "5" + + It accepts three arguments: + - test name + - transactions per seconds + - period in seconds + + Optional paramters: + - Percentile. You can pass percentile as the fourth parameter. By default `mean` value is used. + + You can also modify values for tests by providing `TEST_CONFIG_PATH` env variable, it should contain + the path to json file. For example: + + TEST_CONFIG_PATH=./my_file mix run -e "LoadTest.TestRunner.run()" -- "deposits" "1" "5" 90 + + It fetches all configuration params from env vars. + """ + alias LoadTest.TestRunner.Config + + def run() do + {runner_module, config, property} = Config.parse() + result = Chaperon.run_load_test(runner_module, print_results: true, config: config) + + # / 1 is to convert result to float (mean is float, percentiles are integers)[O + case result.metrics["error_rate"][property] / 1 do + 0.0 -> + System.halt(0) + + _ -> + System.halt(1) + end + end +end diff --git a/priv/perf/apps/load_test/lib/test_runner/config.ex b/priv/perf/apps/load_test/lib/test_runner/config.ex new file mode 100644 index 0000000000..857d666974 --- /dev/null +++ b/priv/perf/apps/load_test/lib/test_runner/config.ex @@ -0,0 +1,133 @@ +# 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.TestRunner.Config do + @moduledoc """ + Command line args parser for TestRunner. + """ + alias ExPlasma.Encoding + + @tests %{ + "deposits" => LoadTest.Runner.Deposits + } + + @configs %{ + "deposits" => %{ + token: {:binary, "0x0000000000000000000000000000000000000000"}, + initial_amount: 500_000_000_000_000_000, + deposited_amount: 200_000_000_000_000_000, + transferred_amount: 100_000_000_000_000_000, + gas_price: 2_000_000_000 + } + } + + def parse() do + [test, rate, period, property] = + case System.argv() do + [test, rate, period] -> + [test, rate, period, :mean] + + [test, rate, period, percentile] -> + percentile = parse_percentile(percentile) + [test, rate, period, percentile] + end + + rate_int = String.to_integer(rate) + period_int = String.to_integer(period) + + config = config(test, rate_int, period_int) + + runner_module = Map.fetch!(@tests, test) + + {runner_module, config, property} + end + + defp config(test, rate_int, period_int) do + # Chaperon's SpreadAsyns (https://github.com/polleverywhere/chaperon/blob/13cc4a2d2a7baacddf20c46397064b5e42a48d97/lib/chaperon/action/spread_async.ex) + # spawns a separate process for each execution. VM may fail if too many processes are spawned + if rate_int * period_int > 200_000, do: raise("too many processes") + + run_config = %{ + tps: rate_int, + period_in_seconds: period_int + } + + chain_config = read_config!(test) + + %{ + run_config: run_config, + chain_config: chain_config, + timeout: :infinity + } + end + + defp read_config!(test) do + config_path = System.get_env("TEST_CONFIG_PATH") + + case config_path do + nil -> + @configs + |> Map.fetch!(test) + |> parse_config_values() + + _ -> + parse_config_file!(config_path, test) + end + end + + defp parse_config_file!(file_path, test) do + default_config = Map.fetch!(@configs, test) + config = file_path |> File.read!() |> Jason.decode!() + + default_config + |> Enum.map(fn {key, default_value} -> + string_key = Atom.to_string(key) + + value = + case default_value do + {type, value} -> {type, config[string_key] || value} + value -> config[string_key] || value + end + + {key, value} + end) + |> Map.new() + |> parse_config_values() + end + + defp parse_config_values(config) do + config + |> Enum.map(fn {key, value} -> + parsed_value = + case value do + {:binary, string} -> Encoding.to_binary(string) + value -> value + end + + {key, parsed_value} + end) + |> Map.new() + end + + defp parse_percentile(percentile) do + percentile_int = String.to_integer(percentile) + + # percentile should be divisible by 10 and < 100 (10, 20, ... 90) + unless rem(percentile_int, 10) == 0 and div(percentile_int, 10) < 10 do + raise("Wrong percentile") + end + + {:percentile, percentile_int / 1} + end +end diff --git a/priv/perf/apps/load_test/lib/watcher_info/balance.ex b/priv/perf/apps/load_test/lib/watcher_info/balance.ex new file mode 100644 index 0000000000..2383c70de5 --- /dev/null +++ b/priv/perf/apps/load_test/lib/watcher_info/balance.ex @@ -0,0 +1,71 @@ +# 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.WatcherInfo.Balance do + @moduledoc """ + Functions related to balanes on the childchain + """ + require Logger + + alias ExPlasma.Encoding + alias LoadTest.Ethereum.Account + alias LoadTest.Service.Sync + + @poll_timeout 60_000 + + @spec fetch_balance(Account.addr_t(), non_neg_integer(), Account.addr_t()) :: non_neg_integer() | :error | nil | map() + def fetch_balance(address, amount, currency \\ <<0::160>>) do + {:ok, result} = + Sync.repeat_until_success( + fn -> + do_fetch_balance(Encoding.to_hex(address), amount, Encoding.to_hex(currency)) + end, + @poll_timeout, + "Failed to fetch childchain balance" + ) + + result + end + + defp do_fetch_balance(address, amount, currency) do + response = + case account_get_balances(address) do + {:ok, response} -> + decoded_response = Jason.decode!(response.body) + Enum.find(decoded_response["data"], fn data -> data["currency"] == currency end) + + result -> + Logger.error("Failed to fetch balance from childchain #{inspect(result)}") + + :error + end + + case response do + # empty response is considered no account balance! + nil when amount == 0 -> + {:ok, nil} + + %{"amount" => ^amount} = balance -> + {:ok, balance} + + response -> + {:error, response} + end + end + + defp account_get_balances(address) do + client = LoadTest.Connection.WatcherInfo.client() + WatcherInfoAPI.Api.Account.account_get_balance(client, %{address: address}) + end +end diff --git a/priv/perf/apps/load_test/lib/watcher_info/transaction.ex b/priv/perf/apps/load_test/lib/watcher_info/transaction.ex new file mode 100644 index 0000000000..8ad4021124 --- /dev/null +++ b/priv/perf/apps/load_test/lib/watcher_info/transaction.ex @@ -0,0 +1,146 @@ +# 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.WatcherInfo.Transaction do + @moduledoc """ + Functions for working with transactions WatherInfo API + """ + + alias ExPlasma.Encoding + alias LoadTest.Ethereum.Account + alias LoadTest.Service.Sync + + @poll_timeout 60_000 + + @spec create_transaction( + non_neg_integer(), + Account.addr_t(), + Account.addr_t(), + Account.addr_t(), + non_neg_integer() + ) :: + {:ok, [binary()]} | {:error, map()} + + def create_transaction(amount_in_wei, input_address, output_address, currency \\ <<0::160>>, timeout \\ 120_000) do + func = fn -> + transaction = %WatcherInfoAPI.Model.CreateTransactionsBodySchema{ + owner: Encoding.to_hex(input_address), + payments: [ + %WatcherInfoAPI.Model.TransactionCreatePayments{ + amount: amount_in_wei, + currency: Encoding.to_hex(currency), + owner: Encoding.to_hex(output_address) + } + ], + fee: %WatcherInfoAPI.Model.TransactionCreateFee{currency: Encoding.to_hex(currency)} + } + + {:ok, response} = + WatcherInfoAPI.Api.Transaction.create_transaction(LoadTest.Connection.WatcherInfo.client(), transaction) + + result = Jason.decode!(response.body)["data"] + + process_transaction_result(result) + end + + Sync.repeat_until_success(func, timeout, "Failed to create a transaction") + end + + @spec submit_transaction(binary(), binary(), [binary()]) :: map() + def submit_transaction(typed_data, sign_hash, private_keys) do + signatures = + Enum.map(private_keys, fn private_key -> + sign_hash + |> to_binary() + |> signature_digest(private_key) + |> Encoding.to_hex() + end) + + typed_data_signed = Map.put_new(typed_data, "signatures", signatures) + + Sync.repeat_until_success( + fn -> + submit_typed(typed_data_signed) + end, + @poll_timeout, + "Failed to submit transaction" + ) + end + + defp submit_typed(typed_data_signed) do + {:ok, response} = execute_submit_typed(typed_data_signed) + decoded_response = Jason.decode!(response.body)["data"] + + case decoded_response do + %{"messages" => %{"code" => "submit:utxo_not_found"}} = error -> + {:error, error} + + %{"messages" => %{"code" => "operation:service_unavailable"}} = error -> + {:error, error} + + %{"txhash" => _} -> + {:ok, decoded_response} + end + end + + defp execute_submit_typed(typed_data_signed) do + WatcherInfoAPI.Api.Transaction.submit_typed(LoadTest.Connection.WatcherInfo.client(), typed_data_signed) + end + + defp process_transaction_result(result) do + case result do + %{"code" => "create:client_error"} -> + {:error, result} + + %{ + "result" => "complete", + "transactions" => [ + %{ + "sign_hash" => sign_hash, + "typed_data" => typed_data, + "txbytes" => txbytes + } + ] + } -> + {:ok, [sign_hash, typed_data, txbytes]} + + error -> + {:error, error} + end + end + + defp signature_digest(hash_digest, private_key_binary) do + {:ok, <>, recovery_id} = + :libsecp256k1.ecdsa_sign_compact( + hash_digest, + private_key_binary, + :default, + <<>> + ) + + # EIP-155 + # See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md + base_recovery_id = 27 + recovery_id = base_recovery_id + recovery_id + + <> + end + + defp to_binary(hex) do + hex + |> String.replace_prefix("0x", "") + |> String.upcase() + |> Base.decode16!() + end +end diff --git a/priv/perf/apps/load_test/mix.exs b/priv/perf/apps/load_test/mix.exs index a7affc9511..e4f05fd99c 100644 --- a/priv/perf/apps/load_test/mix.exs +++ b/priv/perf/apps/load_test/mix.exs @@ -34,7 +34,8 @@ defmodule LoadTest.MixProject do {: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}, + {:ex_plasma, + git: "https://github.com/omisego/ex_plasma.git", ref: "9fcda87af92e9d19cf3253ecea66f9c2c8a0ccd0", override: true}, {:telemetry, "~> 0.4.1"}, # Better adapter for tesla diff --git a/priv/perf/apps/load_test/test/load_tests/runner/deposits_test.exs b/priv/perf/apps/load_test/test/load_tests/runner/deposits_test.exs new file mode 100644 index 0000000000..6bc59e2942 --- /dev/null +++ b/priv/perf/apps/load_test/test/load_tests/runner/deposits_test.exs @@ -0,0 +1,48 @@ +# Copyright 2019-2020 OmiseGO Pte Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule LoadTest.Runner.DepositsTest do + use ExUnit.Case + + @moduletag :deposits + + alias ExPlasma.Encoding + + test "deposits test" do + token = Encoding.to_binary("0x0000000000000000000000000000000000000000") + initial_amount = 500_000_000_000_000_000 + deposited_amount = 200_000_000_000_000_000 + transferred_amount = 100_000_000_000_000_000 + gas_price = 2_000_000_000 + + config = %{ + chain_config: %{ + token: token, + initial_amount: initial_amount, + deposited_amount: deposited_amount, + transferred_amount: transferred_amount, + gas_price: gas_price + }, + run_config: %{ + tps: 1, + period_in_seconds: 20 + }, + timeout: :infinity + } + + result = Chaperon.run_load_test(LoadTest.Runner.Deposits, config: config) + + assert result.metrics["error_rate"][:mean] == 0.0 + end +end diff --git a/priv/perf/config/config.exs b/priv/perf/config/config.exs index d0941409c5..aad0cb0595 100644 --- a/priv/perf/config/config.exs +++ b/priv/perf/config/config.exs @@ -9,15 +9,18 @@ ethereum_client_timeout_ms = 20_000 config :ethereumex, http_options: [recv_timeout: ethereum_client_timeout_ms], - url: System.get_env("ETHEREUM_RPC_URL") + url: System.get_env("ETHEREUM_RPC_URL") || "http://localhost:8545" config :load_test, pool_size: 5000, max_connection: 5000, - child_chain_url: System.get_env("CHILD_CHAIN_URL"), - watcher_security_url: System.get_env("WATCHER_SECURITY_URL"), - watcher_info_url: System.get_env("WATCHER_INFO_URL"), - faucet_private_key: System.get_env("LOAD_TEST_FAUCET_PRIVATE_KEY"), + retry_sleep: System.get_env("RETRY_SLEEP") || 1_000, + child_chain_url: System.get_env("CHILD_CHAIN_URL") || "http://localhost:9656", + watcher_security_url: System.get_env("WATCHER_SECURITY_URL") || "http://localhost:7434", + watcher_info_url: System.get_env("WATCHER_INFO_URL") || "http://localhost:7534", + faucet_private_key: + System.get_env("LOAD_TEST_FAUCET_PRIVATE_KEY") || + "0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce", eth_vault_address: System.get_env("CONTRACT_ADDRESS_ETH_VAULT"), contract_address_payment_exit_game: System.get_env("CONTRACT_ADDRESS_PAYMENT_EXIT_GAME"), child_block_interval: 1000, diff --git a/priv/perf/mix.lock b/priv/perf/mix.lock index a84a4c7b7b..1bf4cf38cc 100644 --- a/priv/perf/mix.lock +++ b/priv/perf/mix.lock @@ -1,20 +1,20 @@ %{ - "basic_auth": {:hex, :basic_auth, "2.2.4", "d8c748237870dd1df3bc5c0f1ab4f1fad6270c75472d7e62b19302ec59e92a79", [:mix], [{:plug, "~> 0.14 or ~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "a595b5f2a07e94cbde7be3dfeba24573e18655c88e74c8eb118364f856642e62"}, + "basic_auth": {:hex, :basic_auth, "2.2.5", "ec2c934e4943b63cfc7d6b01c6f3fa51ade2a518ca36c9c0caee18a90bf98c4e", [:mix], [{:plug, "~> 0.14 or ~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8b5f067bcfe48d7dc02d43c18ad9e9b54e630c2da720667ac8ed46979b54b7cb"}, "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"}, - "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, - "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, - "credo": {:hex, :credo, "1.3.2", "08d456dcf3c24da162d02953fb07267e444469d8dad3a2ae47794938ea467b3a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b11d28cce1f1f399dddffd42d8e21dcad783309e230f84b70267b1a5546468b6"}, + "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", [hex: :websockex, repo: "hexpm", optional: false]}], "hexpm", "dbcf477fe177b9ea91a5f838d5df99cbed1ea4e634f5070a718bbea1767a82ce"}, + "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "e_q": {:hex, :e_q, "1.0.0", "7b4dab148b8f482fac6be0e3b5ecf5b4ca86d6148b33b96ee7ca9f2e2fcf2fb7", [:mix], [], "hexpm", "23dbb293640ba24a38c6b56bfa81a569b8a6654b85e0ccbedf89c740e2b91937"}, - "ethereumex": {:hex, :ethereumex, "0.6.0", "6c3a8f7ac09aa390db5c7d79678c1833948725c4c157946e86a7f4a1a6d03ce2", [:mix], [{:httpoison, "~> 1.4.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1", [hex: :poolboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b221e989962a3a9e9d8d40af212ca86d3ee72800493a432a54426c807517f237"}, + "ethereumex": {:hex, :ethereumex, "0.6.4", "58e998acb13b45a2b76b954b1d503f2f5f0e8118c0a14769c59264ef3ee4c301", [:mix], [{:httpoison, "~> 1.6", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1", [hex: :poolboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "abc0bed1ba691645700f55bc843be7d08a23284e20ad889cabe6279e13debb32"}, "ex_abi": {:hex, :ex_abi, "0.2.2", "805c19edccb98ecd02a2eee1851d49f625ae036953ae45b3349468333b19870e", [:mix], [{:exth_crypto, "~> 0.1.6", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm", "649688a53dde9c676f6a760cc0c1e43cb246010a7f83b13b9e5bd75aa04cc409"}, - "ex_aws": {:hex, :ex_aws, "2.1.1", "1e4de2106cfbf4e837de41be41cd15813eabc722315e388f0d6bb3732cec47cd", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "06b6fde12b33bb6d65d5d3493e903ba5a56d57a72350c15285a4298338089e10"}, + "ex_aws": {:hex, :ex_aws, "2.1.5", "f0f3f3e8a93e32b895be7981d34da14194effeae2ba9ba206e87d211f283fa62", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0f0357f116fc3ca6e962c0be46cc42e9a16622ecd26fcc6c8b97b114d3f81949"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.2", "c0258bbdfea55de4f98f0b2f0ca61fe402cc696f573815134beb1866e778f47b", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "0569f5b211b1a3b12b705fe2a9d0e237eb1360b9d76298028df2346cad13097a"}, - "ex_plasma": {:git, "https://github.com/omisego/ex_plasma.git", "9fcda87af92e9d19cf3253ecea66f9c2c8a0ccd0", []}, + "ex_plasma": {:git, "https://github.com/omisego/ex_plasma.git", "9fcda87af92e9d19cf3253ecea66f9c2c8a0ccd0", [ref: "9fcda87af92e9d19cf3253ecea66f9c2c8a0ccd0"]}, "ex_rlp": {:hex, :ex_rlp, "0.5.3", "9055bddade545ee3e734aaad62c4b4d08211834da3beb43ae269b75785909e5e", [:mix], [], "hexpm", "a755a5f8f9f66079f3ecbe021536b949077fac0df963d9e59a20321bab28722d"}, "exth_crypto": {:hex, :exth_crypto, "0.1.6", "8e636a9bcb75d8e32451be96e547a495121ed2178d078db294edb0f81f7cf2e8", [:mix], [{:binary, "~> 0.0.4", [hex: :binary, repo: "hexpm", optional: false]}, {:keccakf1600, "~> 2.0.0", [hex: :keccakf1600_orig, repo: "hexpm", optional: false]}, {:libsecp256k1, "~> 0.1.9", [hex: :libsecp256k1, repo: "hexpm", optional: false]}], "hexpm", "45d6faf4b889f8fc526deba059e0c7947423784ab1e7fa85be8db4c46cf4416b"}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, @@ -23,24 +23,24 @@ "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "influxql": {:hex, :influxql, "0.2.1", "71bfd5c0d81bf870f239baf3357bf5226b44fce16e1b9399ba1368203ca71245", [:mix], [], "hexpm", "75faf04960d6830ca0827869eaac1ba092655041c5e96deb2a588bafb601205c"}, "instream": {:hex, :instream, "0.21.0", "9660449133120cb19851d92173c92164cf3f70deb32c4b7ee2a772b575fa7df8", [:mix], [{:hackney, "~> 1.1", [hex: :hackney, repo: "hexpm", optional: false]}, {:influxql, "~> 0.2.0", [hex: :influxql, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "bbc6f1c53679df6deb78806ec24259413219b2c9a7c2dbdd6289f3544f065111"}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], [], "hexpm", "bdbbb02d67bea35605f95d4e3de48203347374e414da7945c4f2f7fd13ffe632"}, "libsecp256k1": {:git, "https://github.com/omisego/libsecp256k1.git", "83d4c91b7b5ad79fdd3c020be8c57ff6e2212780", [branch: "elixir-only"]}, "libsecp256k1_source": {:git, "https://github.com/bitcoin-core/secp256k1.git", "d33352151699bd7598b868369dace092f7855740", [ref: "d33352151699bd7598b868369dace092f7855740"]}, "merkle_tree": {:hex, :merkle_tree, "2.0.0", "84a9cbfd3d8fab545d69c320460ff713ddea2f64ec4c14edfcc4f8eba899d5ab", [:mix], [], "hexpm", "351a764e385ce75bd782a972fc9597d99bed4d692631903e4a08936d4939c895"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, - "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.1", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, - "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, - "tesla": {:hex, :tesla, "1.3.2", "deb92c5c9ce35e747a395ba413ca78593a4f75bf0e1545630ee2e3d34264021e", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7567704c4790e21bd9a961b56d0b6a988ff68cc4dacfe6b2106e258da1d5cdda"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, + "tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, "websockex": {:hex, :websockex, "0.4.2", "9a3b7dc25655517ecd3f8ff7109a77fce94956096b942836cdcfbc7c86603ecc", [:mix], [], "hexpm", "803cd76e91544b56f0e655e36790be797fa6436db9224f7c303db9b9df2a3df4"},