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

cabbage test for payment v2 #1578

Merged
merged 6 commits into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions apps/omg/lib/omg/state/transaction/payment/builder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright 2019-2020 OmiseGO Pte Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule OMG.State.Transaction.Payment.Builder do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if different transaction types will have enough shared behaviours to share the same builder? E.g. same number of inputs and outputs, expect the same parameters, etc.

I'm thinking, if not, it might be better to share just the interface (i.e. function signatures) and not the builder implementation (e.g. implying that v1 and v2's inputs can be mapped with Utxo.position/3 and outputs with %Output{})? Otherwise this will be the scary module that everyone's afraid to edit.

Copy link
Contributor Author

@boolafish boolafish Jun 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the ideal state (of my personal thinking), it will be best if each tx type has its own module. This PR is reusing Transaction.Payment that leads to this awkward need of this builder. If each tx type has its own module then the module would be responsible to have its own function like this new and can be interface or behavior-ized. Two payment module can be refactored to share the code between themselves but that is out of the concern of the interface. And new non payment tx type will have nice interface to follow (if everything goes smooth, I know from experience interface breaks often too 😅).

So...I did spend a bit time on having PaymentV2 module. I think it breaks a bit on child chain and breaks quite a lot on watcher-info. The code was not designed for ALD (which makes sense) so nothing is dispatching via tx type at this moment.

As the refactor seems a bit too huge, I want to have test covering first to give us faster feedback loop and some confidence. Also, want to focus on the POC now and try to figure a list of things to refactor on master branch to make it ALD friendly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aight sounds good! Thanks for the detailed info!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I assume this as an approval of the PR :p

@moduledoc """
Module to build payment tx of different tx types. (eg. v1 and v2)
"""

alias OMG.Crypto
alias OMG.Output
alias OMG.State.Transaction
alias OMG.State.Transaction.Payment
alias OMG.Utxo

require Transaction
require Utxo

@max_inputs 4
@max_outputs 4
@zero_metadata <<0::256>>

@payment_tx_type OMG.WireFormatTypes.tx_type_for(:tx_payment_v1)
@payment_output_type OMG.WireFormatTypes.output_type_for(:output_payment_v1)

@payment_v2_tx_type OMG.WireFormatTypes.tx_type_for(:tx_payment_v2)
@payment_v2_output_type OMG.WireFormatTypes.output_type_for(:output_payment_v2)

@output_types %{
@payment_tx_type => @payment_output_type,
@payment_v2_tx_type => @payment_v2_output_type
}

@doc """
Creates a new raw transaction structure from a list of inputs and a list of outputs, given in a succinct tuple form.

assumptions:
```
length(inputs) <= @max_inputs
length(outputs) <= @max_outputs
```
"""
@spec new_payment(
pos_integer,
list({pos_integer, pos_integer, 0..unquote(@max_outputs - 1)}),
list({Crypto.address_t(), Payment.currency(), pos_integer}),
Transaction.metadata()
) :: Payment.t()
def new_payment(tx_type, inputs, outputs, metadata \\ @zero_metadata)
when Transaction.is_metadata(metadata) and length(inputs) <= @max_inputs and length(outputs) <= @max_outputs do
inputs = Enum.map(inputs, &new_input/1)
outputs = Enum.map(outputs, fn output -> new_output(output, @output_types[tx_type]) end)
%Transaction.Payment{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata}
end

# `new_input/1` and `new_output/1` are here to just help interpret the short-hand form of inputs outputs when doing
# `new/3`
defp new_input({blknum, txindex, oindex}), do: Utxo.position(blknum, txindex, oindex)

defp new_output({owner, currency, amount}, output_type) do
%Output{
owner: owner,
currency: currency,
amount: amount,
output_type: output_type
}
end
end
17 changes: 4 additions & 13 deletions apps/omg/lib/omg/wire_format_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,20 @@ defmodule OMG.WireFormatTypes do
3 => OMG.State.Transaction.Fee
}

@module_tx_types %{
OMG.State.Transaction.Payment => 1,
OMG.State.Transaction.Fee => 3
}

@input_pointer_type_values %{
input_pointer_utxo_position: 1
}

@output_type_values %{
output_payment_v1: 1,
output_fee_token_claim: 2
output_fee_token_claim: 2,
output_payment_v2: 3
}

@output_type_modules %{
1 => OMG.Output,
2 => OMG.Output
2 => OMG.Output,
3 => OMG.Output
}

@exit_game_tx_types [
Expand All @@ -71,12 +68,6 @@ defmodule OMG.WireFormatTypes do
@spec tx_type_modules() :: tx_type_to_module_map()
def tx_type_modules(), do: @tx_type_modules

@doc """
Returns the tx type that is associated with the given module
"""
@spec module_tx_types() :: %{atom() => non_neg_integer()}
def module_tx_types(), do: @module_tx_types

@doc """
Returns wire format type value of known input pointer type
"""
Expand Down
9 changes: 5 additions & 4 deletions apps/omg_watcher_info/lib/omg_watcher_info/api/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ defmodule OMG.WatcherInfo.API.Transaction do
%{advice | transactions: Enum.map(txs, fn tx -> Map.put_new(tx, :typed_data, add_type_specs(tx)) end)}
}

defp add_type_specs(%{inputs: inputs, outputs: outputs, metadata: metadata}) do
defp add_type_specs(tx) do
alias OMG.TypedDataHash

message =
[
create_inputs(inputs),
create_outputs(outputs),
[metadata: metadata || @empty_metadata]
create_inputs(tx.inputs),
create_outputs(tx.outputs),
[metadata: tx.metadata || @empty_metadata],
[tx_type: tx.tx_type]
]
|> Enum.concat()
|> Map.new()
Expand Down
34 changes: 24 additions & 10 deletions apps/omg_watcher_info/lib/omg_watcher_info/utxo_selection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defmodule OMG.WatcherInfo.UtxoSelection do
alias OMG.TypedDataHash
alias OMG.WatcherInfo.DB

require Logger
require Transaction
require Transaction.Payment

Expand All @@ -37,6 +38,7 @@ defmodule OMG.WatcherInfo.UtxoSelection do
}

@type order_t() :: %{
tx_type: non_neg_integer(),
owner: Crypto.address_t(),
payments: nonempty_list(payment_t()),
fee: fee_t(),
Expand Down Expand Up @@ -71,7 +73,8 @@ defmodule OMG.WatcherInfo.UtxoSelection do
TODO: seems unocovered by any tests
"""
@spec create_advice(%{Transaction.Payment.currency() => list(%DB.TxOutput{})}, order_t()) :: advice_t()
def create_advice(utxos, %{owner: owner, payments: payments, fee: fee} = order) do
def create_advice(utxos, order) do
%{tx_type: tx_type, owner: owner, payments: payments, fee: fee} = order
needed_funds = needed_funds(payments, fee)
token_utxo_selection = select_utxo(utxos, needed_funds)

Expand All @@ -83,7 +86,7 @@ defmodule OMG.WatcherInfo.UtxoSelection do

if utxo_count <= Transaction.Payment.max_inputs(),
do: create_transaction(funds, order) |> respond(:complete),
else: create_merge(owner, funds) |> respond(:intermediate)
else: create_merge(tx_type, owner, funds) |> respond(:intermediate)
end
end

Expand Down Expand Up @@ -139,7 +142,9 @@ defmodule OMG.WatcherInfo.UtxoSelection do
else: {:error, {:insufficient_funds, missing_funds}}
end

defp create_transaction(utxos_per_token, %{owner: owner, payments: payments, metadata: metadata, fee: fee}) do
defp create_transaction(utxos_per_token, order) do
%{tx_type: tx_type, owner: owner, payments: payments, metadata: metadata, fee: fee} = order

rests =
utxos_per_token
|> Stream.map(fn {token, utxos} ->
Expand All @@ -165,10 +170,11 @@ defmodule OMG.WatcherInfo.UtxoSelection do
{:error, :empty_transaction}

true ->
raw_tx = create_raw_transaction(inputs, outputs, metadata)
raw_tx = create_raw_transaction(tx_type, inputs, outputs, metadata)

{:ok,
%{
tx_type: tx_type,
inputs: inputs,
outputs: outputs,
fee: fee,
Expand All @@ -179,7 +185,7 @@ defmodule OMG.WatcherInfo.UtxoSelection do
end
end

defp create_merge(owner, utxos_per_token) do
defp create_merge(tx_type, owner, utxos_per_token) do
utxos_per_token
|> Enum.map(fn {token, utxos} ->
Stream.chunk_every(utxos, Transaction.Payment.max_outputs())
Expand All @@ -190,6 +196,7 @@ defmodule OMG.WatcherInfo.UtxoSelection do

inputs ->
create_transaction([{token, inputs}], %{
tx_type: tx_type,
fee: %{amount: 0, currency: token},
metadata: @empty_metadata,
owner: owner,
Expand All @@ -201,15 +208,22 @@ defmodule OMG.WatcherInfo.UtxoSelection do
|> Enum.map(fn {:ok, tx} -> tx end)
end

defp create_raw_transaction(inputs, outputs, metadata) do
if Enum.any?(outputs, &(&1.owner == nil)),
do: nil,
else:
Transaction.Payment.new(
defp create_raw_transaction(tx_type, inputs, outputs, metadata) do
# Somehow the API is designed to return nil txbytes instead of claiming that as bad request :(
# So we need to return nil here
# See the test: "test /transaction.create does not return txbytes when spend owner is not provided"
case Enum.any?(outputs, &(&1.owner == nil)) do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn...I used to be on the side of using if and now.....

true ->
nil

false ->
Transaction.Payment.Builder.new_payment(
tx_type,
inputs |> Enum.map(&{&1.blknum, &1.txindex, &1.oindex}),
outputs |> Enum.map(&{&1.owner, &1.currency, &1.amount}),
metadata || @empty_metadata
)
end
end

defp create_txbytes(tx) do
Expand Down
8 changes: 8 additions & 0 deletions apps/omg_watcher_rpc/lib/web/validators/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ defmodule OMG.WatcherRPC.Web.Validator.Order do
alias OMG.State.Transaction
alias OMG.Utils.HttpRPC.Validator.Base
alias OMG.WatcherInfo.UtxoSelection
alias OMG.WireFormatTypes

@default_tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)

@doc """
Parses and validates request body
Expand All @@ -35,9 +38,14 @@ defmodule OMG.WatcherRPC.Web.Validator.Order do
{:ok, fee} <- expect(params, "fee", map: &parse_fee/1),
{:ok, payments} <- expect(params, "payments", list: &parse_payment/1),
{:ok, payments} <- fills_in_outputs?(payments),
{:ok, tx_type} = expect(params, "tx_type", [:integer, :optional]),
:ok <- ensure_not_self_transaction(owner, payments) do
# keep this optional args for backward competibility
tx_type = tx_type || @default_tx_type

{:ok,
%{
tx_type: tx_type,
owner: owner,
payments: payments,
fee: fee,
Expand Down
9 changes: 8 additions & 1 deletion apps/omg_watcher_rpc/lib/web/validators/typed_data_signed.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ defmodule OMG.WatcherRPC.Web.Validator.TypedDataSigned do
alias OMG.State.Transaction
alias OMG.TypedDataHash.Tools
alias OMG.Utils.HttpRPC.Validator.Base
alias OMG.WireFormatTypes

import OMG.Utils.HttpRPC.Validator.Base

@default_tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)

@doc """
Parses and validates request body for /transaction.submit_typed`
"""
Expand All @@ -38,10 +42,13 @@ defmodule OMG.WatcherRPC.Web.Validator.TypedDataSigned do
@spec parse_transaction(map()) :: {:ok, Transaction.Payment.t()} | {:error, any}
def parse_transaction(params) do
with {:ok, msg} <- expect(params, "message", :map),
{:ok, tx_type} <- expect(msg, "tx_type", [:integer, :optional]),
inputs when is_list(inputs) <- parse_inputs(msg),
outputs when is_list(outputs) <- parse_outputs(msg),
{:ok, metadata} <- expect(msg, "metadata", :hash) do
{:ok, Transaction.Payment.new(inputs, outputs, metadata)}
# keep this optional args for backward competibility
tx_type = tx_type || @default_tx_type
{:ok, Transaction.Payment.Builder.new_payment(tx_type, inputs, outputs, metadata)}
end
end

Expand Down
3 changes: 3 additions & 0 deletions apps/omg_watcher_rpc/priv/swagger/info_api_specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,9 @@ paths:
title: CreateTransactionsBodySchema
type: object
properties:
tx_type:
type: integer
format: int256
owner:
type: string
payments:
Expand Down
10 changes: 9 additions & 1 deletion priv/cabbage/apps/itest/lib/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmodule Itest.Client do
require Logger

@gas 180_000
@payment_v1_tx_type 1

def deposit(amount_in_wei, output_address, vault_address, currency \\ Currency.ether()) do
deposit_transaction = deposit_transaction(amount_in_wei, output_address, currency)
Expand All @@ -52,8 +53,15 @@ defmodule Itest.Client do
{:ok, receipt_hash}
end

def create_transaction(amount_in_wei, input_address, output_address, currency \\ Currency.ether()) do
def create_transaction(
amount_in_wei,
input_address,
output_address,
currency \\ Currency.ether(),
tx_type \\ @payment_v1_tx_type
) do
transaction = %CreateTransactionsBodySchema{
tx_type: tx_type,
owner: input_address,
payments: [
%TransactionCreatePayments{
Expand Down
9 changes: 9 additions & 0 deletions priv/cabbage/apps/itest/test/features/payment_v2.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Feature: Payment V2 transaction

Scenario: Alice sends Bob ETH with payment v1 and v2
When Alice deposits "4" ETH to the root chain
Then Alice should have "4" ETH on the child chain
When Alice sends Bob "1" ETH on the child chain with payment v1
Then Bob should have "1" ETH on the child chain
When Alice sends Bob "2" ETH on the child chain with payment v2
Then Bob should have "3" ETH on the child chain
Loading