Merge branch 'master' into track-transaction-lifecycle

pull/1446/head
Zach Daniel 6 years ago committed by GitHub
commit bb612d85c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
  2. 27
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/log_test.exs
  3. 42
      apps/explorer/lib/explorer/chain.ex
  4. 70
      apps/explorer/test/explorer/chain_test.exs
  5. 40
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  6. 36
      apps/indexer/lib/indexer/internal_transaction/fetcher.ex
  7. 78
      apps/indexer/test/indexer/internal_transaction/fetcher_test.exs

@ -171,7 +171,11 @@ defmodule EthereumJSONRPC.Log do
do: entry do: entry
defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do
{key, quantity_to_integer(quantity)} if is_nil(quantity) do
{key, nil}
else
{key, quantity_to_integer(quantity)}
end
end end
defp put_topics(params, topics) when is_map(params) and is_list(topics) do defp put_topics(params, topics) when is_map(params) and is_list(topics) do

@ -2,4 +2,31 @@ defmodule EthereumJSONRPC.LogTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
doctest EthereumJSONRPC.Log doctest EthereumJSONRPC.Log
alias EthereumJSONRPC.Log
describe "to_elixir/1" do
test "does not tries convert nils to integer" do
input = %{
"address" => "0xda8b3276cde6d768a44b9dac659faa339a41ac55",
"blockHash" => nil,
"blockNumber" => nil,
"data" => "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563",
"logIndex" => "0x0",
"removed" => false,
"topics" => [
"0xadc1e8a294f8415511303acc4a8c0c5906c7eb0bf2a71043d7f4b03b46a39130",
"0x000000000000000000000000c15bf627accd3b054075c7880425f903106be72a",
"0x000000000000000000000000a59eb37750f9c8f2e11aac6700e62ef89187e4ed"
],
"transactionHash" => "0xf9b663b4e9b1fdc94eb27b5cfba04eb03d2f7b3fa0b24eb2e1af34f823f2b89e",
"transactionIndex" => "0x0"
}
result = Log.to_elixir(input)
assert result["blockNumber"] == nil
assert result["blockHash"] == nil
end
end
end end

@ -17,6 +17,8 @@ defmodule Explorer.Chain do
where: 3 where: 3
] ]
import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
@ -1868,6 +1870,46 @@ defmodule Explorer.Chain do
|> Data.to_string() |> Data.to_string()
end 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 """ @doc """
Fetches contract creation input data. Fetches contract creation input data.
""" """

@ -1,10 +1,12 @@
defmodule Explorer.ChainTest do defmodule Explorer.ChainTest do
use Explorer.DataCase use Explorer.DataCase
use EthereumJSONRPC.Case, async: true
require Ecto.Query require Ecto.Query
import Ecto.Query import Ecto.Query
import Explorer.Factory import Explorer.Factory
import Mox
alias Explorer.{Chain, Factory, PagingOptions, Repo} alias Explorer.{Chain, Factory, PagingOptions, Repo}
@ -27,6 +29,10 @@ defmodule Explorer.ChainTest do
doctest Explorer.Chain doctest Explorer.Chain
setup :set_mox_global
setup :verify_on_exit!
describe "count_addresses_with_balance_from_cache/0" do describe "count_addresses_with_balance_from_cache/0" do
test "returns the number of addresses with fetched_coin_balance > 0" do test "returns the number of addresses with fetched_coin_balance > 0" do
insert(:address, fetched_coin_balance: 0) insert(:address, fetched_coin_balance: 0)
@ -3631,4 +3637,68 @@ defmodule Explorer.ChainTest do
assert found_creation_data == "" assert found_creation_data == ""
end end
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 end

@ -368,7 +368,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
} }
) do ) do
case transactions_params 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 |> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) do
{:ok, internal_transactions_params} -> {:ok, internal_transactions_params} ->
merged_addresses_params = merged_addresses_params =
@ -387,23 +387,32 @@ defmodule Indexer.Block.Realtime.Fetcher do
end end
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) token_transfer_transaction_hash_set = MapSet.new(token_transfers_params, & &1.transaction_hash)
Enum.flat_map( Enum.flat_map(
transactions_params, 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 end
defp transaction_params_to_fetch_internal_transaction_params_list( defp transaction_params_to_fetch_internal_transaction_params_list(
%{block_number: block_number, transaction_index: transaction_index, hash: hash} = transaction_params, %{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 when is_integer(block_number) and is_integer(transaction_index) and is_binary(hash) do
token_transfer? = hash in token_transfer_transaction_hash_set 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}] [%{block_number: block_number, transaction_index: transaction_index, hash_data: hash}]
else else
[] []
@ -419,6 +428,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
input: unquote(TokenTransfer.transfer_function_signature()) <> params, input: unquote(TokenTransfer.transfer_function_signature()) <> params,
value: 0 value: 0
}, },
_,
_ _
) do ) do
types = [:address, {:uint, 256}] types = [:address, {:uint, 256}]
@ -435,12 +445,24 @@ defmodule Indexer.Block.Realtime.Fetcher do
end end
end end
# Input-less transactions are value-transfers only, so their internal transactions do not need to be indexed defp fetch_internal_transactions?(
defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil, input: "0x"}, _), do: false %{
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 # Token transfers not transferred during contract creation don't need internal transactions as the token transfers
# derive completely from the logs. # derive completely from the logs.
defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil}, true), do: false defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil}, true, _), do: false
defp fetch_internal_transactions?(_, _), do: true defp fetch_internal_transactions?(_, _, _), do: true
defp balances( defp balances(
%Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments},

@ -113,7 +113,12 @@ defmodule Indexer.InternalTransaction.Fetcher do
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) |> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments)
|> case do |> case do
{:ok, internal_transactions_params} -> {:ok, internal_transactions_params} ->
addresses_params = AddressExtraction.extract_addresses(%{internal_transactions: internal_transactions_params}) internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params)
addresses_params =
AddressExtraction.extract_addresses(%{
internal_transactions: internal_transactions_params_without_failed_creations
})
address_hash_to_block_number = address_hash_to_block_number =
Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} -> Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} ->
@ -123,7 +128,7 @@ defmodule Indexer.InternalTransaction.Fetcher do
with {:ok, imported} <- with {:ok, imported} <-
Chain.import(%{ Chain.import(%{
addresses: %{params: addresses_params}, addresses: %{params: addresses_params},
internal_transactions: %{params: internal_transactions_params}, internal_transactions: %{params: internal_transactions_params_without_failed_creations},
timeout: :infinity timeout: :infinity
}) do }) do
async_import_coin_balances(imported, %{ async_import_coin_balances(imported, %{
@ -197,4 +202,31 @@ defmodule Indexer.InternalTransaction.Fetcher do
end end
end) end)
end end
defp remove_failed_creations(internal_transactions_params) do
internal_transactions_params
|> Enum.map(fn internal_transaction_params ->
internal_transaction_params[:trace_address]
failed_parent_index =
Enum.find(internal_transaction_params[:trace_address], fn trace_address ->
parent = Enum.at(internal_transactions_params, trace_address)
!is_nil(parent[:error])
end)
failed_parent = failed_parent_index && Enum.at(internal_transactions_params, failed_parent_index)
if failed_parent do
internal_transaction_params
|> Map.delete(:created_contract_address_hash)
|> Map.delete(:created_contract_code)
|> Map.delete(:gas_used)
|> Map.delete(:output)
|> Map.put(:error, failed_parent[:error])
else
internal_transaction_params
end
end)
end
end end

@ -5,7 +5,7 @@ defmodule Indexer.InternalTransaction.FetcherTest do
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Mox import Mox
alias Explorer.Chain.{Hash, Transaction} alias Explorer.Chain.{Address, Hash, Transaction}
alias Indexer.{CoinBalance, InternalTransaction, PendingTransaction} alias Indexer.{CoinBalance, InternalTransaction, PendingTransaction}
@ -151,6 +151,82 @@ defmodule Indexer.InternalTransaction.FetcherTest do
""" """
end end
@tag :no_parity
test "internal transactions with failed parent does not create a new address", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: %{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"callType" => "call",
"from" => "0xc73add416e2119d20ce80e0904fc1877e33ef246",
"gas" => "0x13388",
"input" => "0xc793bf97",
"to" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340",
"value" => "0x0"
},
"error" => "Reverted",
"subtraces" => 1,
"traceAddress" => [],
"type" => "call"
},
%{
"action" => %{
"from" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340",
"gas" => "0xb2ab",
"init" =>
"0x608060405234801561001057600080fd5b5060d38061001f6000396000f3fe6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"value" => "0x0"
},
"result" => %{
"address" => "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75",
"code" =>
"0x6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"gasUsed" => "0xa535"
},
"subtraces" => 0,
"traceAddress" => [0],
"type" => "create"
}
],
"vmTrace" => nil
}
}
]}
end)
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
%Transaction{hash: %Hash{bytes: bytes}} =
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa")
|> with_block()
:ok =
InternalTransaction.Fetcher.run(
[
{7_202_692, bytes, 0}
],
json_rpc_named_arguments
)
address = "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75"
fetched_address = Repo.one(from(address in Address, where: address.hash == ^address))
assert is_nil(fetched_address)
end
end
test "duplicate transaction hashes only retry uniques", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "duplicate transaction hashes only retry uniques", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->

Loading…
Cancel
Save