Skip to content

Commit

Permalink
Merge pull request #1455 from poanetwork/ab-fetch-internal_transactio…
Browse files Browse the repository at this point in the history
…n_from_contracts

check if to_address_hash is contract address to fetch internal transactions
  • Loading branch information
acravenho authored Feb 19, 2019
2 parents c2f034b + 389583e commit 6fd2939
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 9 deletions.
42 changes: 42 additions & 0 deletions apps/explorer/lib/explorer/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ defmodule Explorer.Chain do
where: 3
]

import EthereumJSONRPC, only: [integer_to_quantity: 1]

alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi}

Expand Down Expand Up @@ -1868,6 +1870,46 @@ defmodule Explorer.Chain do
|> Data.to_string()
end

@doc """
Checks if an address is a contract
"""
@spec contract_address?(String.t(), non_neg_integer(), Keyword.t()) :: boolean() | :json_rpc_error
def contract_address?(address_hash, block_number, json_rpc_named_arguments \\ []) do
{:ok, binary_hash} = Explorer.Chain.Hash.Address.cast(address_hash)

query =
from(
address in Address,
where: address.hash == ^binary_hash
)

address = Repo.one(query)

cond do
is_nil(address) ->
block_quantity = integer_to_quantity(block_number)

case EthereumJSONRPC.fetch_codes(
[%{block_quantity: block_quantity, address: address_hash}],
json_rpc_named_arguments
) do
{:ok, %EthereumJSONRPC.FetchedCodes{params_list: fetched_codes}} ->
result = List.first(fetched_codes)

result && !(is_nil(result[:code]) || result[:code] == "" || result[:code] == "0x")

_ ->
:json_rpc_error
end

is_nil(address.contract_code) ->
false

true ->
true
end
end

@doc """
Fetches contract creation input data.
"""
Expand Down
70 changes: 70 additions & 0 deletions apps/explorer/test/explorer/chain_test.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
defmodule Explorer.ChainTest do
use Explorer.DataCase
use EthereumJSONRPC.Case, async: true

require Ecto.Query

import Ecto.Query
import Explorer.Factory
import Mox

alias Explorer.{Chain, Factory, PagingOptions, Repo}

Expand All @@ -27,6 +29,10 @@ defmodule Explorer.ChainTest do

doctest Explorer.Chain

setup :set_mox_global

setup :verify_on_exit!

describe "count_addresses_with_balance_from_cache/0" do
test "returns the number of addresses with fetched_coin_balance > 0" do
insert(:address, fetched_coin_balance: 0)
Expand Down Expand Up @@ -3631,4 +3637,68 @@ defmodule Explorer.ChainTest do
assert found_creation_data == ""
end
end

describe "contract_address?/2" do
test "returns true if address has contract code" do
code = %Data{
bytes: <<1, 2, 3, 4, 5>>
}

address = insert(:address, contract_code: code)

assert Chain.contract_address?(to_string(address.hash), 1)
end

test "returns false if address has not contract code" do
address = insert(:address)

refute Chain.contract_address?(to_string(address.hash), 1)
end

@tag :no_parity
@tag :no_geth
test "returns true if fetched code from json rpc", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
hash = "0x71300d93a8CdF93385Af9635388cF2D00b95a480"

if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn _arguments, _options ->
{:ok,
[
%{
id: 0,
result: "0x0102030405"
}
]}
end)
end

assert Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments)
end

@tag :no_parity
@tag :no_geth
test "returns false if no fetched code from json rpc", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
hash = "0x71300d93a8CdF93385Af9635388cF2D00b95a480"

if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn _arguments, _options ->
{:ok,
[
%{
id: 0,
result: "0x"
}
]}
end)
end

refute Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments)
end
end
end
40 changes: 31 additions & 9 deletions apps/indexer/lib/indexer/block/realtime/fetcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
}
) do
case transactions_params
|> transactions_params_to_fetch_internal_transactions_params(token_transfers_params)
|> transactions_params_to_fetch_internal_transactions_params(token_transfers_params, json_rpc_named_arguments)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) do
{:ok, internal_transactions_params} ->
merged_addresses_params =
Expand All @@ -387,23 +387,32 @@ defmodule Indexer.Block.Realtime.Fetcher do
end
end

defp transactions_params_to_fetch_internal_transactions_params(transactions_params, token_transfers_params) do
defp transactions_params_to_fetch_internal_transactions_params(
transactions_params,
token_transfers_params,
json_rpc_named_arguments
) do
token_transfer_transaction_hash_set = MapSet.new(token_transfers_params, & &1.transaction_hash)

Enum.flat_map(
transactions_params,
&transaction_params_to_fetch_internal_transaction_params_list(&1, token_transfer_transaction_hash_set)
&transaction_params_to_fetch_internal_transaction_params_list(
&1,
token_transfer_transaction_hash_set,
json_rpc_named_arguments
)
)
end

defp transaction_params_to_fetch_internal_transaction_params_list(
%{block_number: block_number, transaction_index: transaction_index, hash: hash} = transaction_params,
token_transfer_transaction_hash_set
token_transfer_transaction_hash_set,
json_rpc_named_arguments
)
when is_integer(block_number) and is_integer(transaction_index) and is_binary(hash) do
token_transfer? = hash in token_transfer_transaction_hash_set

if fetch_internal_transactions?(transaction_params, token_transfer?) do
if fetch_internal_transactions?(transaction_params, token_transfer?, json_rpc_named_arguments) do
[%{block_number: block_number, transaction_index: transaction_index, hash_data: hash}]
else
[]
Expand All @@ -419,6 +428,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
input: unquote(TokenTransfer.transfer_function_signature()) <> params,
value: 0
},
_,
_
) do
types = [:address, {:uint, 256}]
Expand All @@ -435,12 +445,24 @@ defmodule Indexer.Block.Realtime.Fetcher do
end
end

# Input-less transactions are value-transfers only, so their internal transactions do not need to be indexed
defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil, input: "0x"}, _), do: false
defp fetch_internal_transactions?(
%{
status: :ok,
created_contract_address_hash: nil,
input: "0x",
to_address_hash: to_address_hash,
block_number: block_number
},
_,
json_rpc_named_arguments
) do
Chain.contract_address?(to_address_hash, block_number, json_rpc_named_arguments)
end

# Token transfers not transferred during contract creation don't need internal transactions as the token transfers
# derive completely from the logs.
defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil}, true), do: false
defp fetch_internal_transactions?(_, _), do: true
defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil}, true, _), do: false
defp fetch_internal_transactions?(_, _, _), do: true

defp balances(
%Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments},
Expand Down

0 comments on commit 6fd2939

Please sign in to comment.