From 1616266194447bf95651fc26e13aece54fe4d7f9 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 19 Feb 2019 15:48:35 +0300 Subject: [PATCH] check if to_address_hash is contract address to fetch internal transactions --- apps/explorer/lib/explorer/chain.ex | 42 +++++++++++ apps/explorer/test/explorer/chain_test.exs | 70 +++++++++++++++++++ .../lib/indexer/block/realtime/fetcher.ex | 9 ++- 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f6c8d9eb51..2f6e2f071b 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -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} @@ -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. """ diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 566eb3ad40..118a42f2f8 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -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} @@ -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) @@ -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 diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 7f684d8510..a825b90b77 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -435,8 +435,13 @@ 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: from_address_hash}, + _ + ) do + Chain.contract_address?(from_address_hash) + 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