Merge pull request #1094 from poanetwork/1069

Extract input from Parity call traces
pull/1096/head
Andrew Cravenho 6 years ago committed by GitHub
commit 58f847516d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  2. 2
      apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs
  3. 78
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex
  4. 91
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  5. 2
      apps/explorer/mix.exs
  6. 15
      apps/explorer/priv/repo/migrations/20181108205650_additional_internal_transaction_constraints.exs
  7. 51
      apps/explorer/test/explorer/chain/import_test.exs
  8. 11
      apps/explorer/test/explorer/chain_test.exs
  9. 3
      apps/explorer/test/support/factory.ex

@ -103,7 +103,7 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
"type" => "JSON"
})
assert expected_next_page_url = actual_next_page_url
assert expected_next_page_url == actual_next_page_url
end
test "next_page_params are empty if on last page", %{conn: conn} do

@ -109,7 +109,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do
assert html_response(conn, 404)
end
test "responds with 422 when the hash is invalid" do
test "responds with 422 when the hash is invalid", %{conn: conn} do
conn = get(conn, transaction_path(BlockScoutWeb.Endpoint, :show, "wrong"))
assert html_response(conn, 422)

@ -89,38 +89,39 @@ defmodule EthereumJSONRPC.Parity.Trace do
...> "action" => %{
...> "callType" => "call",
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> "to" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> "gas" => 4677320,
...> "input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
...> "to" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> "value" => 0
...> },
...> "blockNumber" => 35,
...> "transactionIndex" => 0,
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> "index" => 0,
...> "traceAddress" => [],
...> "type" => "call",
...> "result" => %{
...> "gasUsed" => 27770,
...> "output" => "0x"
...> },
...> "subtraces" => 0,
...> "traceAddress" => [],
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> "type" => "call",
...> "transactionIndex" => 0
...> "subtraces" => 0
...> }
...> )
%{
block_number: 35,
transaction_index: 0,
transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
index: 0,
trace_address: [],
type: "call",
call_type: "call",
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
gas: 4677320,
gas_used: 27770,
index: 0,
input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
output: "0x",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
trace_address: [],
transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
type: "call",
value: 0,
transaction_index: 0
value: 0
}
Calls can error and be reverted
@ -130,34 +131,35 @@ defmodule EthereumJSONRPC.Parity.Trace do
...> "action" => %{
...> "callType" => "call",
...> "from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
...> "to" => "0xfdca0da4158740a93693441b35809b5bb463e527",
...> "gas" => 7578728,
...> "input" => "0xa6f2ae3a",
...> "to" => "0xfdca0da4158740a93693441b35809b5bb463e527",
...> "value" => 10000000000000000
...> },
...> "blockNumber" => 35,
...> "error" => "Reverted",
...> "transactionIndex" => 0,
...> "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> "index" => 0,
...> "subtraces" => 7,
...> "traceAddress" => [],
...> "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> "type" => "call",
...> "transactionIndex" => 0
...> "error" => "Reverted",
...> "subtraces" => 7,
...> }
...> )
%{
block_number: 35,
call_type: "call",
error: "Reverted",
from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
gas: 7578728,
transaction_index: 0,
transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
index: 0,
to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
trace_address: [],
transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
type: "call",
value: 10000000000000000,
transaction_index: 0
call_type: "call",
from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
input: "0xa6f2ae3a",
error: "Reverted",
gas: 7578728,
value: 10000000000000000
}
Self-destruct transfer a `"balance"` from `"address"` to `"refundAddress"`. These self-destruct-unique fields can be
@ -204,30 +206,32 @@ defmodule EthereumJSONRPC.Parity.Trace do
%{
"action" => %{
"callType" => call_type,
"to" => to_address_hash,
"from" => from_address_hash,
"input" => input,
"gas" => gas,
"to" => to_address_hash,
"value" => value
},
"blockNumber" => block_number,
"index" => index,
"traceAddress" => trace_address,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash,
"transactionIndex" => transaction_index
"index" => index,
"traceAddress" => trace_address
} = elixir
%{
block_number: block_number,
call_type: call_type,
from_address_hash: from_address_hash,
gas: gas,
transaction_hash: transaction_hash,
transaction_index: transaction_index,
index: index,
to_address_hash: to_address_hash,
trace_address: trace_address,
transaction_hash: transaction_hash,
type: type,
value: value,
transaction_index: transaction_index
call_type: call_type,
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
gas: gas,
input: input,
value: value
}
|> put_call_error_or_result(elixir)
end

@ -30,6 +30,7 @@ defmodule Explorer.Chain.InternalTransaction do
"""
@type t :: %__MODULE__{
block_number: Explorer.Chain.Block.block_number() | nil,
type: Type.t(),
call_type: CallType.t() | nil,
created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
created_contract_address_hash: Hash.t() | nil,
@ -37,11 +38,11 @@ defmodule Explorer.Chain.InternalTransaction do
error: String.t(),
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_hash: Hash.Address.t(),
gas: Gas.t(),
gas: Gas.t() | nil,
gas_used: Gas.t() | nil,
index: non_neg_integer(),
init: Data.t() | nil,
input: Data.t(),
input: Data.t() | nil,
output: Data.t() | nil,
to_address: %Ecto.Association.NotLoaded{} | Address.t() | nil,
to_address_hash: Hash.Address.t() | nil,
@ -49,7 +50,6 @@ defmodule Explorer.Chain.InternalTransaction do
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.t(),
transaction_index: Transaction.transaction_index() | nil,
type: Type.t(),
value: Wei.t()
}
@ -181,19 +181,20 @@ defmodule Explorer.Chain.InternalTransaction do
iex> changeset = Explorer.Chain.InternalTransaction.changeset(
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> block_number: 35,
...> transaction_index: 0,
...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0,
...> trace_address: [],
...> call_type: "call",
...> type: "call",
...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> gas: 4677320,
...> gas_used: 27770,
...> index: 0,
...> input: "0x",
...> output: "0x",
...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> trace_address: [],
...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> type: "call",
...> value: 0,
...> block_number: 35,
...> transaction_index: 0
...> }
...> )
iex> changeset.valid?
@ -204,43 +205,45 @@ defmodule Explorer.Chain.InternalTransaction do
iex> changeset = Explorer.Chain.InternalTransaction.changeset(
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> call_type: "call",
...> error: "Reverted",
...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
...> gas: 7578728,
...> block_number: 35,
...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0,
...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
...> trace_address: [],
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> type: "call",
...> value: 10000000000000000,
...> block_number: 35,
...> transaction_index: 0
...> call_type: "call",
...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
...> gas: 7578728,
...> input: "0x",
...> error: "Reverted",
...> value: 10000000000000000
...> }
...> )
iex> changeset.valid?
true
Failed `:call`s are not allowed to set `gas_used` or `output` because they are part of the successful `result` object
in the Parity JSONRPC response.
in the Parity JSONRPC response. They still need `input`, however.
iex> changeset = Explorer.Chain.InternalTransaction.changeset(
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> block_number: 35,
...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0,
...> trace_address: [],
...> type: "call",
...> call_type: "call",
...> error: "Reverted",
...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
...> gas: 7578728,
...> gas_used: 7578727,
...> index: 0,
...> input: "0x",
...> output: "0x",
...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
...> trace_address: [],
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> type: "call",
...> value: 10000000000000000,
...> block_number: 35,
...> transaction_index: 0
...> error: "Reverted",
...> value: 10000000000000000
...> }
...> )
iex> changeset.valid?
@ -251,22 +254,23 @@ defmodule Explorer.Chain.InternalTransaction do
gas_used: {"can't be present for failed call", []}
]
Likewise, successful `:call`s require `gas_used` and `output` to be set.
Likewise, successful `:call`s require `input`, `gas_used` and `output` to be set.
iex> changeset = Explorer.Chain.InternalTransaction.changeset(
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> call_type: "call",
...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
...> gas: 7578728,
...> block_number: 35,
...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0,
...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
...> trace_address: [],
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> type: "call",
...> value: 10000000000000000,
...> block_number: 35,
...> transaction_index: 0
...> call_type: "call",
...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32",
...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527",
...> input: "0x",
...> gas: 7578728,
...> value: 10000000000000000
...> }
...> )
iex> changeset.valid?
@ -367,8 +371,8 @@ defmodule Explorer.Chain.InternalTransaction do
type_changeset(changeset, attrs, type)
end
@call_optional_fields ~w(error gas_used output block_number transaction_index)
@call_required_fields ~w(call_type from_address_hash gas index to_address_hash trace_address transaction_hash value)a
@call_optional_fields ~w(error gas_used output block_number transaction_index)a
@call_required_fields ~w(call_type from_address_hash gas index input to_address_hash trace_address transaction_hash value)a
@call_allowed_fields @call_optional_fields ++ @call_required_fields
defp type_changeset(changeset, attrs, :call) do
@ -376,13 +380,15 @@ defmodule Explorer.Chain.InternalTransaction do
|> cast(attrs, @call_allowed_fields)
|> validate_required(@call_required_fields)
|> validate_call_error_or_result()
|> check_constraint(:call_type, message: ~S|can't be blank when type is 'call'|, name: :call_has_call_type)
|> check_constraint(:input, message: ~S|can't be blank when type is 'call'|, name: :call_has_call_type)
|> foreign_key_constraint(:from_address_hash)
|> foreign_key_constraint(:to_address_hash)
|> foreign_key_constraint(:transaction_hash)
|> unique_constraint(:index)
end
@create_optional_fields ~w(error created_contract_code created_contract_address_hash gas_used block_number transaction_index)
@create_optional_fields ~w(error created_contract_code created_contract_address_hash gas_used block_number transaction_index)a
@create_required_fields ~w(from_address_hash gas index init trace_address transaction_hash value)a
@create_allowed_fields @create_optional_fields ++ @create_required_fields
@ -391,13 +397,14 @@ defmodule Explorer.Chain.InternalTransaction do
|> cast(attrs, @create_allowed_fields)
|> validate_required(@create_required_fields)
|> validate_create_error_or_result()
|> check_constraint(:init, message: ~S|can't be blank when type is 'create'|, name: :create_has_init)
|> foreign_key_constraint(:created_contract_address_hash)
|> foreign_key_constraint(:from_address_hash)
|> foreign_key_constraint(:transaction_hash)
|> unique_constraint(:index)
end
@selfdestruct_optional_fields ~w(block_number transaction_index)
@selfdestruct_optional_fields ~w(block_number transaction_index)a
@selfdestruct_required_fields ~w(from_address_hash index to_address_hash trace_address transaction_hash type value)a
@selfdestruct_allowed_fields @selfdestruct_optional_fields ++ @selfdestruct_required_fields

@ -67,6 +67,8 @@ defmodule Explorer.Mixfile do
{:bypass, "~> 0.8", only: :test},
{:comeonin, "~> 4.0"},
{:credo, "0.10.2", only: [:dev, :test], runtime: false},
# For Absinthe to load data in batches
{:dataloader, "~> 1.0.0"},
{:decimal, "~> 1.0"},
{:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false},
# Casting Ethereum-native types to Elixir-native types

@ -0,0 +1,15 @@
defmodule Explorer.Repo.Migrations.AdditionalInternalTransactionConstraints do
use Ecto.Migration
def up do
create(constraint(:internal_transactions, :call_has_call_type, check: "type != 'call' OR call_type IS NOT NULL"))
create(constraint(:internal_transactions, :call_has_input, check: "type != 'call' OR input IS NOT NULL"))
create(constraint(:internal_transactions, :create_has_init, check: "type != 'create' OR init IS NOT NULL"))
end
def down do
drop(constraint(:internal_transactions, :call_has_call_type))
drop(constraint(:internal_transactions, :call_has_input))
drop(constraint(:internal_transactions, :create_has_init))
end
end

@ -47,34 +47,36 @@ defmodule Explorer.Chain.ImportTest do
internal_transactions: %{
params: [
%{
block_number: 35,
transaction_index: 0,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
trace_address: [],
type: "call",
call_type: "call",
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
gas: 4_677_320,
gas_used: 27770,
index: 0,
input: "0x",
output: "0x",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
trace_address: [],
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "call",
value: 0,
block_number: 35,
transaction_index: 0
value: 0
},
%{
block_number: 35,
transaction_index: 1,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 1,
trace_address: [0],
type: "call",
call_type: "call",
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
gas: 4_677_320,
gas_used: 27770,
index: 1,
input: "0x",
output: "0x",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
trace_address: [],
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "call",
value: 0,
block_number: 35,
transaction_index: 1
value: 0
}
],
timeout: 5
@ -625,19 +627,19 @@ defmodule Explorer.Chain.ImportTest do
params: [
%{
block_number: 35,
transaction_index: 0,
transaction_hash: transaction_hash,
index: 0,
trace_address: [],
type: "call",
call_type: "call",
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
gas: 4_677_320,
gas_used: 27770,
index: 0,
input: "0x",
output: "0x",
to_address_hash: to_address_hash,
trace_address: [],
transaction_hash: transaction_hash,
type: "call",
value: 0,
transaction_block_number: 35,
transaction_index: 0
value: 0
}
]
}
@ -1767,6 +1769,7 @@ defmodule Explorer.Chain.ImportTest do
to_address_hash: to_address_hash_before,
trace_address: [],
value: 0,
input: "0x",
error: error,
block_number: 35,
transaction_index: 0

@ -850,16 +850,17 @@ defmodule Explorer.ChainTest do
internal_transactions: %{
params: [
%{
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
trace_address: [],
type: "call",
call_type: "call",
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
gas: 4_677_320,
gas_used: 27770,
index: 0,
input: "0x",
output: "0x",
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
trace_address: [],
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "call",
value: 0
}
]

@ -269,7 +269,8 @@ defmodule Explorer.Factory do
call_type: :delegatecall,
gas: gas,
gas_used: gas_used,
output: %Data{bytes: <<1>>},
input: %Data{bytes: <<1>>},
output: %Data{bytes: <<2>>},
# caller MUST supply `index`
trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra

Loading…
Cancel
Save