Merge branch 'master' into ab-change-v-column-type

pull/1559/head
Victor Baranov 6 years ago committed by GitHub
commit 6dfdff4d89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .dialyzer-ignore
  2. 1
      README.md
  3. 16
      apps/block_scout_web/lib/block_scout_web/chain.ex
  4. 16
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  5. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  6. 6
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  7. 50
      apps/block_scout_web/priv/gettext/default.pot
  8. 50
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  9. 14
      apps/block_scout_web/test/block_scout_web/chain_test.exs
  10. 10
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  11. 8
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex
  12. 8
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
  13. 70
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
  14. 16
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex
  15. 6
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs
  16. 88
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs
  17. 203
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs
  18. 92
      apps/explorer/lib/explorer/chain.ex
  19. 9
      apps/explorer/lib/explorer/chain/block.ex
  20. 4
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  21. 38
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  22. 10
      apps/explorer/priv/repo/migrations/20190228220746_add_internal_transactions_indexed_at_to_blocks.exs
  23. 7
      apps/explorer/priv/repo/migrations/20190313085740_add_index_symobl_in_tokens.exs
  24. 7
      apps/explorer/priv/repo/migrations/20190314084907_add_index_to_to_address_hash.exs
  25. 23
      apps/explorer/test/explorer/chain/import_test.exs
  26. 30
      apps/explorer/test/explorer/chain_test.exs
  27. 1
      apps/indexer/config/config.exs
  28. 4
      apps/indexer/config/dev/parity.exs
  29. 2
      apps/indexer/config/prod/parity.exs
  30. 39
      apps/indexer/lib/indexer/block/catchup/fetcher.ex
  31. 20
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  32. 7
      apps/indexer/lib/indexer/block/reward/fetcher.ex
  33. 10
      apps/indexer/lib/indexer/block/reward/supervisor.ex
  34. 83
      apps/indexer/lib/indexer/internal_transaction/fetcher.ex
  35. 5
      apps/indexer/lib/indexer/temporary/failed_created_addresses.ex
  36. 70
      apps/indexer/test/indexer/block/fetcher_test.exs
  37. 338
      apps/indexer/test/indexer/block/realtime/fetcher_test.exs
  38. 30
      apps/indexer/test/indexer/internal_transaction/fetcher_test.exs
  39. 63
      apps/indexer/test/indexer/temporary/failed_created_addresses_test.exs

@ -1,6 +1,6 @@
:0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:390: Function timestamp_to_datetime/1 has no local return
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer()

@ -59,6 +59,7 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
* [Fuse Network](https://explorer.fuse.io/)
* [ARTIS](https://explorer.sigma1.artis.network)
* [SafeChain](https://explorer.safechain.io)
* [SpringChain](https://explorer.springrole.com/)
### Visual Interface

@ -11,7 +11,8 @@ defmodule BlockScoutWeb.Chain do
number_to_block: 1,
string_to_address_hash: 1,
string_to_block_hash: 1,
string_to_transaction_hash: 1
string_to_transaction_hash: 1,
token_contract_address_from_token_name: 1
]
alias Explorer.Chain.Block.Reward
@ -67,10 +68,10 @@ defmodule BlockScoutWeb.Chain do
end
end
def from_param(formatted_number) when is_binary(formatted_number) do
case param_to_block_number(formatted_number) do
def from_param(string) when is_binary(string) do
case param_to_block_number(string) do
{:ok, number} -> number_to_block(number)
{:error, :invalid} -> {:error, :not_found}
_ -> token_address_from_name(string)
end
end
@ -159,6 +160,13 @@ defmodule BlockScoutWeb.Chain do
end
end
defp token_address_from_name(name) do
case token_contract_address_from_token_name(name) do
{:ok, hash} -> find_or_insert_address_from_hash(hash)
_ -> {:error, :not_found}
end
end
defp paging_params({%Reward{block: %{number: number}}, _}) do
%{"block_number" => number, "index" => 0}
end

@ -35,10 +35,18 @@
<% end %>
<span>
<span class="address-detail-item">
<span data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %>
</span>
<%= gettext("Transactions Sent") %>
<%= if contract?(@address) do %>
<%= gettext(">=") %>
<span data-selector="transaction-count">
<%= incoming_transaction_count(@address) %>
</span>
<%= gettext("Incoming Transactions") %>
<% else %>
<span data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %>
</span>
<%= gettext("Transactions Sent") %>
<% end %>
</span>
<span class="address-detail-item">
<%= gettext("Last Balance Update: Block #") %><span data-selector="fetched-coin-balance-block-number"><%= @address.fetched_coin_balance_block_number %></span>

@ -80,7 +80,7 @@
<div class="search-form d-lg-flex d-inline-block">
<%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %>
<div class="input-group">
<%= search_input f, :q, class: 'form-control mr-auto', placeholder: gettext("Search by address, transaction hash, or block number"), "aria-describedby": "search-icon", "aria-label": gettext("Search"), "data-test": "search_input" %>
<%= search_input f, :q, class: 'form-control mr-auto', placeholder: gettext("Search by address, token symbol name, transaction hash, or block number"), "aria-describedby": "search-icon", "aria-label": gettext("Search"), "data-test": "search_input" %>
<div class="input-group-append">
<button class="input-group-text" id="search-icon">
<%= render BlockScoutWeb.IconsView, "_search_icon.html" %>

@ -213,6 +213,12 @@ defmodule BlockScoutWeb.AddressView do
def token_title(%Token{name: name, symbol: symbol}), do: "#{name} (#{symbol})"
def incoming_transaction_count(%Address{} = address) do
count = Chain.address_to_incoming_transaction_count(address)
Cldr.Number.to_string!(count, format: "#,###")
end
def trimmed_hash(%Hash{} = hash) do
string_hash = to_string(hash)
"#{String.slice(string_hash, 0..5)}#{String.slice(string_hash, -6..-1)}"

@ -189,10 +189,10 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:40
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:51
#: lib/block_scout_web/templates/address/overview.html.eex:59
#: lib/block_scout_web/templates/address_validation/index.html.eex:30
#: lib/block_scout_web/templates/address_validation/index.html.eex:57
#: lib/block_scout_web/views/address_view.ex:287
#: lib/block_scout_web/views/address_view.ex:293
msgid "Blocks Validated"
msgstr ""
@ -209,8 +209,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:95
#: lib/block_scout_web/templates/address/overview.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:111
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:91
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99
msgid "Close"
@ -222,7 +222,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:39
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
#: lib/block_scout_web/views/address_view.ex:284
#: lib/block_scout_web/views/address_view.ex:290
msgid "Code"
msgstr ""
@ -328,7 +328,7 @@ msgid "Copy Txn Hash"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:61
#: lib/block_scout_web/templates/address/overview.html.eex:69
msgid "Created by"
msgstr ""
@ -494,7 +494,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:283
#: lib/block_scout_web/views/address_view.ex:289
#: lib/block_scout_web/views/transaction_view.ex:258
msgid "Internal Transactions"
msgstr ""
@ -685,7 +685,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:94
#: lib/block_scout_web/templates/address/overview.html.eex:102
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90
@ -702,7 +702,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:122
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/address_view.ex:285
#: lib/block_scout_web/views/address_view.ex:291
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -733,11 +733,6 @@ msgstr ""
msgid "Search"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, transaction hash, or block number"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
@ -901,7 +896,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/templates/address_validation/index.html.eex:19
#: lib/block_scout_web/views/address_view.ex:281
#: lib/block_scout_web/views/address_view.ex:287
msgid "Tokens"
msgstr ""
@ -962,7 +957,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:93
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:282
#: lib/block_scout_web/views/address_view.ex:288
msgid "Transactions"
msgstr ""
@ -1094,7 +1089,7 @@ msgid "Yes"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:67
#: lib/block_scout_web/templates/address/overview.html.eex:75
msgid "at"
msgstr ""
@ -1405,7 +1400,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:30
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/views/address_view.ex:286
#: lib/block_scout_web/views/address_view.ex:292
msgid "Coin Balance History"
msgstr ""
@ -1647,12 +1642,12 @@ msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:44
#: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Last Balance Update: Block #"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:41
#: lib/block_scout_web/templates/address/overview.html.eex:48
msgid "Transactions Sent"
msgstr ""
@ -1688,6 +1683,21 @@ msgid "EVM Vesion"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:75
#: lib/block_scout_web/templates/address/overview.html.eex:39
msgid ">="
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:43
msgid "Incoming Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:83
msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""

@ -189,10 +189,10 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:40
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:51
#: lib/block_scout_web/templates/address/overview.html.eex:59
#: lib/block_scout_web/templates/address_validation/index.html.eex:30
#: lib/block_scout_web/templates/address_validation/index.html.eex:57
#: lib/block_scout_web/views/address_view.ex:287
#: lib/block_scout_web/views/address_view.ex:293
msgid "Blocks Validated"
msgstr ""
@ -209,8 +209,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:95
#: lib/block_scout_web/templates/address/overview.html.eex:103
#: lib/block_scout_web/templates/address/overview.html.eex:111
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:91
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:99
msgid "Close"
@ -222,7 +222,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:39
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
#: lib/block_scout_web/views/address_view.ex:284
#: lib/block_scout_web/views/address_view.ex:290
msgid "Code"
msgstr ""
@ -328,7 +328,7 @@ msgid "Copy Txn Hash"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:61
#: lib/block_scout_web/templates/address/overview.html.eex:69
msgid "Created by"
msgstr ""
@ -494,7 +494,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:283
#: lib/block_scout_web/views/address_view.ex:289
#: lib/block_scout_web/views/transaction_view.ex:258
msgid "Internal Transactions"
msgstr ""
@ -685,7 +685,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:94
#: lib/block_scout_web/templates/address/overview.html.eex:102
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:90
@ -702,7 +702,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:122
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/address_view.ex:285
#: lib/block_scout_web/views/address_view.ex:291
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -733,11 +733,6 @@ msgstr ""
msgid "Search"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, transaction hash, or block number"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
@ -901,7 +896,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/templates/address_validation/index.html.eex:19
#: lib/block_scout_web/views/address_view.ex:281
#: lib/block_scout_web/views/address_view.ex:287
msgid "Tokens"
msgstr ""
@ -962,7 +957,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:93
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:282
#: lib/block_scout_web/views/address_view.ex:288
msgid "Transactions"
msgstr ""
@ -1094,7 +1089,7 @@ msgid "Yes"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:67
#: lib/block_scout_web/templates/address/overview.html.eex:75
msgid "at"
msgstr ""
@ -1405,7 +1400,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:30
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/views/address_view.ex:286
#: lib/block_scout_web/views/address_view.ex:292
msgid "Coin Balance History"
msgstr ""
@ -1647,12 +1642,12 @@ msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:44
#: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Last Balance Update: Block #"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:41
#: lib/block_scout_web/templates/address/overview.html.eex:48
msgid "Transactions Sent"
msgstr ""
@ -1688,6 +1683,21 @@ msgid "EVM Vesion"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:75
#: lib/block_scout_web/templates/address/overview.html.eex:39
msgid ">="
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:43
msgid "Incoming Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:83
msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""

@ -44,6 +44,20 @@ defmodule BlockScoutWeb.ChainTest do
assert {:ok, %Address{hash: ^hash}} = address |> Phoenix.Param.to_param() |> Chain.from_param()
end
test "finds a token by its name" do
name = "AYR"
insert(:token, symbol: name)
assert {:ok, %Address{}} = name |> Chain.from_param()
end
test "finds a token by its name even if lowercase name was passed" do
name = "ayr"
insert(:token, symbol: String.upcase(name))
assert {:ok, %Address{}} = name |> Chain.from_param()
end
test "returns {:error, :not_found} when garbage is passed in" do
assert {:error, :not_found} = Chain.from_param("any ol' thing")
end

@ -264,6 +264,16 @@ defmodule EthereumJSONRPC do
)
end
@doc """
Fetches internal transactions for entire blocks from variant API.
"""
def fetch_block_internal_transactions(params_list, json_rpc_named_arguments) when is_list(params_list) do
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions(
params_list,
json_rpc_named_arguments
)
end
@doc """
Fetches pending transactions from variant API.
"""

@ -21,6 +21,14 @@ defmodule EthereumJSONRPC.Ganache do
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
Internal transaction fetching is not currently supported for Ganache.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore
@doc """
Pending transaction fetching is not supported currently for Ganache.

@ -32,6 +32,14 @@ defmodule EthereumJSONRPC.Geth do
end
end
@doc """
Internal transaction fetching for entire blocks is not currently supported for Geth.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore
@doc """
Pending transaction fetching is not supported currently for Geth.

@ -26,18 +26,26 @@ defmodule EthereumJSONRPC.Parity do
end
end
@doc """
Internal transaction fetching for individual transactions is no longer supported for Parity.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_internal_transactions(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do
id_to_params = id_to_params(transactions_params)
def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
id_to_params = id_to_params(block_numbers)
with {:ok, responses} <-
id_to_params
|> trace_replay_transaction_requests()
|> trace_replay_block_transactions_requests()
|> json_rpc(json_rpc_named_arguments) do
trace_replay_transaction_responses_to_internal_transactions_params(responses, id_to_params)
trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
end
end
@ -68,9 +76,9 @@ defmodule EthereumJSONRPC.Parity do
Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
end
defp trace_replay_transaction_responses_to_internal_transactions_params(responses, id_to_params)
defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
with {:ok, traces} <- trace_replay_transaction_responses_to_traces(responses, id_to_params) do
with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do
params =
traces
|> Traces.to_elixir()
@ -80,10 +88,10 @@ defmodule EthereumJSONRPC.Parity do
end
end
defp trace_replay_transaction_responses_to_traces(responses, id_to_params)
defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
responses
|> Enum.map(&trace_replay_transaction_response_to_traces(&1, id_to_params))
|> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params))
|> Enum.reduce(
{:ok, []},
fn
@ -115,48 +123,48 @@ defmodule EthereumJSONRPC.Parity do
end
end
defp trace_replay_transaction_response_to_traces(%{id: id, result: %{"trace" => traces}}, id_to_params)
when is_list(traces) and is_map(id_to_params) do
%{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} =
Map.fetch!(id_to_params, id)
defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
when is_list(results) and is_map(id_to_params) do
block_number = Map.fetch!(id_to_params, id)
annotated_traces =
traces
results
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockNumber" => block_number,
"index" => index,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
})
|> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
traces
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockNumber" => block_number,
"transactionHash" => transaction_hash,
"transactionIndex" => transaction_index,
"index" => index
})
end)
end)
{:ok, annotated_traces}
end
defp trace_replay_transaction_response_to_traces(%{id: id, error: error}, id_to_params)
defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params)
when is_map(id_to_params) do
%{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} =
Map.fetch!(id_to_params, id)
block_number = Map.fetch!(id_to_params, id)
annotated_error =
Map.put(error, :data, %{
"blockNumber" => block_number,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
"blockNumber" => block_number
})
{:error, annotated_error}
end
defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} ->
trace_replay_transaction_request(%{id: id, hash_data: hash_data})
defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, block_number} ->
trace_replay_block_transactions_request(%{id: id, block_number: block_number})
end)
end
defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
end
end

@ -43,6 +43,22 @@ defmodule EthereumJSONRPC.Variant do
EthereumJSONRPC.json_rpc_named_arguments()
) :: {:ok, [internal_transaction_params]} | {:error, reason :: term} | :ignore
@doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the variant of the Ethereum JSONRPC API.
Uses API for fetching all internal transactions in the block
## Returns
* `{:ok, [internal_transaction_params]}` - internal transactions were successfully fetched for all blocks
* `{:error, reason}` - there was one or more errors with `reason` in fetching at least one of the blocks'
internal transactions
* `:ignore` - the variant does not support fetching internal transactions.
"""
@callback fetch_block_internal_transactions(
[EthereumJSONRPC.block_number()],
EthereumJSONRPC.json_rpc_named_arguments()
) :: {:ok, [internal_transaction_params]} | {:error, reason :: term} | :ignore
@doc """
Fetch the `t:Explorer.Chain.Transaction.changeset/2` params for pending transactions from the variant of the Ethereum
JSONRPC API.

@ -77,6 +77,12 @@ defmodule EthereumJSONRPC.GethTest do
end
end
describe "fetch_block_internal_transactions/1" do
test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
EthereumJSONRPC.Geth.fetch_block_internal_transactions([], json_rpc_named_arguments)
end
end
describe "fetch_pending_transactions/1" do
test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
EthereumJSONRPC.Geth.fetch_pending_transactions(json_rpc_named_arguments)

@ -103,27 +103,18 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
test "transparently splits batch payloads that would trigger a 504 Gateway Timeout", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
transaction_hashes = ~w(0x196c2579f30077e8df0994e185d724331350c2bdb0f5d4e48b9e83f1e149cc28
0x19eb948514a971bcd3ab737083bbdb32da233fff2ba70490bb0a36937a418006
0x1a1039899fd07a5fd81faf2ec11ca24fc6023d486d4156095688a29b3bf06b7b
0x1a942061ed6cf0736b194732bb6e1edfcbc50cc004e0cdad79335b3e40e23c9c
0x1bdec995deaa0e5b53cc7a0b84eaff39da90f5e507fdb4360881ff31f824d918
0x1c26758e003b0bc89ac7e3e6e87c6fc76dfb8d878dc530055e6a34f4d557cb1c
0x1d592be82979bd1cc320eb70d4bb1d61226d78baa9e57e2a12b24345f81ce3bd
0x1e57e7ce2941c6108e899f786fe339fa50ab053e47fbdcbf5979f475042c6dd8
0x1ec1f9c31a0f43f7e684bfa20e422d7d8a343f81c517be1e30f149618ae306f2
0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c)
block_numbers = [862_272, 862_273, 862_274, 862_275, 862_276, 862_277, 862_278, 862_279, 862_280, 862_281]
if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do
EthereumJSONRPC.HTTP.Mox
|> expect(:json_rpc, fn _url, _json, _options ->
{:ok, %{body: "504 Gateway Timeout", status_code: 413}}
{:ok, %{body: "504 Gateway Timeout", status_code: 504}}
end)
|> expect(:json_rpc, fn _url, json, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c"
assert json_binary =~ "0x1bdec995deaa0e5b53cc7a0b84eaff39da90f5e507fdb4360881ff31f824d918"
refute json_binary =~ "0xD2849"
assert json_binary =~ "0xD2844"
body =
0..4
@ -131,16 +122,19 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
%{
jsonrpc: "2.0",
id: id,
result: %{
"trace" => [
%{
"type" => "create",
"action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"},
"traceAddress" => "0x",
"result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"}
}
]
}
result: [
%{
"trace" => [
%{
"type" => "create",
"action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"},
"traceAddress" => "0x",
"result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"}
}
],
"transactionHash" => "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c"
}
]
}
end)
|> Jason.encode!()
@ -150,9 +144,9 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
|> expect(:json_rpc, fn _url, json, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ "0x1bdec995deaa0e5b53cc7a0b84eaff39da90f5e507fdb4360881ff31f824d918"
assert json_binary =~ "0x1c26758e003b0bc89ac7e3e6e87c6fc76dfb8d878dc530055e6a34f4d557cb1c"
assert json_binary =~ "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c"
refute json_binary =~ "0xD2844"
assert json_binary =~ "0xD2845"
assert json_binary =~ "0xD2849"
body =
5..9
@ -160,16 +154,19 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
%{
jsonrpc: "2.0",
id: id,
result: %{
"trace" => [
%{
"type" => "create",
"action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"},
"traceAddress" => "0x",
"result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"}
}
]
}
result: [
%{
"trace" => [
%{
"type" => "create",
"action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"},
"traceAddress" => "0x",
"result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"}
}
],
"transactionHash" => "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c"
}
]
}
end)
|> Jason.encode!()
@ -178,24 +175,19 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do
end)
end
transactions_params =
Enum.map(transaction_hashes, fn hash_data ->
%{block_number: 0, hash_data: hash_data, gas: 1_000_000, transaction_index: 0}
end)
assert {:ok, responses} =
EthereumJSONRPC.fetch_internal_transactions(transactions_params, json_rpc_named_arguments)
EthereumJSONRPC.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments)
assert Enum.count(responses) == Enum.count(transactions_params)
assert Enum.count(responses) == Enum.count(block_numbers)
transaction_hash_set = MapSet.new(transaction_hashes)
block_number_set = MapSet.new(block_numbers)
response_transaction_hash_set =
Enum.into(responses, MapSet.new(), fn %{transaction_hash: transaction_hash} ->
transaction_hash
response_block_number_set =
Enum.into(responses, MapSet.new(), fn %{block_number: block_number} ->
block_number
end)
assert MapSet.equal?(response_transaction_hash_set, transaction_hash_set)
assert MapSet.equal?(response_block_number_set, block_number_set)
end
end

@ -14,7 +14,13 @@ defmodule EthereumJSONRPC.ParityTest do
@moduletag :no_geth
describe "fetch_internal_transactions/1" do
test "with all valid transaction_params returns {:ok, transactions_params}", %{
test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
EthereumJSONRPC.Parity.fetch_internal_transactions([], json_rpc_named_arguments)
end
end
describe "fetch_block_internal_transactions/1" do
test "with all valid block_numbers returns {:ok, transactions_params}", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
@ -24,7 +30,7 @@ defmodule EthereumJSONRPC.ParityTest do
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef"
value = 0
block_number = 1
block_number = 39
index = 0
created_contract_address_hash = "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461"
@ -43,48 +49,43 @@ defmodule EthereumJSONRPC.ParityTest do
[
%{
id: 0,
result: %{
"trace" => [
%{
"action" => %{
"from" => from_address_hash,
"gas" => integer_to_quantity(gas),
"init" => init,
"value" => integer_to_quantity(value)
},
"blockNumber" => block_number,
"index" => index,
"result" => %{
"address" => created_contract_address_hash,
"code" => created_contract_code,
"gasUsed" => integer_to_quantity(gas_used)
},
"traceAddress" => trace_address,
"transactionHash" => transaction_hash,
"transactionIndex" => transaction_index,
"type" => type
}
]
}
result: [
%{
"trace" => [
%{
"action" => %{
"from" => from_address_hash,
"gas" => integer_to_quantity(gas),
"init" => init,
"value" => integer_to_quantity(value)
},
"blockNumber" => block_number,
"index" => index,
"result" => %{
"address" => created_contract_address_hash,
"code" => created_contract_code,
"gasUsed" => integer_to_quantity(gas_used)
},
"traceAddress" => trace_address,
"type" => type
}
],
"transactionHash" => transaction_hash
}
]
}
]}
end)
end
assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[
%{
block_number: block_number,
hash_data: transaction_hash,
transaction_index: transaction_index
}
],
assert EthereumJSONRPC.Parity.fetch_block_internal_transactions(
[block_number],
json_rpc_named_arguments
) == {
:ok,
[
%{
block_number: 1,
block_number: block_number,
created_contract_address_hash: created_contract_address_hash,
created_contract_code: created_contract_code,
from_address_hash: from_address_hash,
@ -101,140 +102,6 @@ defmodule EthereumJSONRPC.ParityTest do
]
}
end
test "with all invalid transaction_params returns {:error, reasons}", %{
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,
error: %{
code: -32603,
message: "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
}
]}
end)
end
assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[
%{
block_number: 1,
hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001",
transaction_index: 0
}
],
json_rpc_named_arguments
) ==
{:error,
[
%{
code: -32603,
data: %{
"blockNumber" => 1,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001",
"transactionIndex" => 0
},
message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
]}
end
test "with a mix of valid and invalid transaction_params returns {:error, reasons}", %{
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,
result: %{
"trace" => []
}
},
%{
id: 1,
result: %{
"trace" => []
}
},
%{
id: 2,
error: %{
code: -32603,
message: "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
},
%{
id: 3,
error: %{
code: -32603,
message: "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
}
]}
end)
end
assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[
# start with :ok
%{
block_number: 1,
hash_data: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1",
transaction_index: 0
},
# :ok, :ok clause
%{
block_number: 34,
hash_data: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
transaction_index: 0
},
# :ok, :error clause
%{
block_number: 1,
hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001",
transaction_index: 0
},
# :error, :error clause
%{
block_number: 2,
hash_data: "0x0000000000000000000000000000000000000000000000000000000000000002",
transaction_index: 0
}
],
json_rpc_named_arguments
) ==
{:error,
[
%{
code: -32603,
data: %{
"blockNumber" => 1,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001",
"transactionIndex" => 0
},
message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
},
%{
code: -32603,
data: %{
"blockNumber" => 2,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000002",
"transactionIndex" => 0
},
message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
]}
end
end
describe "fetch_beneficiaries/1" do

@ -51,6 +51,8 @@ defmodule Explorer.Chain do
@default_paging_options %PagingOptions{page_size: 50}
@max_incoming_transactions_count 10_000
@typedoc """
The name of an association on the `t:Ecto.Schema.t/0`
"""
@ -417,6 +419,24 @@ defmodule Explorer.Chain do
Repo.aggregate(query, :count, :hash)
end
@spec address_to_incoming_transaction_count(Address.t()) :: non_neg_integer()
def address_to_incoming_transaction_count(%Address{hash: address_hash}) do
paging_options = %PagingOptions{page_size: @max_incoming_transactions_count}
base_query =
paging_options
|> fetch_transactions()
to_address_query =
base_query
|> where([t], t.to_address_hash == ^address_hash)
Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity)
end
@spec max_incoming_transactions_count() :: non_neg_integer()
def max_incoming_transactions_count, do: @max_incoming_transactions_count
@doc """
How many blocks have confirmed `block` based on the current `max_block_number`
@ -636,6 +656,22 @@ defmodule Explorer.Chain do
end
end
@spec token_contract_address_from_token_name(String.t()) :: {:ok, Hash.Address.t()} | {:error, :not_found}
def token_contract_address_from_token_name(name) when is_binary(name) do
query =
from(token in Token,
where: ilike(token.symbol, ^name),
select: token.contract_address_hash
)
query
|> Repo.one()
|> case do
nil -> {:error, :not_found}
hash -> {:ok, hash}
end
end
@doc """
Converts `t:Explorer.Chain.Address.t/0` `hash` to the `t:Explorer.Chain.Address.t/0` with that `hash`.
@ -1142,6 +1178,62 @@ defmodule Explorer.Chain do
|> Repo.stream_reduce(initial, reducer)
end
@doc """
Returns a stream of all blocks with unfetched internal transactions.
Only blocks with consensus are returned.
iex> non_consensus = insert(:block, consensus: false)
iex> unfetched = insert(:block)
iex> fetched = insert(:block, internal_transactions_indexed_at: DateTime.utc_now())
iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions(
...> [:number],
...> MapSet.new(),
...> fn %Explorer.Chain.Block{number: number}, acc ->
...> MapSet.put(acc, number)
...> end
...> )
iex> non_consensus.number in number_set
false
iex> unfetched.number in number_set
true
iex> fetched.hash in number_set
false
"""
@spec stream_blocks_with_unfetched_internal_transactions(
fields :: [
:consensus
| :difficulty
| :gas_limit
| :gas_used
| :hash
| :miner
| :miner_hash
| :nonce
| :number
| :parent_hash
| :size
| :timestamp
| :total_difficulty
| :transactions
| :internal_transactions_indexed_at
],
initial :: accumulator,
reducer :: (entry :: term(), accumulator -> accumulator)
) :: {:ok, accumulator}
when accumulator: term()
def stream_blocks_with_unfetched_internal_transactions(fields, initial, reducer) when is_function(reducer, 2) do
query =
from(
b in Block,
where: b.consensus and is_nil(b.internal_transactions_indexed_at),
select: ^fields
)
Repo.stream_reduce(query, initial, reducer)
end
@doc """
Returns a stream of all collated transactions with unfetched internal transactions.

@ -10,6 +10,8 @@ defmodule Explorer.Chain.Block do
alias Explorer.Chain.{Address, Gas, Hash, Transaction}
alias Explorer.Chain.Block.{Reward, SecondDegreeRelation}
@optional_attrs ~w(internal_transactions_indexed_at)a
@required_attrs ~w(consensus difficulty gas_limit gas_used hash miner_hash nonce number parent_hash size timestamp
total_difficulty)a
@ -45,6 +47,7 @@ defmodule Explorer.Chain.Block do
* `timestamp` - When the block was collated
* `total_difficulty` - the total `difficulty` of the chain until this block.
* `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block.
* `internal_transactions_indexed_at` - when `internal_transactions` were fetched by `Indexer`.
"""
@type t :: %__MODULE__{
consensus: boolean(),
@ -60,7 +63,8 @@ defmodule Explorer.Chain.Block do
size: non_neg_integer(),
timestamp: DateTime.t(),
total_difficulty: difficulty(),
transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()]
transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()],
internal_transactions_indexed_at: DateTime.t()
}
@primary_key {:hash, Hash.Full, autogenerate: false}
@ -74,6 +78,7 @@ defmodule Explorer.Chain.Block do
field(:size, :integer)
field(:timestamp, :utc_datetime_usec)
field(:total_difficulty, :decimal)
field(:internal_transactions_indexed_at, :utc_datetime_usec)
timestamps()
@ -95,7 +100,7 @@ defmodule Explorer.Chain.Block do
def changeset(%__MODULE__{} = block, attrs) do
block
|> cast(attrs, @required_attrs)
|> cast(attrs, @required_attrs ++ @optional_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:parent_hash)
|> unique_constraint(:hash, name: :blocks_pkey)

@ -249,6 +249,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
difficulty: fragment("EXCLUDED.difficulty"),
gas_limit: fragment("EXCLUDED.gas_limit"),
gas_used: fragment("EXCLUDED.gas_used"),
internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"),
miner_hash: fragment("EXCLUDED.miner_hash"),
nonce: fragment("EXCLUDED.nonce"),
number: fragment("EXCLUDED.number"),
@ -267,7 +268,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
fragment("EXCLUDED.miner_hash <> ?", block.miner_hash) or fragment("EXCLUDED.nonce <> ?", block.nonce) or
fragment("EXCLUDED.number <> ?", block.number) or fragment("EXCLUDED.parent_hash <> ?", block.parent_hash) or
fragment("EXCLUDED.size <> ?", block.size) or fragment("EXCLUDED.timestamp <> ?", block.timestamp) or
fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty)
fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty) or
fragment("EXCLUDED.internal_transactions_indexed_at <> ?", block.internal_transactions_indexed_at)
)
end

@ -6,7 +6,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
require Ecto.Query
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Hash, Import, InternalTransaction, Transaction}
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, Transaction}
alias Explorer.Chain.Import.Runner
import Ecto.Query, only: [from: 2]
@ -54,6 +54,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
|> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, _ ->
update_transactions(repo, changes_list, update_transactions_options)
end)
|> Multi.run(:internal_transactions_indexed_at_blocks, fn repo, _ ->
update_blocks(repo, changes_list, update_transactions_options)
end)
end
@impl Runner
@ -192,4 +195,37 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
{:error, %{exception: postgrex_error, transaction_hashes: ordered_transaction_hashes}}
end
end
defp update_blocks(repo, internal_transactions, %{
timeout: timeout,
timestamps: timestamps
})
when is_list(internal_transactions) do
ordered_block_numbers =
internal_transactions
|> MapSet.new(& &1.block_number)
|> Enum.sort()
query =
from(
b in Block,
where: b.number in ^ordered_block_numbers and b.consensus,
update: [
set: [
internal_transactions_indexed_at: ^timestamps.updated_at
]
]
)
block_count = Enum.count(ordered_block_numbers)
try do
{^block_count, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, block_numbers: ordered_block_numbers}}
end
end
end

@ -0,0 +1,10 @@
defmodule Explorer.Repo.Migrations.AddInternalTransactionsIndexedAtToBlocks do
use Ecto.Migration
def change do
alter table(:blocks) do
# `null` when `internal_transactions` has never been fetched
add(:internal_transactions_indexed_at, :utc_datetime_usec, null: true)
end
end
end

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.AddIndexSymoblInTokens do
use Ecto.Migration
def change do
create(index(:tokens, [:symbol]))
end
end

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.AddIndexToToAddressHash do
use Ecto.Migration
def change do
create(index(:transactions, [:to_address_hash]))
end
end

@ -49,7 +49,7 @@ defmodule Explorer.Chain.ImportTest do
internal_transactions: %{
params: [
%{
block_number: 35,
block_number: 37,
transaction_index: 0,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
@ -65,7 +65,7 @@ defmodule Explorer.Chain.ImportTest do
value: 0
},
%{
block_number: 35,
block_number: 37,
transaction_index: 1,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 1,
@ -519,7 +519,7 @@ defmodule Explorer.Chain.ImportTest do
from_address_hash = "0x8cc2e4b51b4340cb3727cffe3f1878756e732cee"
from_address = insert(:address, hash: from_address_hash)
block = insert(:block)
block = insert(:block, number: 37)
transaction_string_hash = "0x0705ea0a5b997d9aafd5c531e016d9aabe3297a28c0bd4ef005fe6ea329d301b"
@ -551,7 +551,7 @@ defmodule Explorer.Chain.ImportTest do
transaction_hash: transaction_string_hash,
type: "create",
value: 0,
block_number: 35,
block_number: 37,
transaction_index: 0
}
]
@ -569,7 +569,7 @@ defmodule Explorer.Chain.ImportTest do
address_hash = "0x1c494fa496f1cfd918b5ff190835af3aaf609899"
from_address = insert(:address, hash: address_hash)
block = insert(:block, consensus: true)
block = insert(:block, consensus: true, number: 37)
transaction =
:transaction
@ -578,6 +578,7 @@ defmodule Explorer.Chain.ImportTest do
internal_transacton =
insert(:internal_transaction,
block_number: 37,
transaction_hash: transaction.hash,
error: "Bad Instruction",
index: 0,
@ -677,7 +678,7 @@ defmodule Explorer.Chain.ImportTest do
internal_transactions: %{
params: [
%{
block_number: 35,
block_number: block_number,
transaction_index: 0,
transaction_hash: transaction_hash,
index: 0,
@ -768,7 +769,7 @@ defmodule Explorer.Chain.ImportTest do
internal_transactions: %{
params: [
%{
block_number: 35,
block_number: block_number,
call_type: "call",
created_contract_code: smart_contract_bytecode,
created_contract_address_hash: created_contract_address_hash,
@ -874,7 +875,7 @@ defmodule Explorer.Chain.ImportTest do
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "create",
value: 0,
block_number: 35,
block_number: 37,
transaction_index: 0
},
%{
@ -891,7 +892,7 @@ defmodule Explorer.Chain.ImportTest do
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "create",
value: 0,
block_number: 35,
block_number: 37,
transaction_index: 1
}
],
@ -1556,7 +1557,7 @@ defmodule Explorer.Chain.ImportTest do
index: 0,
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
block_number: 35,
block_number: block_number,
transaction_index: 0
)
],
@ -1824,7 +1825,7 @@ defmodule Explorer.Chain.ImportTest do
value: 0,
input: "0x",
error: error,
block_number: 35,
block_number: block_number,
transaction_index: 0
}
]

@ -597,6 +597,20 @@ defmodule Explorer.ChainTest do
end
end
describe "address_to_incoming_transaction_count/1" do
test "without transactions" do
address = insert(:address)
assert Chain.address_to_incoming_transaction_count(address) == 0
end
test "with transactions" do
%Transaction{to_address: to_address} = insert(:transaction)
assert Chain.address_to_incoming_transaction_count(to_address) == 1
end
end
describe "confirmations/1" do
test "with block.number == block_height " do
block = insert(:block)
@ -841,6 +855,21 @@ defmodule Explorer.ChainTest do
end
end
describe "token_contract_address_from_token_name/1" do
test "return not found if token doesn't exist" do
name = "AYR"
assert {:error, :not_found} = Chain.token_contract_address_from_token_name(name)
end
test "return the correct token if it exists" do
name = "AYR"
insert(:token, symbol: name)
assert {:ok, _} = Chain.token_contract_address_from_token_name(name)
end
end
describe "find_or_insert_address_from_hash/1" do
test "returns an address if it already exists" do
address = insert(:address)
@ -996,6 +1025,7 @@ defmodule Explorer.ChainTest do
internal_transactions: %{
params: [
%{
block_number: 37,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
trace_address: [],

@ -36,6 +36,7 @@ config :indexer,
memory_limit: 1 <<< 30
# config :indexer, Indexer.ReplacedTransaction.Supervisor, disabled?: true
# config :indexer, Indexer.Block.Reward.Supervisor, disabled?: true
config :indexer, Indexer.Tracer,
service: :indexer,

@ -10,9 +10,9 @@ config :indexer,
method_to_url: [
eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
],
http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Parity
],

@ -12,7 +12,7 @@ config :indexer,
trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
],
http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Parity
],

@ -150,7 +150,7 @@ defmodule Indexer.Block.Catchup.Fetcher do
async_import_block_rewards(block_reward_errors)
async_import_coin_balances(imported, options)
async_import_created_contract_codes(imported)
async_import_internal_transactions(imported, json_rpc_named_arguments)
async_import_internal_transactions(imported, Keyword.get(json_rpc_named_arguments, :variant))
async_import_tokens(imported)
async_import_token_balances(imported)
async_import_uncles(imported)
@ -180,28 +180,27 @@ defmodule Indexer.Block.Catchup.Fetcher do
defp async_import_created_contract_codes(_), do: :ok
defp async_import_internal_transactions(%{transactions: transactions}, json_rpc_named_arguments) do
transaction_data =
Enum.flat_map(transactions, fn
%Transaction{block_number: block_number, index: index, hash: hash, internal_transactions_indexed_at: nil} ->
[%{block_number: block_number, index: index, hash: hash}]
%Transaction{internal_transactions_indexed_at: %DateTime{}} ->
[]
end)
defp async_import_internal_transactions(%{blocks: blocks}, EthereumJSONRPC.Parity) do
blocks
|> Enum.map(fn %Chain.Block{number: block_number} -> %{number: block_number} end)
|> InternalTransaction.Fetcher.async_block_fetch(10_000)
end
filtered_transaction_data =
if Keyword.get(json_rpc_named_arguments, :variant) == EthereumJSONRPC.Geth do
{_, max_block_number} = Chain.fetch_min_and_max_block_numbers()
defp async_import_internal_transactions(%{transactions: transactions}, EthereumJSONRPC.Geth) do
{_, max_block_number} = Chain.fetch_min_and_max_block_numbers()
Enum.filter(transaction_data, fn %{block_number: block_number} ->
max_block_number - block_number < @geth_block_limit
end)
else
transaction_data
end
transactions
|> Enum.flat_map(fn
%Transaction{block_number: block_number, index: index, hash: hash, internal_transactions_indexed_at: nil} ->
[%{block_number: block_number, index: index, hash: hash}]
InternalTransaction.Fetcher.async_fetch(filtered_transaction_data, 10_000)
%Transaction{internal_transactions_indexed_at: %DateTime{}} ->
[]
end)
|> Enum.filter(fn %{block_number: block_number} ->
max_block_number - block_number < @geth_block_limit
end)
|> InternalTransaction.Fetcher.async_fetch(10_000)
end
defp async_import_internal_transactions(_, _), do: :ok

@ -165,6 +165,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
address_hash_to_fetched_balance_block_number: address_hash_to_block_number,
address_token_balances: %{params: address_token_balances_params},
addresses: %{params: addresses_params},
blocks: %{params: blocks_params},
block_rewards: block_rewards,
transactions: %{params: transactions_params},
token_transfers: %{params: token_transfers_params}
@ -179,6 +180,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
{:internal_transactions,
internal_transactions(block_fetcher, %{
addresses_params: addresses_params,
blocks_params: blocks_params,
token_transfers_params: token_transfers_params,
transactions_params: transactions_params
})},
@ -363,13 +365,25 @@ defmodule Indexer.Block.Realtime.Fetcher do
%Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments},
%{
addresses_params: addresses_params,
blocks_params: blocks_params,
token_transfers_params: token_transfers_params,
transactions_params: transactions_params
}
) do
case transactions_params
|> transactions_params_to_fetch_internal_transactions_params(token_transfers_params, json_rpc_named_arguments)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) do
json_rpc_named_arguments
|> Keyword.fetch!(:variant)
|> case do
EthereumJSONRPC.Parity ->
blocks_params
|> Enum.map(fn %{number: block_number} -> block_number end)
|> EthereumJSONRPC.fetch_block_internal_transactions(json_rpc_named_arguments)
_ ->
transactions_params
|> transactions_params_to_fetch_internal_transactions_params(token_transfers_params, json_rpc_named_arguments)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments)
end
|> case do
{:ok, internal_transactions_params} ->
merged_addresses_params =
%{internal_transactions: internal_transactions_params}

@ -18,6 +18,7 @@ defmodule Indexer.Block.Reward.Fetcher do
alias Explorer.Chain.{Block, Wei}
alias Indexer.Address.CoinBalances
alias Indexer.{AddressExtraction, BufferedTask, CoinBalance, Tracer}
alias Indexer.Block.Reward.Supervisor, as: BlockRewardSupervisor
@behaviour BufferedTask
@ -34,7 +35,11 @@ defmodule Indexer.Block.Reward.Fetcher do
"""
@spec async_fetch([Block.block_number()]) :: :ok
def async_fetch(block_numbers) when is_list(block_numbers) do
BufferedTask.buffer(__MODULE__, block_numbers)
if BlockRewardSupervisor.disabled?() do
:ok
else
BufferedTask.buffer(__MODULE__, block_numbers)
end
end
@doc false

@ -22,7 +22,15 @@ defmodule Indexer.Block.Reward.Supervisor do
end
def start_link(arguments, gen_server_options \\ []) do
Supervisor.start_link(__MODULE__, arguments, Keyword.put_new(gen_server_options, :name, __MODULE__))
if disabled?() do
:ignore
else
Supervisor.start_link(__MODULE__, arguments, Keyword.put_new(gen_server_options, :name, __MODULE__))
end
end
def disabled?() do
Application.get_env(:indexer, __MODULE__, [])[:disabled?] == true
end
@impl Supervisor

@ -50,6 +50,29 @@ defmodule Indexer.InternalTransaction.Fetcher do
BufferedTask.buffer(__MODULE__, entries, timeout)
end
@doc """
Asynchronously fetches internal transactions.
## Limiting Upstream Load
Internal transactions are an expensive upstream operation. The number of
results to fetch is configured by `@max_batch_size` and represents the number
of transaction hashes to request internal transactions in a single JSONRPC
request. Defaults to `#{@max_batch_size}`.
The `@max_concurrency` attribute configures the number of concurrent requests
of `@max_batch_size` to allow against the JSONRPC. Defaults to `#{@max_concurrency}`.
*Note*: The internal transactions for individual transactions cannot be paginated,
so the total number of internal transactions that could be produced is unknown.
"""
@spec async_block_fetch([%{required(:block_number) => Block.block_number()}]) :: :ok
def async_block_fetch(transactions_fields, timeout \\ 5000) when is_list(transactions_fields) do
entries = Enum.map(transactions_fields, &block_entry/1)
BufferedTask.buffer(__MODULE__, entries, timeout)
end
@doc false
def child_spec([init_options, gen_server_options]) do
{state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments)
@ -69,17 +92,31 @@ defmodule Indexer.InternalTransaction.Fetcher do
end
@impl BufferedTask
def init(initial, reducer, _) do
def init(initial, reducer, json_rpc_named_arguments) do
{:ok, final} =
Chain.stream_transactions_with_unfetched_internal_transactions(
[:block_number, :hash, :index],
initial,
fn transaction_fields, acc ->
transaction_fields
|> entry()
|> reducer.(acc)
end
)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
Chain.stream_blocks_with_unfetched_internal_transactions(
[:number],
initial,
fn block_fields, acc ->
block_fields
|> block_entry()
|> reducer.(acc)
end
)
_ ->
Chain.stream_transactions_with_unfetched_internal_transactions(
[:block_number, :hash, :index],
initial,
fn transaction_fields, acc ->
transaction_fields
|> entry()
|> reducer.(acc)
end
)
end
final
end
@ -93,6 +130,10 @@ defmodule Indexer.InternalTransaction.Fetcher do
%{block_number: block_number, hash_data: to_string(hash), transaction_index: index}
end
defp block_entry(%{number: block_number}) when is_integer(block_number) do
block_number
end
@impl BufferedTask
@decorate trace(
name: "fetch",
@ -101,16 +142,30 @@ defmodule Indexer.InternalTransaction.Fetcher do
tracer: Tracer
)
def run(entries, json_rpc_named_arguments) do
unique_entries = unique_entries(entries)
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
unique_entries =
case variant do
EthereumJSONRPC.Parity -> Enum.uniq(entries)
_ -> unique_entries(entries)
end
unique_entries_count = Enum.count(unique_entries)
Logger.metadata(count: unique_entries_count)
Logger.debug("fetching internal transactions for transactions")
unique_entries
|> Enum.map(&params/1)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments)
variant
|> case do
EthereumJSONRPC.Parity ->
unique_entries
|> EthereumJSONRPC.fetch_block_internal_transactions(json_rpc_named_arguments)
_ ->
unique_entries
|> Enum.map(&params/1)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments)
end
|> case do
{:ok, internal_transactions_params} ->
internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params)

@ -96,11 +96,6 @@ defmodule Indexer.Temporary.FailedCreatedAddresses do
|> Indexer.Code.Fetcher.run(json_rpc_named_arguments)
end)
:ok =
transaction
|> transaction_entry()
|> Indexer.InternalTransaction.Fetcher.run(json_rpc_named_arguments)
Logger.debug(
[
"Finished fixing transaction #{to_string(transaction.hash)}"

@ -114,16 +114,18 @@ defmodule Indexer.Block.FetcherTest do
|> expect(:json_rpc, fn [%{id: id, method: "trace_block", params: [^block_quantity]}], _options ->
{:ok, [%{id: id, result: []}]}
end)
|> expect(:json_rpc, fn [
%{
id: id,
jsonrpc: "2.0",
method: "eth_getBalance",
params: [^miner_hash, ^block_quantity]
}
],
_options ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
# async requests need to be grouped in one expect because the order is non-deterministic while multiple expect
# calls on the same name/arity are used in order
|> expect(:json_rpc, 2, fn json, _options ->
[request] = json
case request do
%{id: id, method: "eth_getBalance", params: [^miner_hash, ^block_quantity]} ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
%{id: id, method: "trace_replayBlockTransactions", params: [^block_quantity, ["trace"]]} ->
{:ok, [%{id: id, result: []}]}
end
end)
EthereumJSONRPC.Geth ->
@ -379,33 +381,37 @@ defmodule Indexer.Block.FetcherTest do
%{id: id, method: "eth_getBalance", params: [^from_address_hash, ^block_quantity]} ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0xd0d4a965ab52d8cd740000"}]}
%{id: id, method: "trace_replayTransaction", params: [^transaction_hash, ["trace"]]} ->
%{id: id, method: "trace_replayBlockTransactions", params: [^block_quantity, ["trace"]]} ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: %{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"callType" => "call",
"from" => from_address_hash,
"gas" => "0x475ec8",
"input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
"to" => to_address_hash,
"value" => "0x0"
},
"result" => %{"gasUsed" => "0x6c7a", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [],
"type" => "call"
}
],
"vmTrace" => nil
}
result: [
%{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"callType" => "call",
"from" => from_address_hash,
"gas" => "0x475ec8",
"input" =>
"0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
"to" => to_address_hash,
"value" => "0x0"
},
"result" => %{"gasUsed" => "0x6c7a", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [],
"type" => "call"
}
],
"transactionHash" => transaction_hash,
"vmTrace" => nil
}
]
}
]}
end

@ -24,7 +24,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do
|> put_in(
[:transport_options, :method_to_url],
eth_getBalance: "http://54.144.107.14:8545",
trace_replayTransaction: "http://54.144.107.14:8545",
trace_replayBlockTransactions: "http://54.144.107.14:8545",
trace_block: "http://54.144.107.14:8545"
)
@ -204,170 +204,184 @@ defmodule Indexer.Block.Realtime.FetcherTest do
responses = Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end)
{:ok, responses}
end)
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "trace_replayTransaction",
params: [
"0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8",
["trace"]
]
}
],
_ ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: %{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"callType" => "call",
"from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16",
"gas" => "0x383ad",
"input" =>
"0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005",
"to" => "0x698bf6943bab687b2756394624aa183f434f65da",
"value" => "0x1158e4f216242a000"
},
"result" => %{"gasUsed" => "0x23256", "output" => "0x"},
"subtraces" => 5,
"traceAddress" => [],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x36771",
"input" => "0x6352211e000000000000000000000000000000000000000000000000000000000000006c",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{
"gasUsed" => "0x495",
"output" => "0x00000000000000000000000040b18103537c0f15d5e137dd8ddd019b84949d16"
},
"subtraces" => 0,
"traceAddress" => [0],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x35acb",
"input" => "0x33f30a43000000000000000000000000000000000000000000000000000000000000006c",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{
"gasUsed" => "0x52d2",
"output" =>
"0x00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004f000000000000000000000000000000000000000000000000000000000000004d000000000000000000000000000000000000000000000000000000000000004b000000000000000000000000000000000000000000000000000000000000004f00000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000005b61df09000000000000000000000000000000000000000000000000000000005b61df5e000000000000000000000000000000000000000000000000000000005b61df8b000000000000000000000000000000000000000000000000000000005b61df2c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000fd000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054c65696c61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002566303430313037303331343330303332333036303933333235303131323036303730373131000000000000000000000000000000000000000000000000000000"
},
"subtraces" => 0,
"traceAddress" => [1],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x2fc79",
"input" => "0x1b8ef0bb000000000000000000000000000000000000000000000000000000000000006c",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{
"gasUsed" => "0x10f2",
"output" => "0x0000000000000000000000000000000000000000000000000000000000000013"
},
"subtraces" => 0,
"traceAddress" => [2],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x2e21f",
"input" =>
"0xcf5f87d0000000000000000000000000000000000000000000000000000000000000006c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{"gasUsed" => "0x1ca1", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [3],
"type" => "call"
},
|> expect(:json_rpc, 2, fn
[
%{
id: 0,
jsonrpc: "2.0",
method: "trace_replayBlockTransactions",
params: [
"0x3C3660",
["trace"]
]
},
%{
id: 1,
jsonrpc: "2.0",
method: "trace_replayBlockTransactions",
params: [
"0x3C365F",
["trace"]
]
}
],
_ ->
{:ok,
[
%{id: 0, jsonrpc: "2.0", result: []},
%{
id: 1,
jsonrpc: "2.0",
result: [
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x8fc",
"input" => "0x",
"to" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16",
"value" => "0x9184e72a000"
},
"result" => %{"gasUsed" => "0x0", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [4],
"type" => "call"
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"callType" => "call",
"from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16",
"gas" => "0x383ad",
"input" =>
"0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005",
"to" => "0x698bf6943bab687b2756394624aa183f434f65da",
"value" => "0x1158e4f216242a000"
},
"result" => %{"gasUsed" => "0x23256", "output" => "0x"},
"subtraces" => 5,
"traceAddress" => [],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x36771",
"input" => "0x6352211e000000000000000000000000000000000000000000000000000000000000006c",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{
"gasUsed" => "0x495",
"output" => "0x00000000000000000000000040b18103537c0f15d5e137dd8ddd019b84949d16"
},
"subtraces" => 0,
"traceAddress" => [0],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x35acb",
"input" => "0x33f30a43000000000000000000000000000000000000000000000000000000000000006c",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{
"gasUsed" => "0x52d2",
"output" =>
"0x00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004f000000000000000000000000000000000000000000000000000000000000004d000000000000000000000000000000000000000000000000000000000000004b000000000000000000000000000000000000000000000000000000000000004f00000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000005b61df09000000000000000000000000000000000000000000000000000000005b61df5e000000000000000000000000000000000000000000000000000000005b61df8b000000000000000000000000000000000000000000000000000000005b61df2c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000fd000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054c65696c61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002566303430313037303331343330303332333036303933333235303131323036303730373131000000000000000000000000000000000000000000000000000000"
},
"subtraces" => 0,
"traceAddress" => [1],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x2fc79",
"input" => "0x1b8ef0bb000000000000000000000000000000000000000000000000000000000000006c",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{
"gasUsed" => "0x10f2",
"output" => "0x0000000000000000000000000000000000000000000000000000000000000013"
},
"subtraces" => 0,
"traceAddress" => [2],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x2e21f",
"input" =>
"0xcf5f87d0000000000000000000000000000000000000000000000000000000000000006c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a",
"to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c",
"value" => "0x0"
},
"result" => %{"gasUsed" => "0x1ca1", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [3],
"type" => "call"
},
%{
"action" => %{
"callType" => "call",
"from" => "0x698bf6943bab687b2756394624aa183f434f65da",
"gas" => "0x8fc",
"input" => "0x",
"to" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16",
"value" => "0x9184e72a000"
},
"result" => %{"gasUsed" => "0x0", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [4],
"type" => "call"
}
],
"transactionHash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8",
"vmTrace" => nil
}
],
"vmTrace" => nil
]
}
}
]}
end)
|> expect(:json_rpc, fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", "0x3C365F"]
},
%{
id: 1,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"]
},
%{
id: 2,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
},
%{
id: 3,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
},
%{
id: 4,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"]
}
],
_ ->
{:ok,
[
%{id: 0, jsonrpc: "2.0", result: "0x49e3de5187cf037d127"},
%{id: 1, jsonrpc: "2.0", result: "0x148adc763b603291685"},
%{id: 2, jsonrpc: "2.0", result: "0x53474fa377a46000"},
%{id: 3, jsonrpc: "2.0", result: "0x53507afe51f28000"},
%{id: 4, jsonrpc: "2.0", result: "0x3e1a95d7517dc197108"}
]}
]}
[
%{
id: 0,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", "0x3C365F"]
},
%{
id: 1,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"]
},
%{
id: 2,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"]
},
%{
id: 3,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"]
},
%{
id: 4,
jsonrpc: "2.0",
method: "eth_getBalance",
params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"]
}
],
_ ->
{:ok,
[
%{id: 0, jsonrpc: "2.0", result: "0x49e3de5187cf037d127"},
%{id: 1, jsonrpc: "2.0", result: "0x148adc763b603291685"},
%{id: 2, jsonrpc: "2.0", result: "0x53474fa377a46000"},
%{id: 3, jsonrpc: "2.0", result: "0x53507afe51f28000"},
%{id: 4, jsonrpc: "2.0", result: "0x3e1a95d7517dc197108"}
]}
end)
end

@ -87,6 +87,7 @@ defmodule Indexer.InternalTransaction.FetcherTest do
) == []
end
@tag :no_parity
test "buffers collated transactions with unfetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
@ -104,6 +105,7 @@ defmodule Indexer.InternalTransaction.FetcherTest do
) == [{block.number, collated_unfetched_transaction.hash.bytes, collated_unfetched_transaction.index}]
end
@tag :no_parity
test "does not buffer collated transactions with fetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
@ -117,9 +119,36 @@ defmodule Indexer.InternalTransaction.FetcherTest do
json_rpc_named_arguments
) == []
end
@tag :no_geth
test "buffers blocks with unfetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
block = insert(:block)
assert InternalTransaction.Fetcher.init(
[],
fn block_number, acc -> [block_number | acc] end,
json_rpc_named_arguments
) == [block.number]
end
@tag :no_geth
test "does not buffer blocks with fetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
insert(:block, internal_transactions_indexed_at: DateTime.utc_now())
assert InternalTransaction.Fetcher.init(
[],
fn block_number, acc -> [block_number | acc] end,
json_rpc_named_arguments
) == []
end
end
describe "run/2" do
@tag :no_parity
test "duplicate transaction hashes are logged", %{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 ->
@ -227,6 +256,7 @@ defmodule Indexer.InternalTransaction.FetcherTest do
end
end
@tag :no_parity
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
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->

@ -7,7 +7,7 @@ defmodule Indexer.Temporary.FailedCreatedAddressesTest do
import Ecto.Query
alias Explorer.Repo
alias Explorer.Chain.{Address, Transaction}
alias Explorer.Chain.Address
alias Indexer.Temporary.FailedCreatedAddresses.Supervisor
alias Indexer.CoinBalance
@ -55,53 +55,6 @@ defmodule Indexer.Temporary.FailedCreatedAddressesTest do
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [_address, _block_quantity]}], _options ->
{:ok, [%{id: id, result: "0x0"}]}
end)
|> expect(: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" => "0x4bb278f3",
"value" => "0x0"
},
"result" => %{
"address" => "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75",
"code" =>
"0x6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"gasUsed" => "0xa535"
},
"subtraces" => 0,
"traceAddress" => [0],
"type" => "create"
}
],
"vmTrace" => nil
}
}
]}
end)
end
params = [json_rpc_named_arguments, [name: TestFailedCreatedAddresses]]
@ -112,20 +65,6 @@ defmodule Indexer.Temporary.FailedCreatedAddressesTest do
Process.sleep(3_000)
query =
from(t in Transaction,
where: t.hash == ^transaction.hash,
preload: [internal_transactions: :created_contract_address]
)
fetched_transaction = Repo.one(query)
assert Enum.count(fetched_transaction.internal_transactions) == 2
assert Enum.all?(fetched_transaction.internal_transactions, fn it ->
it.error && is_nil(it.created_contract_address_hash)
end)
fetched_address =
Repo.one(
from(a in Address,

Loading…
Cancel
Save