Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kevsul/standard exit perf test #1732

Merged
merged 12 commits into from
Sep 18, 2020
96 changes: 0 additions & 96 deletions priv/perf/apps/load_test/lib/child_chain/deposit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ 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

Expand Down Expand Up @@ -51,52 +49,6 @@ 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)
Expand Down Expand Up @@ -160,52 +112,4 @@ 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
141 changes: 105 additions & 36 deletions priv/perf/apps/load_test/lib/child_chain/exit.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# 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.Exit do
@moduledoc """
Utility functions for exits on a child chain.
Expand All @@ -6,80 +20,135 @@ defmodule LoadTest.ChildChain.Exit do
require Logger

alias ExPlasma.Encoding
alias ExPlasma.Utxo
alias LoadTest.ChildChain.Transaction
alias LoadTest.Ethereum
alias LoadTest.Ethereum.Account
alias LoadTest.Ethereum.Crypto

@gas_start_exit 400_000
@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
@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(),
@poll_interval 1_000

@doc """
Returns the exit data of a utxo.
"""
@spec get_exit_data(Utxo.t()) :: any()
def get_exit_data(%Utxo{} = utxo), do: get_exit_data(Utxo.pos(utxo))

@spec get_exit_data(non_neg_integer()) :: any()
def get_exit_data(utxo_pos) do
body = %WatcherSecurityCriticalAPI.Model.UtxoPositionBodySchema{
utxo_pos: utxo_pos
}

{:ok, response} =
WatcherSecurityCriticalAPI.Api.UTXO.utxo_get_exit_data(
LoadTest.Connection.WatcherSecurity.client(),
body
)

data = Jason.decode!(response.body)["data"]

%{
proof: data["proof"],
txbytes: data["txbytes"],
utxo_pos: data["utxo_pos"]
}
end

@doc """
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, counter) do
data = get_exit_data(utxo_pos)

if data.proof do
data
else
_ = Logger.info("Waiting for exit data")
Process.sleep(@poll_interval)
wait_for_exit_data(utxo_pos, counter - 1)
end
kevsul marked this conversation as resolved.
Show resolved Hide resolved
end

@doc """
Starts an exit.
"""
@spec start_exit(any(), Account.t(), pos_integer()) :: any()
def start_exit(exit_data, from, gas_price) do
data =
ABI.encode(
"startStandardExit((uint256,bytes,bytes))",
[{utxo_pos, tx_bytes, proof}],
opts
[
{
exit_data.utxo_pos,
Encoding.to_binary(exit_data.txbytes),
Encoding.to_binary(exit_data.proof)
}
]
)

Encoding.to_hex(transaction_hash)
tx = %Ethereum.Transaction{
to: contract_address_payment_exit_game(),
value: @standard_exit_bond,
gas_price: gas_price,
gas_limit: @gas_start_exit,
data: data
}

{:ok, tx_hash} = Ethereum.send_raw_transaction(tx, from)
tx_hash
end

def add_exit_queue() do
if has_exit_queue?() do
def add_exit_queue(vault_id, token, from, gas_price) do
kevsul marked this conversation as resolved.
Show resolved Hide resolved
if has_exit_queue?(vault_id, token) 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]
[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)
tx = %Ethereum.Transaction{
to: Encoding.to_binary(Application.fetch_env!(:load_test, :contract_address_plasma_framework)),
gas_price: gas_price,
gas_limit: @gas_add_exit_queue,
data: data
}

{:ok, receipt_hash} = Ethereumex.HttpClient.eth_send_transaction(txmap)
{:ok, receipt_hash} = Ethereum.send_raw_transaction(tx, from)
Ethereum.transact_sync(receipt_hash)
wait_for_exit_queue(100)
wait_for_exit_queue(vault_id, token, 100)
receipt_hash
end
end

defp wait_for_exit_queue(0), do: exit(1)
defp wait_for_exit_queue(_vault_id, _token, 0), do: exit(1)

defp wait_for_exit_queue(counter) do
if has_exit_queue?() do
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(counter - 1)
wait_for_exit_queue(vault_id, token, counter - 1)
end
end

defp has_exit_queue?() do
defp has_exit_queue?(vault_id, token) do
data =
ABI.encode(
"hasExitQueue(uint256,address)",
[@vault_id, @token]
[vault_id, token]
)

{:ok, receipt_enc} =
Expand Down
34 changes: 29 additions & 5 deletions priv/perf/apps/load_test/lib/child_chain/utxos.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,28 @@ defmodule LoadTest.ChildChain.Utxos do
@moduledoc """
Utility functions for utxos
"""
require Logger

alias ExPlasma.Encoding
alias ExPlasma.Utxo
alias LoadTest.ChildChain.Transaction
alias LoadTest.Connection.WatcherInfo, as: Connection
alias WatcherInfoAPI.Api
alias WatcherInfoAPI.Model

@poll_interval 2_000

@doc """
Returns an addresses utxos.
"""
@spec get_utxos(Utxo.address_binary()) :: list(Utxo.t())
def get_utxos(address) do
body = %Model.AddressBodySchema1{
body = %WatcherInfoAPI.Model.AddressBodySchema1{
address: Encoding.to_hex(address)
}

{:ok, response} = Api.Account.account_get_utxos(Connection.client(), body)
{:ok, response} =
WatcherInfoAPI.Api.Account.account_get_utxos(
LoadTest.Connection.WatcherInfo.client(),
body
)

utxos = Jason.decode!(response.body)["data"]

Expand Down Expand Up @@ -97,4 +102,23 @@ defmodule LoadTest.ChildChain.Utxos do

utxo
end

@doc """
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, counter) 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
Loading