diff --git a/priv/cabbage/apps/itest/lib/reorg.ex b/priv/cabbage/apps/itest/lib/reorg.ex index 3e4cd80386..794a958bf1 100644 --- a/priv/cabbage/apps/itest/lib/reorg.ex +++ b/priv/cabbage/apps/itest/lib/reorg.ex @@ -25,20 +25,34 @@ defmodule Itest.Reorg do @pause_seconds 100 def finish_reorg() do - if Application.get_env(:cabbage, :reorg) do + if run_reorg?() do unpause_container!(@node1) unpause_container!(@node2) - Process.sleep(60 * 1000) + Process.sleep(30 * 1000) end end def start_reorg() do - if Application.get_env(:cabbage, :reorg) do + if run_reorg?() do GenServer.cast(__MODULE__, :reorg_step1) end end + def trigger_reorg(func) do + start_reorg() + + response = func() + + finish_reorg() + + response + end + + defp run_reorg?() do + Application.get_env(:cabbage, :reorg) + end + def start_link() do print_available_containers() diff --git a/priv/cabbage/apps/itest/test/itest/configuration_api_test.exs b/priv/cabbage/apps/itest/test/itest/configuration_api_test.exs index 098d7f02a8..ee138952c7 100644 --- a/priv/cabbage/apps/itest/test/itest/configuration_api_test.exs +++ b/priv/cabbage/apps/itest/test/itest/configuration_api_test.exs @@ -18,9 +18,6 @@ defmodule ConfigurationRetrievalTests do require Logger setup_all do - Reorg.finish_reorg() - Reorg.start_reorg() - data = ABI.encode("getVersion()", []) {:ok, response} = @@ -47,26 +44,28 @@ defmodule ConfigurationRetrievalTests do end defwhen ~r/^Operator deploys "(?[^"]+)"$/, %{service: service}, state do - {:ok, response} = - case service do - "Child Chain" -> - ChildChainAPI.Api.Configuration.configuration_get(ChildChainAPI.Connection.new()) + Reorg.start_reorg(fn -> + {:ok, response} = + case service do + "Child Chain" -> + ChildChainAPI.Api.Configuration.configuration_get(ChildChainAPI.Connection.new()) - "Watcher" -> - WatcherSecurityCriticalAPI.Api.Configuration.configuration_get(WatcherSecurityCriticalAPI.Connection.new()) + "Watcher" -> + WatcherSecurityCriticalAPI.Api.Configuration.configuration_get(WatcherSecurityCriticalAPI.Connection.new()) - "Watcher Info" -> - WatcherInfoAPI.Api.Configuration.configuration_get(WatcherInfoAPI.Connection.new()) - end + "Watcher Info" -> + WatcherInfoAPI.Api.Configuration.configuration_get(WatcherInfoAPI.Connection.new()) + end - body = Jason.decode!(response.body) + body = Jason.decode!(response.body) - new_state = - state - |> Map.put(:service_response, body) - |> Map.put(:service, service) + new_state = + state + |> Map.put(:service_response, body) + |> Map.put(:service, service) - {:ok, new_state} + {:ok, new_state} + end) end defthen ~r/^Operator can read its configurational values$/, _, %{service: service} = state do diff --git a/priv/cabbage/apps/itest/test/itest/deposits_test.exs b/priv/cabbage/apps/itest/test/itest/deposits_test.exs index 143f84bcc6..e6338c83ce 100644 --- a/priv/cabbage/apps/itest/test/itest/deposits_test.exs +++ b/priv/cabbage/apps/itest/test/itest/deposits_test.exs @@ -23,36 +23,34 @@ defmodule DepositsTests do alias Itest.Transactions.Currency setup do - Reorg.finish_reorg() - [{alice_account, alice_pkey}, {bob_account, _bob_pkey}] = Account.take_accounts(2) - Reorg.start_reorg() - %{alice_account: alice_account, alice_pkey: alice_pkey, bob_account: bob_account, gas: 0} end defwhen ~r/^Alice deposits "(?[^"]+)" ETH to the root chain$/, %{amount: amount}, %{alice_account: alice_account} = state do - initial_balance = Itest.Poller.root_chain_get_balance(alice_account) + Reorg.trigger_reorg(fn -> + initial_balance = Itest.Poller.root_chain_get_balance(alice_account) - {:ok, receipt_hash} = - amount - |> Currency.to_wei() - |> Client.deposit(alice_account, Itest.PlasmaFramework.vault(Currency.ether())) + {:ok, receipt_hash} = + amount + |> Currency.to_wei() + |> Client.deposit(alice_account, Itest.PlasmaFramework.vault(Currency.ether())) - gas_used = Client.get_gas_used(receipt_hash) + gas_used = Client.get_gas_used(receipt_hash) - {_, new_state} = - Map.get_and_update!(state, :gas, fn current_gas -> - {current_gas, current_gas + gas_used} - end) + {_, new_state} = + Map.get_and_update!(state, :gas, fn current_gas -> + {current_gas, current_gas + gas_used} + end) - balance_after_deposit = Itest.Poller.root_chain_get_balance(alice_account) + balance_after_deposit = Itest.Poller.root_chain_get_balance(alice_account) - state = Map.put_new(new_state, :alice_ethereum_balance, balance_after_deposit) - {:ok, Map.put_new(state, :alice_initial_balance, initial_balance)} + state = Map.put_new(new_state, :alice_ethereum_balance, balance_after_deposit) + {:ok, Map.put_new(state, :alice_initial_balance, initial_balance)} + end) end defthen ~r/^Alice should have "(?[^"]+)" ETH on the child chain$/, @@ -87,17 +85,19 @@ defmodule DepositsTests do defwhen ~r/^Alice sends Bob "(?[^"]+)" ETH on the child chain$/, %{amount: amount}, %{alice_account: alice_account, alice_pkey: alice_pkey, bob_account: bob_account} = state do - {:ok, [sign_hash, typed_data, _txbytes]} = - Client.create_transaction( - Currency.to_wei(amount), - alice_account, - bob_account - ) - - # Alice needs to sign 2 inputs of 1 Eth, 1 for Bob and 1 for the fees - _ = Client.submit_transaction(typed_data, sign_hash, [alice_pkey, alice_pkey]) - - {:ok, state} + Reorg.trigger_reorg(fn -> + {:ok, [sign_hash, typed_data, _txbytes]} = + Client.create_transaction( + Currency.to_wei(amount), + alice_account, + bob_account + ) + + # Alice needs to sign 2 inputs of 1 Eth, 1 for Bob and 1 for the fees + _ = Client.submit_transaction(typed_data, sign_hash, [alice_pkey, alice_pkey]) + + {:ok, state} + end) end defthen ~r/^Bob should have "(?[^"]+)" ETH on the child chain$/, diff --git a/priv/cabbage/apps/itest/test/itest/fee_claiming_test.exs b/priv/cabbage/apps/itest/test/itest/fee_claiming_test.exs index 5a93d28cba..21bae2a510 100644 --- a/priv/cabbage/apps/itest/test/itest/fee_claiming_test.exs +++ b/priv/cabbage/apps/itest/test/itest/fee_claiming_test.exs @@ -26,12 +26,8 @@ defmodule FeeClaimingTests do @expected_fee_rule %{"amount" => 1, "currency" => "0x0000000000000000000000000000000000000000"} setup do - Reorg.finish_reorg() - [{alice_address, alice_pkey}, {bob_address, bob_pkey}] = Account.take_accounts(2) - Reorg.start_reorg() - initial_balance = @fee_claimer_address |> Client.get_balance() @@ -57,39 +53,43 @@ defmodule FeeClaimingTests do defwhen ~r/^"(?[^"]+)" deposits "(?[^"]+)" ETH to the root chain$/, %{entity: entity, amount: amount}, state do - entity_address = state[entity].address + Reorg.trigger_reorg(fn -> + entity_address = state[entity].address - {:ok, receipt_hash} = - amount - |> Currency.to_wei() - |> Client.deposit(entity_address, Itest.PlasmaFramework.vault(Currency.ether())) + {:ok, receipt_hash} = + amount + |> Currency.to_wei() + |> Client.deposit(entity_address, Itest.PlasmaFramework.vault(Currency.ether())) - gas_used = Client.get_gas_used(receipt_hash) + gas_used = Client.get_gas_used(receipt_hash) - {_, new_state} = - Map.get_and_update!(state, :gas, fn current_gas -> - {current_gas, current_gas + gas_used} - end) + {_, new_state} = + Map.get_and_update!(state, :gas, fn current_gas -> + {current_gas, current_gas + gas_used} + end) - {:ok, new_state} + {:ok, new_state} + end) end defwhen ~r/^"(?[^"]+)" sends "(?[^"]+)" "(?[^"]+)" ETH on the child chain$/, %{sender: sender, receiver: receiver, amount: amount}, state do - sender = state[sender] - receiver = state[receiver] + Reorg.trigger_reorg(fn -> + sender = state[sender] + receiver = state[receiver] - {:ok, [sign_hash, typed_data, _txbytes]} = - Client.create_transaction( - Currency.to_wei(amount), - sender.address, - receiver.address - ) + {:ok, [sign_hash, typed_data, _txbytes]} = + Client.create_transaction( + Currency.to_wei(amount), + sender.address, + receiver.address + ) - _ = Client.submit_transaction(typed_data, sign_hash, [sender.pkey]) + _ = Client.submit_transaction(typed_data, sign_hash, [sender.pkey]) - {:ok, state} + {:ok, state} + end) end defthen ~r/^"(?[^"]+)" should have "(?[^"]+)" ETH on the child chain$/, diff --git a/priv/cabbage/apps/itest/test/itest/in_flight_exits_test.exs b/priv/cabbage/apps/itest/test/itest/in_flight_exits_test.exs index 96bd719f62..bb1ccb02fb 100644 --- a/priv/cabbage/apps/itest/test/itest/in_flight_exits_test.exs +++ b/priv/cabbage/apps/itest/test/itest/in_flight_exits_test.exs @@ -63,7 +63,6 @@ defmodule InFlightExitsTests do @gas_process_exit_price 1_000_000_000 setup do - Reorg.finish_reorg() # as we're testing IFEs, queue needs to be empty 0 = get_next_exit_from_queue() vault_address = Currency.ether() |> Itest.PlasmaFramework.vault() |> Encoding.to_hex() @@ -87,8 +86,6 @@ defmodule InFlightExitsTests do exit_game_contract_address = Itest.PlasmaFramework.exit_game_contract_address(ExPlasma.payment_v1()) - Reorg.start_reorg() - %{ "exit_game_contract_address" => exit_game_contract_address, "in_flight_exit_bond_size" => get_in_flight_exit_bond_size(exit_game_contract_address), @@ -127,42 +124,44 @@ defmodule InFlightExitsTests do defgiven ~r/^"(?[^"]+)" deposits "(?[^"]+)" ETH to the root chain$/, %{entity: entity, amount: amount}, state do - %{address: address} = entity_state = state[entity] - initial_balance = Itest.Poller.root_chain_get_balance(address) + Reorg.trigger_reorg(fn -> + %{address: address} = entity_state = state[entity] + initial_balance = Itest.Poller.root_chain_get_balance(address) - {:ok, receipt_hash} = - amount - |> Currency.to_wei() - |> Client.deposit(address, Itest.PlasmaFramework.vault(Currency.ether())) + {:ok, receipt_hash} = + amount + |> Currency.to_wei() + |> Client.deposit(address, Itest.PlasmaFramework.vault(Currency.ether())) - geth_block_every = 1 + geth_block_every = 1 - {:ok, response} = - WatcherSecurityCriticalAPI.Api.Configuration.configuration_get(WatcherSecurityCriticalAPI.Connection.new()) + {:ok, response} = + WatcherSecurityCriticalAPI.Api.Configuration.configuration_get(WatcherSecurityCriticalAPI.Connection.new()) - watcher_security_critical_config = - WatcherSecurityCriticalConfiguration.to_struct(Jason.decode!(response.body)["data"]) + watcher_security_critical_config = + WatcherSecurityCriticalConfiguration.to_struct(Jason.decode!(response.body)["data"]) - finality_margin_blocks = watcher_security_critical_config.deposit_finality_margin - to_miliseconds = 1000 + finality_margin_blocks = watcher_security_critical_config.deposit_finality_margin + to_miliseconds = 1000 - finality_margin_blocks - |> Kernel.*(geth_block_every) - |> Kernel.*(to_miliseconds) - |> Kernel.round() - |> Process.sleep() + finality_margin_blocks + |> Kernel.*(geth_block_every) + |> Kernel.*(to_miliseconds) + |> Kernel.round() + |> Process.sleep() - balance_after_deposit = Itest.Poller.root_chain_get_balance(address) - deposited_amount = initial_balance - balance_after_deposit + balance_after_deposit = Itest.Poller.root_chain_get_balance(address) + deposited_amount = initial_balance - balance_after_deposit - entity_state = - entity_state - |> Map.put(:ethereum_balance, balance_after_deposit) - |> Map.put(:ethereum_initial_balance, initial_balance) - |> Map.put(:last_deposited_amount, deposited_amount) - |> Map.put(:receipt_hashes, [receipt_hash | entity_state.receipt_hashes]) + entity_state = + entity_state + |> Map.put(:ethereum_balance, balance_after_deposit) + |> Map.put(:ethereum_initial_balance, initial_balance) + |> Map.put(:last_deposited_amount, deposited_amount) + |> Map.put(:receipt_hashes, [receipt_hash | entity_state.receipt_hashes]) - {:ok, Map.put(state, entity, entity_state)} + {:ok, Map.put(state, entity, entity_state)} + end) end defthen ~r/^"(?[^"]+)" should have "(?[^"]+)" ETH on the child chain after finality margin$/, @@ -215,471 +214,499 @@ defmodule InFlightExitsTests do defgiven ~r/^Alice and Bob create a transaction for "(?[^"]+)" ETH$/, %{amount: amount}, state do - amount = Currency.to_wei(amount) + Reorg.trigger_reorg(fn -> + amount = Currency.to_wei(amount) - %{address: alice_address, utxos: alice_utxos, pkey: alice_pkey, child_chain_balance: alice_child_chain_balance} = - alice_state = state["Alice"] + %{address: alice_address, utxos: alice_utxos, pkey: alice_pkey, child_chain_balance: alice_child_chain_balance} = + alice_state = state["Alice"] - %{address: bob_address, utxos: bob_utxos, pkey: bob_pkey, child_chain_balance: bob_child_chain_balance} = - state["Bob"] + %{address: bob_address, utxos: bob_utxos, pkey: bob_pkey, child_chain_balance: bob_child_chain_balance} = + state["Bob"] - # inputs - alice_deposit_utxo = hd(alice_utxos) + # inputs + alice_deposit_utxo = hd(alice_utxos) - alice_deposit_input = %ExPlasma.Utxo{ - blknum: alice_deposit_utxo["blknum"], - currency: Currency.ether(), - oindex: 0, - txindex: 0, - output_type: 1, - owner: alice_address - } + alice_deposit_input = %ExPlasma.Utxo{ + blknum: alice_deposit_utxo["blknum"], + currency: Currency.ether(), + oindex: 0, + txindex: 0, + output_type: 1, + owner: alice_address + } - bob_deposit_utxo = hd(bob_utxos) + bob_deposit_utxo = hd(bob_utxos) - bob_deposit_input = %ExPlasma.Utxo{ - blknum: bob_deposit_utxo["blknum"], - currency: Currency.ether(), - oindex: 0, - txindex: 0, - output_type: 1, - owner: bob_address - } + bob_deposit_input = %ExPlasma.Utxo{ + blknum: bob_deposit_utxo["blknum"], + currency: Currency.ether(), + oindex: 0, + txindex: 0, + output_type: 1, + owner: bob_address + } - alice_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: alice_address, - amount: alice_child_chain_balance - Currency.to_wei(5) - state["fee"] - } + alice_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: alice_address, + amount: alice_child_chain_balance - Currency.to_wei(5) - state["fee"] + } - bob_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: bob_address, - amount: amount + bob_child_chain_balance - } + bob_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: bob_address, + amount: amount + bob_child_chain_balance + } - # NOTE: Bob-the-double-spender's input comes first, otherwise the currently used contracts impl has problems - transaction = %Payment{inputs: [bob_deposit_input, alice_deposit_input], outputs: [alice_output, bob_output]} + # NOTE: Bob-the-double-spender's input comes first, otherwise the currently used contracts impl has problems + transaction = %Payment{inputs: [bob_deposit_input, alice_deposit_input], outputs: [alice_output, bob_output]} - submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [bob_pkey, alice_pkey] - ) + submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [bob_pkey, alice_pkey] + ) - txbytes = ExPlasma.Transaction.encode(submitted_tx) + txbytes = ExPlasma.Transaction.encode(submitted_tx) - ## we need to duplicate the transaction because we need an unsigned one later! - unsigned_submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [] - ) + ## we need to duplicate the transaction because we need an unsigned one later! + unsigned_submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [] + ) - unsigned_txbytes = ExPlasma.Transaction.encode(unsigned_submitted_tx) + unsigned_txbytes = ExPlasma.Transaction.encode(unsigned_submitted_tx) - alice_state = - alice_state - |> Map.put(:submitted_tx, submitted_tx) - |> Map.put(:txbytes, txbytes) - |> Map.put(:unsigned_submitted_tx, unsigned_submitted_tx) - |> Map.put(:unsigned_txbytes, unsigned_txbytes) + alice_state = + alice_state + |> Map.put(:submitted_tx, submitted_tx) + |> Map.put(:txbytes, txbytes) + |> Map.put(:unsigned_submitted_tx, unsigned_submitted_tx) + |> Map.put(:unsigned_txbytes, unsigned_txbytes) - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end defgiven ~r/^Alice creates a transaction for "(?[^"]+)" ETH$/, %{amount: amount}, state do - amount = Currency.to_wei(amount) - - %{address: alice_address, utxos: alice_utxos, pkey: alice_pkey, child_chain_balance: alice_child_chain_balance} = - alice_state = state["Alice"] - - # inputs - alice_deposit_utxo = hd(alice_utxos) - - alice_deposit_input = %ExPlasma.Utxo{ - blknum: alice_deposit_utxo["blknum"], - currency: Currency.ether(), - oindex: 0, - txindex: 0, - output_type: 1, - owner: alice_address - } + Reorg.trigger_reorg(fn -> + amount = Currency.to_wei(amount) + + %{address: alice_address, utxos: alice_utxos, pkey: alice_pkey, child_chain_balance: alice_child_chain_balance} = + alice_state = state["Alice"] + + # inputs + alice_deposit_utxo = hd(alice_utxos) + + alice_deposit_input = %ExPlasma.Utxo{ + blknum: alice_deposit_utxo["blknum"], + currency: Currency.ether(), + oindex: 0, + txindex: 0, + output_type: 1, + owner: alice_address + } - alice_output_1 = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: alice_address, - amount: amount - } + alice_output_1 = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: alice_address, + amount: amount + } - rest = alice_child_chain_balance - amount - state["fee"] + rest = alice_child_chain_balance - amount - state["fee"] - alice_output_2 = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: alice_address, - amount: rest - } + alice_output_2 = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: alice_address, + amount: rest + } - transaction = %Payment{inputs: [alice_deposit_input], outputs: [alice_output_1, alice_output_2]} + transaction = %Payment{inputs: [alice_deposit_input], outputs: [alice_output_1, alice_output_2]} - in_flight_tx = - ExPlasma.Transaction.sign(transaction, - keys: [alice_pkey] - ) + in_flight_tx = + ExPlasma.Transaction.sign(transaction, + keys: [alice_pkey] + ) - txbytes = ExPlasma.Transaction.encode(in_flight_tx) - alice_state = Map.put(alice_state, :txbytes, txbytes) + txbytes = ExPlasma.Transaction.encode(in_flight_tx) + alice_state = Map.put(alice_state, :txbytes, txbytes) - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end defand ~r/^Bob gets in flight exit data for "(?[^"]+)" ETH from his most recent deposit$/, %{amount: amount}, state do - amount = Currency.to_wei(amount) - %{address: bob_address, utxos: bob_utxos, pkey: bob_pkey} = bob_state = state["Bob"] - - # inputs - bob_deposit_utxo = hd(bob_utxos) - - bob_deposit_input = %ExPlasma.Utxo{ - blknum: bob_deposit_utxo["blknum"], - currency: Currency.ether(), - oindex: 0, - txindex: 0, - output_type: 1, - owner: bob_address - } + Reorg.trigger_reorg(fn -> + amount = Currency.to_wei(amount) + %{address: bob_address, utxos: bob_utxos, pkey: bob_pkey} = bob_state = state["Bob"] + + # inputs + bob_deposit_utxo = hd(bob_utxos) + + bob_deposit_input = %ExPlasma.Utxo{ + blknum: bob_deposit_utxo["blknum"], + currency: Currency.ether(), + oindex: 0, + txindex: 0, + output_type: 1, + owner: bob_address + } - # outputs - bob_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: bob_address, - amount: amount - } + # outputs + bob_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: bob_address, + amount: amount + } - transaction = %Payment{inputs: [bob_deposit_input], outputs: [bob_output]} + transaction = %Payment{inputs: [bob_deposit_input], outputs: [bob_output]} - submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [bob_pkey] - ) + submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [bob_pkey] + ) - txbytes = ExPlasma.Transaction.encode(submitted_tx) + txbytes = ExPlasma.Transaction.encode(submitted_tx) - unsigned_submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [] - ) + unsigned_submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [] + ) - unsigned_txbytes = ExPlasma.Transaction.encode(unsigned_submitted_tx) + unsigned_txbytes = ExPlasma.Transaction.encode(unsigned_submitted_tx) - payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(txbytes)} - response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_data, Watcher.new(), payload) - exit_data = IfeExitData.to_struct(response) + payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(txbytes)} + response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_data, Watcher.new(), payload) + exit_data = IfeExitData.to_struct(response) - bob_state = - bob_state - |> Map.put(:exit_data, exit_data) - |> Map.put(:unsigned_txbytes, unsigned_txbytes) + bob_state = + bob_state + |> Map.put(:exit_data, exit_data) + |> Map.put(:unsigned_txbytes, unsigned_txbytes) - entity = "Bob" - {:ok, Map.put(state, entity, bob_state)} + entity = "Bob" + {:ok, Map.put(state, entity, bob_state)} + end) end defand ~r/^Alice sends the most recently created transaction$/, _, state do - %{txbytes: txbytes} = alice_state = state["Alice"] + Reorg.trigger_reorg(fn -> + %{txbytes: txbytes} = alice_state = state["Alice"] - submit_transaction_response = send_transaction(txbytes) + submit_transaction_response = send_transaction(txbytes) - alice_state = Map.put(alice_state, :transaction_submit, submit_transaction_response) + alice_state = Map.put(alice_state, :transaction_submit, submit_transaction_response) - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end defand ~r/^Bob spends an output from the most recently sent transaction$/, _, state do - %{address: alice_address, transaction_submit: alice_transaction_submit} = state["Alice"] - - %{address: bob_address, pkey: bob_pkey} = bob_state = state["Bob"] - # Bob sends a transaction spending Alices outputs - # inputs - bob_input = %ExPlasma.Utxo{ - blknum: alice_transaction_submit.blknum, - currency: Currency.ether(), - oindex: 1, - txindex: alice_transaction_submit.txindex, - output_type: 1, - owner: bob_address - } + Reorg.trigger_reorg(fn -> + %{address: alice_address, transaction_submit: alice_transaction_submit} = state["Alice"] + + %{address: bob_address, pkey: bob_pkey} = bob_state = state["Bob"] + # Bob sends a transaction spending Alices outputs + # inputs + bob_input = %ExPlasma.Utxo{ + blknum: alice_transaction_submit.blknum, + currency: Currency.ether(), + oindex: 1, + txindex: alice_transaction_submit.txindex, + output_type: 1, + owner: bob_address + } - # outputs - alice_output1 = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: alice_address, - amount: Currency.to_wei(2) - } + # outputs + alice_output1 = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: alice_address, + amount: Currency.to_wei(2) + } - alice_output2 = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: alice_address, - amount: Currency.to_wei(3) - } + alice_output2 = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: alice_address, + amount: Currency.to_wei(3) + } - bob_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: bob_address, - amount: Currency.to_wei(10) - state["fee"] - } + bob_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: bob_address, + amount: Currency.to_wei(10) - state["fee"] + } - transaction = %Payment{inputs: [bob_input], outputs: [alice_output1, alice_output2, bob_output]} + transaction = %Payment{inputs: [bob_input], outputs: [alice_output1, alice_output2, bob_output]} - submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [bob_pkey] - ) + submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [bob_pkey] + ) - txbytes = ExPlasma.Transaction.encode(submitted_tx) + txbytes = ExPlasma.Transaction.encode(submitted_tx) - submit_transaction_response = send_transaction(txbytes) + submit_transaction_response = send_transaction(txbytes) - bob_state = - bob_state - |> Map.put(:submitted_tx, submitted_tx) - |> Map.put(:txbytes, txbytes) - |> Map.put(:transaction_submit, submit_transaction_response) + bob_state = + bob_state + |> Map.put(:submitted_tx, submitted_tx) + |> Map.put(:txbytes, txbytes) + |> Map.put(:transaction_submit, submit_transaction_response) - entity = "Bob" - {:ok, Map.put(state, entity, bob_state)} + entity = "Bob" + {:ok, Map.put(state, entity, bob_state)} + end) end defand ~r/^Alice starts an in flight exit from the most recently created transaction$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] - in_flight_exit_bond_size = state["in_flight_exit_bond_size"] - %{address: address, txbytes: txbytes} = alice_state = state["Alice"] - payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(txbytes)} - response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_data, Watcher.new(), payload) - exit_data = IfeExitData.to_struct(response) - receipt_hash = do_in_flight_exit(exit_game_contract_address, in_flight_exit_bond_size, address, exit_data) - - alice_state = - alice_state - |> Map.put(:exit_data, exit_data) - |> Map.put(:receipt_hashes, [receipt_hash | alice_state.receipt_hashes]) - - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] + in_flight_exit_bond_size = state["in_flight_exit_bond_size"] + %{address: address, txbytes: txbytes} = alice_state = state["Alice"] + payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(txbytes)} + response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_data, Watcher.new(), payload) + exit_data = IfeExitData.to_struct(response) + receipt_hash = do_in_flight_exit(exit_game_contract_address, in_flight_exit_bond_size, address, exit_data) + + alice_state = + alice_state + |> Map.put(:exit_data, exit_data) + |> Map.put(:receipt_hashes, [receipt_hash | alice_state.receipt_hashes]) + + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end defgiven ~r/^"(?[^"]+)" verifies its in flight exit from the most recently created transaction$/, %{entity: entity}, state do - exit_game_contract_address = state["exit_game_contract_address"] - %{exit_data: exit_data} = entity_state = state[entity] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] + %{exit_data: exit_data} = entity_state = state[entity] - in_flight_exit_id = get_in_flight_exit_id(exit_game_contract_address, exit_data) - [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - assert in_flight_exit.exit_map == 0 + in_flight_exit_id = get_in_flight_exit_id(exit_game_contract_address, exit_data) + [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + assert in_flight_exit.exit_map == 0 - entity_state = - entity_state - |> Map.put(:in_flight_exit_id, in_flight_exit_id) - |> Map.put(:in_flight_exit, in_flight_exit) + entity_state = + entity_state + |> Map.put(:in_flight_exit_id, in_flight_exit_id) + |> Map.put(:in_flight_exit, in_flight_exit) - {:ok, Map.put(state, entity, entity_state)} + {:ok, Map.put(state, entity, entity_state)} + end) end defgiven ~r/^Bob piggybacks inputs and outputs from Alices most recent in flight exit$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] - %{exit_data: exit_data, in_flight_exit_id: in_flight_exit_id} = state["Alice"] - %{address: address} = bob_state = state["Bob"] + %{exit_data: exit_data, in_flight_exit_id: in_flight_exit_id} = state["Alice"] + %{address: address} = bob_state = state["Bob"] - output_index = 1 - input_index = 0 + output_index = 1 + input_index = 0 - receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) - receipt_hash_2 = piggyback_input(exit_game_contract_address, address, input_index, exit_data) + receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) + receipt_hash_2 = piggyback_input(exit_game_contract_address, address, input_index, exit_data) - bob_state = - Map.put( - bob_state, - :receipt_hashes, - Enum.concat([receipt_hash_1, receipt_hash_2], bob_state.receipt_hashes) - ) + bob_state = + Map.put( + bob_state, + :receipt_hashes, + Enum.concat([receipt_hash_1, receipt_hash_2], bob_state.receipt_hashes) + ) - [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - # bits is flagged when output is piggybacked - assert in_flight_exit.exit_map != 0 - entity = "Bob" + [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + # bits is flagged when output is piggybacked + assert in_flight_exit.exit_map != 0 + entity = "Bob" - {:ok, Map.put(state, entity, bob_state)} + {:ok, Map.put(state, entity, bob_state)} + end) end defgiven ~r/^Alice piggybacks output from her most recent in flight exit$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] - %{address: address, exit_data: exit_data, in_flight_exit_id: in_flight_exit_id} = alice_state = state["Alice"] + %{address: address, exit_data: exit_data, in_flight_exit_id: in_flight_exit_id} = alice_state = state["Alice"] - output_index = 0 - receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) + output_index = 0 + receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) - alice_state = - Map.put( - alice_state, - :receipt_hashes, - Enum.concat([receipt_hash_1], alice_state.receipt_hashes) - ) + alice_state = + Map.put( + alice_state, + :receipt_hashes, + Enum.concat([receipt_hash_1], alice_state.receipt_hashes) + ) - [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - # bits is flagged when output is piggybacked - assert in_flight_exit.exit_map != 0 + [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + # bits is flagged when output is piggybacked + assert in_flight_exit.exit_map != 0 - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end # ### start the competing IFE, to double-spend some inputs defand ~r/^Bob starts a piggybacked in flight exit using his most recently prepared in flight exit data$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] - in_flight_exit_bond_size = state["in_flight_exit_bond_size"] - %{address: address, exit_data: exit_data} = bob_state = state["Bob"] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] + in_flight_exit_bond_size = state["in_flight_exit_bond_size"] + %{address: address, exit_data: exit_data} = bob_state = state["Bob"] - output_index = 0 - input_index = 0 + output_index = 0 + input_index = 0 - receipt_hash = do_in_flight_exit(exit_game_contract_address, in_flight_exit_bond_size, address, exit_data) + receipt_hash = do_in_flight_exit(exit_game_contract_address, in_flight_exit_bond_size, address, exit_data) - # only piggyback_available for tx2 is present, tx1 is included in block and does not spawn that event - # excapt the awaited piggyback_available, invalid_piggyback and non_canonical_ife appear, b/c of the double-spend - assert all_events_in_status?(["invalid_piggyback", "non_canonical_ife", "piggyback_available"]) + # only piggyback_available for tx2 is present, tx1 is included in block and does not spawn that event + # excapt the awaited piggyback_available, invalid_piggyback and non_canonical_ife appear, b/c of the double-spend + assert all_events_in_status?(["invalid_piggyback", "non_canonical_ife", "piggyback_available"]) - # NOTE: the reason to piggyback this IFE fully is to be able to leave the system in clean and secure state, without - # any remaining `piggyback_available` events - receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) - receipt_hash_2 = piggyback_input(exit_game_contract_address, address, input_index, exit_data) + # NOTE: the reason to piggyback this IFE fully is to be able to leave the system in clean and secure state, without + # any remaining `piggyback_available` events + receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) + receipt_hash_2 = piggyback_input(exit_game_contract_address, address, input_index, exit_data) - bob_state = - Map.put( - bob_state, - :receipt_hashes, - Enum.concat([receipt_hash, receipt_hash_1, receipt_hash_2], bob_state.receipt_hashes) - ) + bob_state = + Map.put( + bob_state, + :receipt_hashes, + Enum.concat([receipt_hash, receipt_hash_1, receipt_hash_2], bob_state.receipt_hashes) + ) - entity = "Bob" + entity = "Bob" - {:ok, Map.put(state, entity, bob_state)} + {:ok, Map.put(state, entity, bob_state)} + end) end defand ~r/^Alice fully challenges Bobs most recent invalid in flight exit$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] + + %{ + address: address, + in_flight_exit_id: in_flight_exit_id, + in_flight_exit: in_flight_exit, + unsigned_txbytes: unsigned_txbytes + } = alice_state = state["Alice"] + + %{address: bob_address, unsigned_txbytes: bob_unsigned_txbytes} = state["Bob"] + + # only a single non_canonical event, since one of the IFE txs is included! + # I’m waiting for these three, and only these three to appear + # there's 2x invalid_piggyback, because the other IFE from Bob has an invalidly piggybacked input too + # SLA margin passed so there are unchallenged exit events + assert all_events_in_status?([ + "unchallenged_non_canonical_ife", + "unchallenged_piggyback", + "unchallenged_piggyback", + "invalid_piggyback", + "invalid_piggyback", + "non_canonical_ife" + ]) + + ### + # CANONICITY GAME + ### + + payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(bob_unsigned_txbytes)} + + response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_competitor, Watcher.new(), payload) + ife_competitor = IfeCompetitor.to_struct(response) + + assert ife_competitor.competing_tx_pos > 0 + assert ife_competitor.competing_proof != "" + challenge_in_flight_exit_not_canonical(exit_game_contract_address, bob_address, ife_competitor) + + # I’m waiting for only these two to remain + # there's 2x invalid_piggyback, because the other IFE from Bob has an invalidly piggybacked input too + # SLA margin passed so there are unchallenged exit events + assert all_events_in_status?([ + "unchallenged_piggyback", + "unchallenged_piggyback", + "invalid_piggyback", + "invalid_piggyback" + ]) + + ### + # PIGGYBACKS + ### + + # First input challenge + payload_0 = %InFlightExitInputChallengeDataBodySchema{txbytes: Encoding.to_hex(unsigned_txbytes), input_index: 0} + + response_0 = + pull_api_until_successful(InFlightExit, :in_flight_exit_get_input_challenge_data, Watcher.new(), payload_0) + + ife_input_challenge_0 = IfeInputChallenge.to_struct(response_0) + assert ife_input_challenge_0.in_flight_txbytes == Encoding.to_hex(unsigned_txbytes) + receipt_hash_0 = challenge_in_flight_exit_input_spent(exit_game_contract_address, address, ife_input_challenge_0) + # sanity check + [in_flight_exit_0] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + assert in_flight_exit_0.exit_map != in_flight_exit.exit_map + assert in_flight_exit_0.exit_map != 0 + + # Second input challenge + payload_1 = %InFlightExitInputChallengeDataBodySchema{ + txbytes: Encoding.to_hex(bob_unsigned_txbytes), + input_index: 0 + } - %{ - address: address, - in_flight_exit_id: in_flight_exit_id, - in_flight_exit: in_flight_exit, - unsigned_txbytes: unsigned_txbytes - } = alice_state = state["Alice"] - - %{address: bob_address, unsigned_txbytes: bob_unsigned_txbytes} = state["Bob"] - - # only a single non_canonical event, since one of the IFE txs is included! - # I’m waiting for these three, and only these three to appear - # there's 2x invalid_piggyback, because the other IFE from Bob has an invalidly piggybacked input too - # SLA margin passed so there are unchallenged exit events - assert all_events_in_status?([ - "unchallenged_non_canonical_ife", - "unchallenged_piggyback", - "unchallenged_piggyback", - "invalid_piggyback", - "invalid_piggyback", - "non_canonical_ife" - ]) - - ### - # CANONICITY GAME - ### - - payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(bob_unsigned_txbytes)} - - response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_competitor, Watcher.new(), payload) - ife_competitor = IfeCompetitor.to_struct(response) - - assert ife_competitor.competing_tx_pos > 0 - assert ife_competitor.competing_proof != "" - challenge_in_flight_exit_not_canonical(exit_game_contract_address, bob_address, ife_competitor) - - # I’m waiting for only these two to remain - # there's 2x invalid_piggyback, because the other IFE from Bob has an invalidly piggybacked input too - # SLA margin passed so there are unchallenged exit events - assert all_events_in_status?([ - "unchallenged_piggyback", - "unchallenged_piggyback", - "invalid_piggyback", - "invalid_piggyback" - ]) - - ### - # PIGGYBACKS - ### - - # First input challenge - payload_0 = %InFlightExitInputChallengeDataBodySchema{txbytes: Encoding.to_hex(unsigned_txbytes), input_index: 0} - - response_0 = - pull_api_until_successful(InFlightExit, :in_flight_exit_get_input_challenge_data, Watcher.new(), payload_0) - - ife_input_challenge_0 = IfeInputChallenge.to_struct(response_0) - assert ife_input_challenge_0.in_flight_txbytes == Encoding.to_hex(unsigned_txbytes) - receipt_hash_0 = challenge_in_flight_exit_input_spent(exit_game_contract_address, address, ife_input_challenge_0) - # sanity check - [in_flight_exit_0] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - assert in_flight_exit_0.exit_map != in_flight_exit.exit_map - assert in_flight_exit_0.exit_map != 0 - - # Second input challenge - payload_1 = %InFlightExitInputChallengeDataBodySchema{ - txbytes: Encoding.to_hex(bob_unsigned_txbytes), - input_index: 0 - } + response_1 = + pull_api_until_successful(InFlightExit, :in_flight_exit_get_input_challenge_data, Watcher.new(), payload_1) - response_1 = - pull_api_until_successful(InFlightExit, :in_flight_exit_get_input_challenge_data, Watcher.new(), payload_1) - - ife_input_challenge_1 = IfeInputChallenge.to_struct(response_1) - assert ife_input_challenge_1.in_flight_txbytes == Encoding.to_hex(bob_unsigned_txbytes) - receipt_hash_1 = challenge_in_flight_exit_input_spent(exit_game_contract_address, address, ife_input_challenge_1) - # sanity check - # leaving this with no sanity check here, to limit complexity - - # output challenge - payload_2 = %InFlightExitOutputChallengeDataBodySchema{txbytes: Encoding.to_hex(unsigned_txbytes), output_index: 1} - - response_2 = - pull_api_until_successful(InFlightExit, :in_flight_exit_get_output_challenge_data, Watcher.new(), payload_2) - - ife_output_challenge_2 = IfeOutputChallenge.to_struct(response_2) - assert ife_output_challenge_2.in_flight_txbytes == Encoding.to_hex(unsigned_txbytes) - receipt_hash_2 = challenge_in_flight_exit_output_spent(exit_game_contract_address, address, ife_output_challenge_2) - # observe the result - piggybacks are gone - [in_flight_exit_2] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - assert in_flight_exit_2.exit_map == 0 - - # observe the byzantine events gone - # I’m waiting for clean state / secure chain to remain after all the challenges - assert all_events_in_status?([]) - - alice_state = - Map.put( - alice_state, - :receipt_hashes, - Enum.concat([receipt_hash_0, receipt_hash_1, receipt_hash_2], alice_state.receipt_hashes) - ) + ife_input_challenge_1 = IfeInputChallenge.to_struct(response_1) + assert ife_input_challenge_1.in_flight_txbytes == Encoding.to_hex(bob_unsigned_txbytes) + receipt_hash_1 = challenge_in_flight_exit_input_spent(exit_game_contract_address, address, ife_input_challenge_1) + # sanity check + # leaving this with no sanity check here, to limit complexity + + # output challenge + payload_2 = %InFlightExitOutputChallengeDataBodySchema{ + txbytes: Encoding.to_hex(unsigned_txbytes), + output_index: 1 + } + + response_2 = + pull_api_until_successful(InFlightExit, :in_flight_exit_get_output_challenge_data, Watcher.new(), payload_2) + + ife_output_challenge_2 = IfeOutputChallenge.to_struct(response_2) + assert ife_output_challenge_2.in_flight_txbytes == Encoding.to_hex(unsigned_txbytes) + + receipt_hash_2 = + challenge_in_flight_exit_output_spent(exit_game_contract_address, address, ife_output_challenge_2) + + # observe the result - piggybacks are gone + [in_flight_exit_2] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + assert in_flight_exit_2.exit_map == 0 + + # observe the byzantine events gone + # I’m waiting for clean state / secure chain to remain after all the challenges + assert all_events_in_status?([]) + + alice_state = + Map.put( + alice_state, + :receipt_hashes, + Enum.concat([receipt_hash_0, receipt_hash_1, receipt_hash_2], alice_state.receipt_hashes) + ) - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end defthen ~r/^"(?[^"]+)" can processes its own most recent in flight exit$/, %{entity: entity}, state do @@ -697,49 +724,51 @@ defmodule InFlightExitsTests do defwhen ~r/^Bob sends Alice "(?[^"]+)" ETH on the child chain$/, %{amount: amount}, state do - amount = Currency.to_wei(amount) + Reorg.trigger_reorg(fn -> + amount = Currency.to_wei(amount) - %{address: alice_address} = state["Alice"] + %{address: alice_address} = state["Alice"] - %{address: bob_address, utxos: bob_utxos, pkey: bob_pkey, child_chain_balance: bob_child_chain_balance} = - state["Bob"] + %{address: bob_address, utxos: bob_utxos, pkey: bob_pkey, child_chain_balance: bob_child_chain_balance} = + state["Bob"] - # inputs - bob_deposit_utxo = hd(bob_utxos) + # inputs + bob_deposit_utxo = hd(bob_utxos) - bob_input = %ExPlasma.Utxo{ - blknum: bob_deposit_utxo["blknum"], - currency: Currency.ether(), - oindex: 0, - txindex: 0, - output_type: 1, - owner: bob_address - } + bob_input = %ExPlasma.Utxo{ + blknum: bob_deposit_utxo["blknum"], + currency: Currency.ether(), + oindex: 0, + txindex: 0, + output_type: 1, + owner: bob_address + } - alice_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: alice_address, - amount: amount - } + alice_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: alice_address, + amount: amount + } - bob_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: bob_address, - amount: bob_child_chain_balance - amount - state["fee"] - } + bob_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: bob_address, + amount: bob_child_chain_balance - amount - state["fee"] + } - transaction = %Payment{inputs: [bob_input], outputs: [alice_output, bob_output]} + transaction = %Payment{inputs: [bob_input], outputs: [alice_output, bob_output]} - submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [bob_pkey] - ) + submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [bob_pkey] + ) - txbytes = ExPlasma.Transaction.encode(submitted_tx) + txbytes = ExPlasma.Transaction.encode(submitted_tx) - _submit_transaction_response = send_transaction(txbytes) + _submit_transaction_response = send_transaction(txbytes) - {:ok, state} + {:ok, state} + end) end defthen ~r/^"(?[^"]+)" should have "(?[^"]+)" ETH on the child chain after a successful transaction$/, @@ -765,183 +794,199 @@ defmodule InFlightExitsTests do defgiven ~r/^Alice creates a transaction spending her recently received input to Bob$/, _, state do - %{utxos: alice_utxos, pkey: alice_pkey} = alice_state = state["Alice"] + Reorg.trigger_reorg(fn -> + %{utxos: alice_utxos, pkey: alice_pkey} = alice_state = state["Alice"] - amount = Currency.to_wei(5) + amount = Currency.to_wei(5) - %{address: bob_address} = state["Bob"] + %{address: bob_address} = state["Bob"] - double_spent_utxo = alice_utxos |> Enum.reverse() |> Enum.at(0) + double_spent_utxo = alice_utxos |> Enum.reverse() |> Enum.at(0) - assert double_spent_utxo["amount"] == amount + assert double_spent_utxo["amount"] == amount - alice_deposit_input = %ExPlasma.Utxo{ - blknum: double_spent_utxo["blknum"], - currency: double_spent_utxo["currency"], - oindex: double_spent_utxo["oindex"], - txindex: double_spent_utxo["txindex"], - output_type: double_spent_utxo["otype"], - owner: double_spent_utxo["owner"] - } + alice_deposit_input = %ExPlasma.Utxo{ + blknum: double_spent_utxo["blknum"], + currency: double_spent_utxo["currency"], + oindex: double_spent_utxo["oindex"], + txindex: double_spent_utxo["txindex"], + output_type: double_spent_utxo["otype"], + owner: double_spent_utxo["owner"] + } - bob_output = %ExPlasma.Utxo{ - currency: Currency.ether(), - owner: bob_address, - amount: amount - state["fee"] - } + bob_output = %ExPlasma.Utxo{ + currency: Currency.ether(), + owner: bob_address, + amount: amount - state["fee"] + } - transaction = %Payment{inputs: [alice_deposit_input], outputs: [bob_output]} + transaction = %Payment{inputs: [alice_deposit_input], outputs: [bob_output]} - submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [alice_pkey] - ) + submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [alice_pkey] + ) - txbytes = ExPlasma.Transaction.encode(submitted_tx) + txbytes = ExPlasma.Transaction.encode(submitted_tx) - ## we need to duplicate the transaction because we need an unsigned one later! - unsigned_submitted_tx = - ExPlasma.Transaction.sign(transaction, - keys: [] - ) + ## we need to duplicate the transaction because we need an unsigned one later! + unsigned_submitted_tx = + ExPlasma.Transaction.sign(transaction, + keys: [] + ) - unsigned_txbytes = ExPlasma.Transaction.encode(unsigned_submitted_tx) + unsigned_txbytes = ExPlasma.Transaction.encode(unsigned_submitted_tx) - alice_state = - alice_state - |> Map.put(:submitted_tx, submitted_tx) - |> Map.put(:txbytes, txbytes) - |> Map.put(:unsigned_submitted_tx, unsigned_submitted_tx) - |> Map.put(:unsigned_txbytes, unsigned_txbytes) + alice_state = + alice_state + |> Map.put(:submitted_tx, submitted_tx) + |> Map.put(:txbytes, txbytes) + |> Map.put(:unsigned_submitted_tx, unsigned_submitted_tx) + |> Map.put(:unsigned_txbytes, unsigned_txbytes) - entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + entity = "Alice" + {:ok, Map.put(state, entity, alice_state)} + end) end defwhen ~r/^Alice starts a standard exit on the child chain from her recently received input from Bob$/, _, state do - %{utxos: alice_utxos, address: alice_address} = state["Alice"] - utxo = alice_utxos |> Enum.reverse() |> Enum.at(0) + Reorg.trigger_reorg(fn -> + %{utxos: alice_utxos, address: alice_address} = state["Alice"] + utxo = alice_utxos |> Enum.reverse() |> Enum.at(0) - assert utxo["amount"] == Currency.to_wei(5) + assert utxo["amount"] == Currency.to_wei(5) - standard_exit_client = %StandardExitClient{address: alice_address, utxo: Utxo.to_struct(utxo)} - StandardExitClient.start_standard_exit(standard_exit_client) + standard_exit_client = %StandardExitClient{address: alice_address, utxo: Utxo.to_struct(utxo)} + StandardExitClient.start_standard_exit(standard_exit_client) - {:ok, state} + {:ok, state} + end) end defand ~r/^Bob starts an in flight exit from the most recently created transaction$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] - in_flight_exit_bond_size = state["in_flight_exit_bond_size"] - %{txbytes: txbytes} = state["Alice"] - %{address: bob_address} = bob_state = state["Bob"] - payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(txbytes)} - response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_data, Watcher.new(), payload) - exit_data = IfeExitData.to_struct(response) - receipt_hash = do_in_flight_exit(exit_game_contract_address, in_flight_exit_bond_size, bob_address, exit_data) - - bob_state = - bob_state - |> Map.put(:exit_data, exit_data) - |> Map.put(:receipt_hashes, [receipt_hash | bob_state.receipt_hashes]) - - entity = "Bob" - {:ok, Map.put(state, entity, bob_state)} + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] + in_flight_exit_bond_size = state["in_flight_exit_bond_size"] + %{txbytes: txbytes} = state["Alice"] + %{address: bob_address} = bob_state = state["Bob"] + payload = %InFlightExitTxBytesBodySchema{txbytes: Encoding.to_hex(txbytes)} + response = pull_api_until_successful(InFlightExit, :in_flight_exit_get_data, Watcher.new(), payload) + exit_data = IfeExitData.to_struct(response) + receipt_hash = do_in_flight_exit(exit_game_contract_address, in_flight_exit_bond_size, bob_address, exit_data) + + bob_state = + bob_state + |> Map.put(:exit_data, exit_data) + |> Map.put(:receipt_hashes, [receipt_hash | bob_state.receipt_hashes]) + + entity = "Bob" + {:ok, Map.put(state, entity, bob_state)} + end) end defgiven ~r/^Bob piggybacks outputs from his most recent in flight exit$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] - %{exit_data: exit_data, in_flight_exit_id: in_flight_exit_id, address: address} = bob_state = state["Bob"] + %{exit_data: exit_data, in_flight_exit_id: in_flight_exit_id, address: address} = bob_state = state["Bob"] - output_index = 0 + output_index = 0 - receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) + receipt_hash_1 = piggyback_output(exit_game_contract_address, address, output_index, exit_data) - bob_state = - Map.put( - bob_state, - :receipt_hashes, - Enum.concat([receipt_hash_1], bob_state.receipt_hashes) - ) + bob_state = + Map.put( + bob_state, + :receipt_hashes, + Enum.concat([receipt_hash_1], bob_state.receipt_hashes) + ) - [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - # bits is flagged when output is piggybacked - assert in_flight_exit.exit_map != 0 - entity = "Bob" + [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + # bits is flagged when output is piggybacked + assert in_flight_exit.exit_map != 0 + entity = "Bob" - {:ok, Map.put(state, entity, bob_state)} + {:ok, Map.put(state, entity, bob_state)} + end) end defwhen ~r/^Bob fully challenges Alices most recent invalid exit$/, _, state do - assert all_events_in_status?(["invalid_exit"]) + Reorg.trigger_reorg(fn -> + assert all_events_in_status?(["invalid_exit"]) - %{exit_data: %{input_utxos_pos: [utxo_pos | _]}, address: address} = state["Bob"] + %{exit_data: %{input_utxos_pos: [utxo_pos | _]}, address: address} = state["Bob"] - StandardExitChallengeClient.challenge_standard_exit(utxo_pos, address) + StandardExitChallengeClient.challenge_standard_exit(utxo_pos, address) - {:ok, state} + {:ok, state} + end) end defwhen ~r/^Alice piggybacks inputs from Bobs most recent in flight exit$/, _, state do - exit_game_contract_address = state["exit_game_contract_address"] + Reorg.trigger_reorg(fn -> + exit_game_contract_address = state["exit_game_contract_address"] - %{exit_data: exit_data, in_flight_exit_id: in_flight_exit_id} = state["Bob"] - %{address: address} = alice_state = state["Alice"] + %{exit_data: exit_data, in_flight_exit_id: in_flight_exit_id} = state["Bob"] + %{address: address} = alice_state = state["Alice"] - input_index = 0 + input_index = 0 - receipt_hash_1 = piggyback_input(exit_game_contract_address, address, input_index, exit_data) + receipt_hash_1 = piggyback_input(exit_game_contract_address, address, input_index, exit_data) - alice_state = - Map.put( - alice_state, - :receipt_hashes, - Enum.concat([receipt_hash_1], alice_state.receipt_hashes) - ) + alice_state = + Map.put( + alice_state, + :receipt_hashes, + Enum.concat([receipt_hash_1], alice_state.receipt_hashes) + ) - [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) - # bits is flagged when input is piggybacked - assert in_flight_exit.exit_map != 0 - entity = "Alice" + [in_flight_exit] = get_in_flight_exits(exit_game_contract_address, in_flight_exit_id) + # bits is flagged when input is piggybacked + assert in_flight_exit.exit_map != 0 + entity = "Alice" - {:ok, Map.put(state, entity, alice_state)} + {:ok, Map.put(state, entity, alice_state)} + end) end # And "Alice" in flight transaction inputs are not spendable after exit finalization defand ~r/^"(?[^"]+)" in flight transaction inputs are not spendable any more$/, %{entity: entity}, state do - %{address: address, in_flight_exit: in_flight_exit} = state[entity] + Reorg.trigger_reorg(fn -> + %{address: address, in_flight_exit: in_flight_exit} = state[entity] - assert Itest.Poller.utxo_absent?(address, in_flight_exit.position) - assert Itest.Poller.exitable_utxo_absent?(address, in_flight_exit.position) + assert Itest.Poller.utxo_absent?(address, in_flight_exit.position) + assert Itest.Poller.exitable_utxo_absent?(address, in_flight_exit.position) - {:ok, state} + {:ok, state} + end) end defand ~r/^"(?[^"]+)" in flight transaction most recently piggybacked output is not spendable any more$/, %{entity: entity}, state do - %{address: address, transaction_submit: submit_response, child_chain_balance: balance} = state[entity] - piggybacked_output_index = 0 - %SubmitTransactionResponse{blknum: output_blknum, txindex: output_txindex} = submit_response - - pull_balance_until_amount(address, balance - Currency.to_wei(5) - state["fee"]) - - {:ok, %{"data" => utxos}} = Client.get_utxos(%{address: address}) - - assert nil == - Enum.find( - utxos, - fn %{"blknum" => blknum, "txindex" => txindex, "oindex" => oindex} -> - blknum == output_blknum and txindex == output_txindex and oindex == piggybacked_output_index - end - ) + Reorg.trigger_reorg(fn -> + %{address: address, transaction_submit: submit_response, child_chain_balance: balance} = state[entity] + piggybacked_output_index = 0 + %SubmitTransactionResponse{blknum: output_blknum, txindex: output_txindex} = submit_response + + pull_balance_until_amount(address, balance - Currency.to_wei(5) - state["fee"]) + + {:ok, %{"data" => utxos}} = Client.get_utxos(%{address: address}) + + assert nil == + Enum.find( + utxos, + fn %{"blknum" => blknum, "txindex" => txindex, "oindex" => oindex} -> + blknum == output_blknum and txindex == output_txindex and oindex == piggybacked_output_index + end + ) + end) end ############################################################################################### @@ -1015,6 +1060,9 @@ defmodule InFlightExitsTests do "" -> :queue_not_added + 0 -> + 0 + result -> next_exit_id = hd(ABI.TypeDecoder.decode(result, [{:uint, 256}])) next_exit_id &&& (1 <<< 160) - 1