Merge branch 'master' into vb-first-trace-of-trivial-tx

pull/3000/head
Victor Baranov 5 years ago committed by GitHub
commit a44071cce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .circleci/config.yml
  2. 5
      .dialyzer-ignore
  3. 7
      CHANGELOG.md
  4. 7
      apps/block_scout_web/README.md
  5. 4
      apps/block_scout_web/config/dev.exs
  6. 5
      apps/block_scout_web/config/dev.secret.exs.example
  7. 9
      apps/block_scout_web/lib/block_scout_web/checksum_address.ex
  8. 3
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
  9. 3
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
  10. 6
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  11. 3
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  12. 10
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
  13. 38
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
  14. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex
  15. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex
  16. 8
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
  17. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_metatags.html.eex
  18. 6
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
  19. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/read_contract/index.html.eex
  20. 6
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  21. 26
      apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
  22. 6
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  23. 24
      apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex
  24. 1
      apps/block_scout_web/lib/block_scout_web/views/tokens/read_contract_view.ex
  25. 1
      apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
  26. 4
      apps/block_scout_web/priv/gettext/default.pot
  27. 4
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  28. 33
      apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs
  29. 15
      apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
  30. 14
      apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs
  31. 14
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  32. 14
      apps/block_scout_web/test/block_scout_web/schema/subscription/token_transfers_test.exs
  33. 34
      apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
  34. 29
      apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs
  35. 10
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  36. 8
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex
  37. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
  38. 123
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
  39. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex
  40. 23
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex
  41. 1
      apps/ethereum_jsonrpc/lib/rsk.ex
  42. 101
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs
  43. 7
      apps/explorer/README.md
  44. 2
      apps/explorer/config/config.exs
  45. 7
      apps/explorer/config/dev.exs
  46. 10
      apps/explorer/config/dev.secret.exs.example
  47. 2
      apps/explorer/config/prod.exs
  48. 9
      apps/explorer/config/test.exs
  49. 9
      apps/explorer/config/test.secret.exs.example
  50. 147
      apps/explorer/lib/explorer/chain.ex
  51. 2
      apps/explorer/lib/explorer/chain/import.ex
  52. 1
      apps/explorer/lib/explorer/chain/import/runner.ex
  53. 20
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  54. 65
      apps/explorer/lib/explorer/etherscan.ex
  55. 6
      apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
  56. 380
      apps/explorer/test/explorer/chain_test.exs
  57. 9
      apps/indexer/config/config.exs
  58. 2
      apps/indexer/config/dev/ganache.exs
  59. 2
      apps/indexer/config/dev/geth.exs
  60. 2
      apps/indexer/config/dev/parity.exs
  61. 2
      apps/indexer/config/dev/rsk.exs
  62. 2
      apps/indexer/config/prod/ganache.exs
  63. 2
      apps/indexer/config/prod/geth.exs
  64. 2
      apps/indexer/config/prod/parity.exs
  65. 2
      apps/indexer/config/prod/rsk.exs
  66. 30
      apps/indexer/lib/indexer/block/catchup/fetcher.ex
  67. 68
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  68. 21
      apps/indexer/lib/indexer/fetcher/internal_transaction.ex
  69. 5
      apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs
  70. 5
      apps/indexer/test/indexer/block/catchup/fetcher_test.exs
  71. 52
      apps/indexer/test/indexer/block/realtime/fetcher_test.exs
  72. 23
      docker/Makefile

@ -233,7 +233,7 @@ jobs:
eslint:
docker:
# Ensure .tool-versions matches
- image: circleci/node:12.15.0-browsers-legacy
- image: circleci/node:12.16.1-browsers-legacy
working_directory: ~/app
@ -275,7 +275,7 @@ jobs:
jest:
docker:
# Ensure .tool-versions matches
- image: circleci/node:12.15.0-browsers-legacy
- image: circleci/node:12.16.1-browsers-legacy
working_directory: ~/app

@ -2,15 +2,14 @@
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
lib/explorer/repo/prometheus_logger.ex:8
lib/block_scout_web/views/layout_view.ex:175
lib/explorer/smart_contract/publisher_worker.ex:6
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()
apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:175: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
lib/block_scout_web/views/layout_view.ex:172: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true'
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:324
lib/block_scout_web/views/layout_view.ex:146
lib/block_scout_web/views/layout_view.ex:142

@ -1,11 +1,18 @@
## Current
### Features
- [#3013](https://github.com/poanetwork/blockscout/pull/3013) - Raw trace of transaction on-demand
- [#3000](https://github.com/poanetwork/blockscout/pull/3000) - Get rid of storing of internal transactions for simple coin transfers
- [#2875](https://github.com/poanetwork/blockscout/pull/2875) - Save contract code from Parity genesis file
- [#2834](https://github.com/poanetwork/blockscout/pull/2834) - always redirect to checksummed hash
### Fixes
- [#3024](https://github.com/poanetwork/blockscout/pull/3024) - Fix pool size default value in config
- [#3021](https://github.com/poanetwork/blockscout/pull/3021), [#3022](https://github.com/poanetwork/blockscout/pull/3022) - Refine dev/test config
- [#3016](https://github.com/poanetwork/blockscout/pull/3016), [#3017](https://github.com/poanetwork/blockscout/pull/3017) - Fix token instance QR code data
- [#3014](https://github.com/poanetwork/blockscout/pull/3014) - Fix checksum address feature for tokens pages
- [#3012](https://github.com/poanetwork/blockscout/pull/3012) - Speedup token transfers list query
- [#3011](https://github.com/poanetwork/blockscout/pull/3011) - Revert realtime fetcher small skips feature
- [#3009](https://github.com/poanetwork/blockscout/pull/3009) - Fix broken export to CSV
- [#3007](https://github.com/poanetwork/blockscout/pull/3007) - Fix copy UTF8 tx input action
- [#2996](https://github.com/poanetwork/blockscout/pull/2996) - Fix awesomplete lib loading in Firefox

@ -4,9 +4,9 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we
## Machine Requirements
* Erlang/OTP 20.2+
* Elixir 1.5+
* Postgres 10.0
* Erlang/OTP 21+
* Elixir 1.9+
* Postgres 10.3
## Required Accounts
@ -21,7 +21,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we
To get BlockScout Web interface up and running locally:
* Setup `../explorer`
* Set up some default configuration with: `$ cp config/dev.secret.exs.example config/dev.secret.exs`
* Install Node.js dependencies with `$ cd assets && npm install && cd ..`
* Start Phoenix with `$ mix phx.server` (This can be run from this directory or the project root: the project root is recommended.)

@ -15,6 +15,8 @@ port =
end
config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base:
System.get_env("SECRET_KEY_BASE") || "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5",
http: [
port: port || 4000
],
@ -79,5 +81,3 @@ config :logger, :block_scout_web,
# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20
import_config "dev.secret.exs"

@ -1,5 +0,0 @@
use Mix.Config
# Configures the endpoint
config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base: "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5"

@ -31,7 +31,14 @@ defmodule BlockScoutWeb.ChecksumAddress do
if checksummed_hash != id do
conn = %{conn | params: Map.merge(conn.params, %{param_name => checksummed_hash})}
new_path = String.replace(conn.request_path, id, checksummed_hash)
path_with_checksummed_address = String.replace(conn.request_path, id, checksummed_hash)
new_path =
if conn.query_string != "" do
path_with_checksummed_address <> "?" <> conn.query_string
else
path_with_checksummed_address
end
conn
|> Controller.redirect(to: new_path)

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.HolderController do
alias BlockScoutWeb.Tokens.HolderView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View
import BlockScoutWeb.Chain,
@ -52,7 +53,7 @@ defmodule BlockScoutWeb.Tokens.HolderController do
"index.html",
current_path: current_path(conn),
token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)})
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
)
else
:error ->

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
alias BlockScoutWeb.Tokens.TransferView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
@ -24,7 +25,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
conn,
:index,
token_id,
token.contract_address_hash,
Address.checksum(token.contract_address_hash),
Map.delete(next_page_params, "type")
)
end

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
alias BlockScoutWeb.Tokens.InventoryView
alias Explorer.{Chain, Market}
alias Explorer.Chain.TokenTransfer
alias Explorer.Chain.{Address, TokenTransfer}
alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0]
@ -28,7 +28,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
token_inventory_path(
conn,
:index,
address_hash_string,
Address.checksum(address_hash_string),
Map.delete(next_page_params, "type")
)
end
@ -71,7 +71,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
"index.html",
current_path: current_path(conn),
token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)})
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
)
else
:error ->

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
def index(conn, %{"token_id" => address_hash_string}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
@ -13,7 +14,7 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
conn,
"index.html",
token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)})
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
)
else
:not_found ->

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.TransferController do
alias BlockScoutWeb.Tokens.TransferView
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
@ -19,7 +20,12 @@ defmodule BlockScoutWeb.Tokens.TransferController do
nil
next_page_params ->
token_transfer_path(conn, :index, token.contract_address_hash, Map.delete(next_page_params, "type"))
token_transfer_path(
conn,
:index,
Address.checksum(token.contract_address_hash),
Map.delete(next_page_params, "type")
)
end
transfers_json =
@ -51,7 +57,7 @@ defmodule BlockScoutWeb.Tokens.TransferController do
render(
conn,
"index.html",
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)}),
counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}),
current_path: current_path(conn),
token: Market.add_price(token)
)

@ -2,7 +2,10 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.TransactionView
alias EthereumJSONRPC
alias Explorer.{Chain, Market}
alias Explorer.Chain.Import
alias Explorer.Chain.Import.Runner.InternalTransactions
alias Explorer.ExchangeRates.Token
def index(conn, %{"transaction_id" => hash_string}) do
@ -19,7 +22,40 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
:token_transfers => :optional
}
) do
internal_transactions = Chain.transaction_to_internal_transactions(hash)
internal_transactions = Chain.all_transaction_to_internal_transactions(hash)
first_trace_exists =
Enum.find_index(internal_transactions, fn trace ->
trace.index == 0
end)
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
internal_transactions =
if first_trace_exists do
internal_transactions
else
{:ok, first_trace_params} =
Chain.fetch_first_trace(
[
%{
block_hash: transaction.block_hash,
block_number: transaction.block_number,
hash_data: hash_string,
transaction_index: transaction.index
}
],
json_rpc_named_arguments
)
InternalTransactions.run_insert_only(first_trace_params, %{
timeout: :infinity,
timestamps: Import.timestamps(),
internal_transactions: %{params: first_trace_params}
})
Chain.all_transaction_to_internal_transactions(hash)
end
render(
conn,

@ -18,7 +18,7 @@
data-placement="top"
data-toggle="tooltip"
title='<%= gettext("View Contract") %>'
onclick='<%= "location='#{address_path(@conn, :show, @token.contract_address_hash)}'" %>'
onclick='<%= "location='#{address_path(@conn, :show, Address.checksum(@token.contract_address_hash))}'" %>'
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32" transform="translate(8,8)">
<path fill-rule="evenodd" d="M15 16H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1zM14 2H2v12h12V2z"/>

@ -8,7 +8,7 @@
<%= if @token_instance.instance do %>
<%= link(
gettext("Metadata"),
to: token_instance_metadata_path(@conn, :index, @token.contract_address_hash, to_string(@token_instance.token_id)),
to: token_instance_metadata_path(@conn, :index, Address.checksum(@token.contract_address_hash), to_string(@token_instance.token_id)),
class: "card-tab #{tab_status("metadata", @conn.request_path)}")
%>
<% end %>

@ -11,7 +11,7 @@
<% end %>
<!-- buttons -->
<span class="overview-title-buttons float-right">
<span class="overview-title-item" data-clipboard-text="<%= @token.contract_address_hash %>">
<span class="overview-title-item" data-clipboard-text="<%= Address.checksum(@token.contract_address_hash) %>">
<span
aria-label='<%= gettext("Copy Address") %>'
class="btn-copy-icon"
@ -44,12 +44,12 @@
</span>
</h1>
<h3><%= to_string(@token.contract_address_hash) %></h3>
<h3><%= Address.checksum(@token.contract_address_hash) %></h3>
<div class="d-flex flex-column flex-md-row justify-content-start text-muted">
<span class="mr-4 mb-3 mb-md-0">
<%= link to:
address_path(@conn, :show, @token.contract_address_hash),
address_path(@conn, :show, Address.checksum(@token.contract_address_hash)),
"data-test": "token_contract_address"
do %>
<%= gettext "View Contract" %>
@ -108,7 +108,7 @@
</button>
</div>
<div class="modal-body">
<img src="data:image/png;base64, <%= BlockScoutWeb.AddressView.qr_code(@token.contract_address_hash) %> " class="qr-code" alt="qr_code" title="<%= @token.contract_address %>" />
<img src="data:image/png;base64, <%= BlockScoutWeb.AddressView.qr_code(Address.checksum(@token.contract_address_hash)) %> " class="qr-code" alt="qr_code" title="<%= @token.contract_address %>" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"><%= gettext "Close" %></button>

@ -2,4 +2,4 @@
<%= "#{token_name(@token)} (#{token_symbol(@token)}) - #{LayoutView.subnetwork_title()} - BlockScout" %>
</title>
<meta name="description" content="<%= token_name(@token) %>, balances, and analytics on the <%= LayoutView.network_title() %>">
<meta name="keywords" content="<%= "#{token_name(@token)}, #{token_symbol(@token)}, #{to_string(@token.contract_address_hash)}, #{LayoutView.network_title()}, #{Explorer.coin()}" %>">
<meta name="keywords" content="<%= "#{token_name(@token)}, #{token_symbol(@token)}, #{Address.checksum(@token.contract_address_hash)}, #{LayoutView.network_title()}, #{Explorer.coin()}" %>">

@ -9,21 +9,21 @@
gettext("Token Holders"),
class: "card-tab #{tab_status("token_holders", @conn.request_path)}",
"data-test": "token_holders_tab",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
to: token_holder_path(@conn, :index, Address.checksum(@token.contract_address_hash))
)
%>
<%= if display_inventory?(@token) do %>
<%= link(
gettext("Inventory"),
class: "card-tab #{tab_status("inventory", @conn.request_path)}",
to: token_inventory_path(@conn, :index, @token.contract_address_hash)
to: token_inventory_path(@conn, :index, Address.checksum(@token.contract_address_hash))
)
%>
<% end %>
<%= if smart_contract_with_read_only_functions?(@token) do %>
<%= link(
gettext("Read Contract"),
to: token_read_contract_path(@conn, :index, @token.contract_address_hash),
to: token_read_contract_path(@conn, :index, Address.checksum(@token.contract_address_hash)),
class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%>
<% end %>

@ -11,7 +11,7 @@
<div class="card">
<%= render OverviewView, "_tabs.html", assigns %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@token.contract_address.hash) %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div class="card-body" data-smart-contract-functions data-hash="<%= Address.checksum(@token.contract_address.hash) %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div class="tile tile-muted text-center">
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>

@ -8,7 +8,7 @@
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @token_transfer.transaction_hash %>
<span class="text-nowrap">
<%= link to: address_token_transfers_path(@conn, :index, to_string(@token_transfer.from_address), to_string(@token.contract_address_hash)), "data-test": "address_hash_link" do %>
<%= link to: address_token_transfers_path(@conn, :index, Address.checksum(@token_transfer.from_address), Address.checksum(@token.contract_address_hash)), "data-test": "address_hash_link" do %>
<%= render(
BlockScoutWeb.AddressView,
"_responsive_hash.html",
@ -18,7 +18,7 @@
) %>
<% end %>
&rarr;
<%= link to: address_token_transfers_path(@conn, :index, to_string(@token_transfer.to_address), to_string(@token.contract_address_hash)), "data-test": "address_hash_link" do %>
<%= link to: address_token_transfers_path(@conn, :index, Address.checksum(@token_transfer.to_address), Address.checksum(@token.contract_address_hash)), "data-test": "address_hash_link" do %>
<%= render(
BlockScoutWeb.AddressView,
"_responsive_hash.html",
@ -32,7 +32,7 @@
<span class="tile-title">
<%= case token_transfer_amount(@token_transfer) do %>
<% {:ok, :erc721_instance} -> %>
<%= "TokenID ["%><%= link(@token_transfer.token_id, to: token_instance_path(@conn, :show, @token_transfer.token.contract_address_hash, to_string(@token_transfer.token_id))) %><%= "]" %>
<%= "TokenID ["%><%= link(@token_transfer.token_id, to: token_instance_path(@conn, :show, Address.checksum(@token_transfer.token.contract_address_hash), to_string(@token_transfer.token_id))) %><%= "]" %>
<% {:ok, value} -> %>
<%= value %>
<% end %>

@ -34,7 +34,21 @@ defmodule BlockScoutWeb.APIDocsView do
end)
end
def blockscout_url(is_api) do
def blockscout_url(set_path) when set_path == false do
url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
host = url_params[:host]
scheme = Keyword.get(url_params, :scheme, "http")
if host != "localhost" do
"#{scheme}://#{host}"
else
port = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:http][:port]
"#{scheme}://#{host}:#{to_string(port)}"
end
end
def blockscout_url(set_path, is_api) when set_path == true do
url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
host = url_params[:host]
@ -57,17 +71,19 @@ defmodule BlockScoutWeb.APIDocsView do
def api_url do
is_api = true
set_path = true
is_api
|> blockscout_url()
set_path
|> blockscout_url(is_api)
|> Path.join("api")
end
def eth_rpc_api_url do
is_api = true
set_path = true
is_api
|> blockscout_url()
set_path
|> blockscout_url(is_api)
|> Path.join("api/eth_rpc")
end
end

@ -38,10 +38,6 @@ defmodule BlockScoutWeb.LayoutView do
alias BlockScoutWeb.SocialMedia
def network_icon_partial do
Keyword.get(application_config(), :network_icon) || "_network_icon.html"
end
def logo do
Keyword.get(application_config(), :logo) || "/images/blockscout_logo.svg"
end
@ -52,7 +48,7 @@ defmodule BlockScoutWeb.LayoutView do
end
def subnetwork_title do
Keyword.get(application_config(), :subnetwork) || "Sokol Testnet"
Keyword.get(application_config(), :subnetwork) || "POA Sokol"
end
def network_title do

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
alias BlockScoutWeb.CurrencyHelpers
alias Explorer.Chain.{Address, SmartContract, Token}
import BlockScoutWeb.APIDocsView, only: [blockscout_url: 1]
import BlockScoutWeb.APIDocsView, only: [blockscout_url: 1, blockscout_url: 2]
@tabs ["token_transfers", "metadata"]
@ -55,8 +55,26 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
def qr_code(conn, token_id, hash) do
token_instance_path = token_instance_path(conn, :show, to_string(hash), to_string(token_id))
is_api = false
url = Path.join(blockscout_url(is_api), token_instance_path)
url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
api_path = url_params[:api_path]
path = url_params[:path]
url_prefix =
if String.length(path) > 0 && path != "/" do
set_path = false
blockscout_url(set_path)
else
if String.length(api_path) > 0 && api_path != "/" do
is_api = true
set_path = true
blockscout_url(set_path, is_api)
else
set_path = false
blockscout_url(set_path)
end
end
url = Path.join(url_prefix, token_instance_path)
url
|> QRCode.to_png()

@ -2,4 +2,5 @@ defmodule BlockScoutWeb.Tokens.ReadContractView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
alias Explorer.Chain.Address
end

@ -2,4 +2,5 @@ defmodule BlockScoutWeb.Tokens.TransferView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
alias Explorer.Chain.Address
end

@ -898,7 +898,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:308
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:72
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:90
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:314
msgid "Token Transfers"
@ -1774,7 +1774,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:73
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:91
msgid "Metadata"
msgstr ""

@ -898,7 +898,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:308
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:72
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:90
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:314
msgid "Token Transfers"
@ -1774,7 +1774,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:73
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:91
msgid "Metadata"
msgstr ""

@ -3,10 +3,26 @@ defmodule BlockScoutWeb.AddressControllerTest do
# ETS tables are shared in `Explorer.Counters.*`
async: false
import Mox
alias Explorer.Chain.Address
alias Explorer.Counters.AddressesCounter
describe "GET index/2" do
setup :set_mox_global
setup do
# Use TestSource mock for this test set
configuration = Application.get_env(:block_scout_web, :show_percentage)
Application.put_env(:block_scout_web, :show_percentage, false)
:ok
on_exit(fn ->
Application.put_env(:block_scout_web, :show_percentage, configuration)
end)
end
test "returns top addresses", %{conn: conn} do
address_hashes =
4..1
@ -38,8 +54,21 @@ defmodule BlockScoutWeb.AddressControllerTest do
end
describe "GET show/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "redirects to address/:address_id/transactions", %{conn: conn} do
insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
insert(:address, hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
conn = get(conn, "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
@ -48,7 +77,7 @@ defmodule BlockScoutWeb.AddressControllerTest do
end
describe "GET address_counters/2" do
test "returns address counters" do
test "returns address counters", %{conn: conn} do
address = insert(:address)
conn = get(conn, "/address_counters", %{"id" => Address.checksum(address.hash)})

@ -4,7 +4,22 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))

@ -2,10 +2,24 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
use BlockScoutWeb.ConnCase, async: true
import BlockScoutWeb.WebRouter.Helpers, only: [address_token_path: 3]
import Mox
alias Explorer.Chain.{Address, Token}
describe "GET index/2" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_token_path(conn, :index, "invalid_address"))

@ -2,11 +2,25 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
use BlockScoutWeb.ConnCase, async: true
import BlockScoutWeb.WebRouter.Helpers, only: [address_transaction_path: 3, address_transaction_path: 4]
import Mox
alias Explorer.Chain.{Address, Transaction}
alias Explorer.ExchangeRates.Token
describe "GET index/2" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_transaction_path(conn, :index, "invalid_address"))

@ -1,9 +1,23 @@
defmodule BlockScoutWeb.Schema.Subscription.TokenTransfersTest do
use BlockScoutWeb.SubscriptionCase
import Mox
alias BlockScoutWeb.Notifier
describe "token_transfers field" do
setup :set_mox_global
setup do
configuration = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, pubsub: [name: BlockScoutWeb.PubSub])
:ok
on_exit(fn ->
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, configuration)
end)
end
test "with valid argument, returns all expected fields", %{socket: socket} do
transaction = insert(:transaction)
token_transfer = insert(:token_transfer, transaction: transaction)

@ -38,6 +38,40 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
end
end
describe "blockscout_url/2" do
test "set_path = true returns url with path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/eth/mainnet"]
)
assert APIDocsView.blockscout_url(true, true) == "https://blockscout.com/eth/mainnet"
end
test "set_path = false returns url w/out path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/eth/mainnet"]
)
assert APIDocsView.blockscout_url(false) == "https://blockscout.com"
end
test "set_path = true is_api returns url with api_path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/"]
)
assert APIDocsView.blockscout_url(true, true) == "https://blockscout.com/eth/mainnet"
end
test "set_path = true is_api returns url with path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", api_path: "/eth/mainnet", path: "/eth/mainnet2"]
)
assert APIDocsView.blockscout_url(true, false) == "https://blockscout.com/eth/mainnet2"
end
end
describe "eth_rpc_api_url/1" do
setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)

@ -13,18 +13,6 @@ defmodule BlockScoutWeb.LayoutViewTest do
end)
end
describe "network_icon_partial/0" do
test "use the enviroment icon when it's configured" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, network_icon: "custom_icon")
assert LayoutView.network_icon_partial() == "custom_icon"
end
test "use the default icon when there is no env configured for it" do
assert LayoutView.network_icon_partial() == "_network_icon.html"
end
end
describe "logo/0" do
test "use the enviroment logo when it's configured" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, logo: "custom/logo.png")
@ -45,7 +33,7 @@ defmodule BlockScoutWeb.LayoutViewTest do
end
test "use the default subnetwork title when there is no env configured for it" do
assert LayoutView.subnetwork_title() == "Sokol Testnet"
assert LayoutView.subnetwork_title() == "POA Sokol"
end
end
@ -119,11 +107,6 @@ defmodule BlockScoutWeb.LayoutViewTest do
url: "https://blockscout.com/rsk/mainnet",
other?: true
},
%{
title: "POA Sokol",
url: "https://blockscout.com/poa/sokol",
test_net?: true
},
%{
title: "LUKSO L14 testnet",
url: "https://blockscout.com/lukso/l14",
@ -157,11 +140,6 @@ defmodule BlockScoutWeb.LayoutViewTest do
Application.put_env(:block_scout_web, :other_networks, @supported_chains_pattern)
assert LayoutView.test_nets(LayoutView.other_networks()) == [
%{
title: "POA Sokol",
url: "https://blockscout.com/poa/sokol",
test_net?: true
},
%{
title: "LUKSO L14 testnet",
url: "https://blockscout.com/lukso/l14",
@ -185,11 +163,6 @@ defmodule BlockScoutWeb.LayoutViewTest do
title: "RSK Mainnet",
url: "https://blockscout.com/rsk/mainnet",
other?: true
},
%{
title: "POA Sokol",
url: "https://blockscout.com/poa/sokol",
test_net?: true
}
]
end

@ -301,6 +301,16 @@ defmodule EthereumJSONRPC do
)
end
@doc """
Retrieves traces from variant API.
"""
def fetch_first_trace(params_list, json_rpc_named_arguments) when is_list(params_list) do
Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_first_trace(
params_list,
json_rpc_named_arguments
)
end
@doc """
Fetches pending transactions from variant API.
"""

@ -36,4 +36,12 @@ defmodule EthereumJSONRPC.Ganache do
"""
@impl EthereumJSONRPC.Variant
def fetch_pending_transactions(_json_rpc_named_arguments), do: :ignore
@doc """
Traces are not supported currently for Ganache.
To signal to the caller that fetching is not supported, `:ignore` is returned.
"""
@impl EthereumJSONRPC.Variant
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
end

@ -37,6 +37,12 @@ defmodule EthereumJSONRPC.Geth do
end
end
@doc """
Fetches the first trace from the Parity trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
@doc """
Internal transaction fetching for entire blocks is not currently supported for Geth.

@ -49,6 +49,24 @@ defmodule EthereumJSONRPC.Parity do
end
end
@impl EthereumJSONRPC.Variant
def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do
id_to_params = id_to_params(transactions_params)
with {:ok, responses} <-
id_to_params
|> trace_replay_transaction_requests()
|> json_rpc(json_rpc_named_arguments) do
{:ok, [first_trace]} = trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params)
%{block_hash: block_hash} =
transactions_params
|> Enum.at(0)
{:ok, [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]}
end
end
@doc """
Fetches the pending transactions from the Parity node.
@ -167,4 +185,109 @@ defmodule EthereumJSONRPC.Parity do
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
def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do
params =
traces
|> Traces.to_elixir()
|> Traces.elixir_to_params()
{:ok, params}
end
end
defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params)
when is_list(responses) and is_map(id_to_params) do
responses
|> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params))
|> Enum.reduce(
{:ok, []},
fn
{:ok, traces}, {:ok, acc_traces_list} ->
{:ok, [traces | acc_traces_list]}
{:ok, _}, {:error, _} = acc_error ->
acc_error
{:error, reason}, {:ok, _} ->
{:error, [reason]}
{:error, reason}, {:error, acc_reason} ->
{:error, [reason | acc_reason]}
end
)
|> case do
{:ok, traces_list} ->
traces =
traces_list
|> Enum.reverse()
|> List.flatten()
{:ok, traces}
{:error, reverse_reasons} ->
reasons = Enum.reverse(reverse_reasons)
{:error, reasons}
end
end
defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params)
when is_list(traces) and is_map(id_to_params) do
%{
block_hash: block_hash,
block_number: block_number,
hash_data: transaction_hash,
transaction_index: transaction_index
} = Map.fetch!(id_to_params, id)
first_trace =
traces
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
"blockHash" => block_hash,
"blockNumber" => block_number,
"index" => index,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
})
end)
|> Enum.filter(fn trace ->
Map.get(trace, "index") == 0
end)
{:ok, first_trace}
end
defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params)
when is_map(id_to_params) do
%{
block_hash: block_hash,
block_number: block_number,
hash_data: transaction_hash,
transaction_index: transaction_index
} = Map.fetch!(id_to_params, id)
annotated_error =
Map.put(error, :data, %{
"blockHash" => block_hash,
"blockNumber" => block_number,
"transactionIndex" => transaction_index,
"transactionHash" => transaction_hash
})
{: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})
end)
end
defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
end
end

@ -407,6 +407,7 @@ defmodule EthereumJSONRPC.Parity.Trace do
...> "value" => "0x2386f26fc10000"
...> },
...> "blockNumber" => 1,
...> "blockHash" => "0x940ec4bab528861b5c5904c8d143d466a2b237e4b8c9bc96201dfde037d185f2",
...> "error" => "Reverted",
...> "index" => 0,
...> "subtraces" => 7,
@ -426,6 +427,7 @@ defmodule EthereumJSONRPC.Parity.Trace do
"value" => 10000000000000000
},
"blockNumber" => 1,
"blockHash" => "0x940ec4bab528861b5c5904c8d143d466a2b237e4b8c9bc96201dfde037d185f2",
"error" => "Reverted",
"index" => 0,
"subtraces" => 7,
@ -450,7 +452,7 @@ defmodule EthereumJSONRPC.Parity.Trace do
# subtraces is an actual integer in JSON and not hex-encoded
# traceAddress is a list of actual integers, not a list of hex-encoded
defp entry_to_elixir({key, _} = entry)
when key in ~w(subtraces traceAddress transactionHash type output),
when key in ~w(subtraces traceAddress transactionHash blockHash type output),
do: entry
defp entry_to_elixir({"action" = key, action}) do

@ -12,6 +12,7 @@ defmodule EthereumJSONRPC.Variant do
@type t :: module
@type internal_transaction_params :: map()
@type raw_trace_params :: map()
@doc """
Fetch the block reward contract beneficiaries for a given blocks from the variant of the Ethereum JSONRPC API.
@ -71,4 +72,26 @@ defmodule EthereumJSONRPC.Variant do
"""
@callback fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) ::
{:ok, [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 retrieve first trace of transaction
## Returns
* `{:ok, raw_trace_params}` - first trace was successfully retrieved for transaction
* `{:error, reason}` - there was one or more errors with `reason` in extraction trace of transaction
* `:ignore` - the variant does not support extraction of trace.
"""
@callback fetch_first_trace(
[
%{
hash_data: EthereumJSONRPC.hash(),
block_hash: EthereumJSONRPC.hash(),
block_number: EthereumJSONRPC.block_number(),
transaction_index: Integer
}
],
EthereumJSONRPC.json_rpc_named_arguments()
) :: {:ok, [raw_trace_params]} | {:error, reason :: term} | :ignore
end

@ -9,4 +9,5 @@ defmodule EthereumJSONRPC.RSK do
def fetch_pending_transactions(_), do: :ignore
def fetch_block_internal_transactions(_block_numbers, _json_rpc_named_arguments), do: :ignore
def fetch_beneficiaries(_, _), do: :ignore
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
end

@ -104,6 +104,107 @@ defmodule EthereumJSONRPC.ParityTest do
end
end
describe "fetch_first_trace/2" do
test "returns {:ok, first_trace_params}", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
gas = 4_533_872
init =
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef"
value = 0
block_number = 39
block_hash = "0x74c72ccabcb98b7ebbd7b31de938212b7e8814a002263b6569564e944d88f51f"
index = 0
created_contract_address_hash = "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461"
created_contract_code =
"0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029"
gas_used = 382_953
trace_address = []
transaction_hash = "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1"
transaction_index = 0
type = "create"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
result: %{
"output" => "0x",
"stateDiff" => nil,
"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_first_trace(
[
%{
hash_data: transaction_hash,
block_hash: block_hash,
block_number: block_number,
transaction_index: transaction_index
}
],
json_rpc_named_arguments
) == {
:ok,
[
%{
first_trace: %{
block_number: block_number,
created_contract_address_hash: created_contract_address_hash,
created_contract_code: created_contract_code,
from_address_hash: from_address_hash,
gas: gas,
gas_used: gas_used,
index: index,
init: init,
trace_address: trace_address,
transaction_hash: transaction_hash,
type: type,
value: value,
transaction_index: transaction_index
},
block_hash: block_hash,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.Mox,
transport_options: [],
variant: EthereumJSONRPC.Parity
]
}
]
}
end
end
describe "fetch_beneficiaries/1" do
test "with valid block range, returns {:ok, addresses}", %{
json_rpc_named_arguments: json_rpc_named_arguments

@ -5,9 +5,9 @@ This is a tool for inspecting and analyzing the POA Network blockchain.
## Machine Requirements
* Erlang/OTP 20.2+
* Elixir 1.5+
* Postgres 10.0
* Erlang/OTP 21+
* Elixir 1.9+
* Postgres 10.3
## Required Accounts
@ -21,7 +21,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain.
To get BlockScout up and running locally:
* Set up some default configuration with: `$ cp config/dev.secret.exs.example config/dev.secret.exs`
* Install dependencies with `$ mix do deps.get, local.rebar, deps.compile, compile`
* Create and migrate your database with `$ mix ecto.create && mix ecto.migrate`
* Run IEx (Interactive Elixir) to access the index and explore: `$ iex -S mix`

@ -45,7 +45,7 @@ config :explorer, Explorer.ChainSpec.GenesisData,
enabled: true,
chain_spec_path: System.get_env("CHAIN_SPEC_PATH"),
emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"),
rewards_contract_address: System.get_env("REWARDS_CONTRACT_ADDRESS", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5")
rewards_contract_address: System.get_env("REWARDS_CONTRACT", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5")
config :explorer, Explorer.Chain.Cache.BlockNumber,
enabled: true,

@ -2,9 +2,8 @@ use Mix.Config
# Configure your database
config :explorer, Explorer.Repo,
database: "explorer_dev",
hostname: "localhost",
pool_size: 20,
url: System.get_env("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE", "50")),
timeout: :timer.seconds(80)
config :explorer, Explorer.Tracer, env: "dev", disabled?: true
@ -23,8 +22,6 @@ config :logger, :token_instances,
path: Path.absname("logs/dev/explorer/tokens/token_instances.log"),
metadata_filter: [fetcher: :token_instances]
import_config "dev.secret.exs"
variant =
if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do
"ganache"

@ -1,10 +0,0 @@
use Mix.Config
# Configure your database
config :explorer, Explorer.Repo,
database: "explorer_dev",
hostname: "localhost",
username: "postgres",
password: "<REPLACE WITH THE PASSWORD YOU CHOSE>",
pool_size: 20,
timeout: 80_000

@ -3,7 +3,7 @@ use Mix.Config
# Configures the database
config :explorer, Explorer.Repo,
url: System.get_env("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
pool_size: String.to_integer(System.get_env("POOL_SIZE", "50")),
ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"),
prepare: :unnamed,
timeout: :timer.seconds(60)

@ -31,15 +31,6 @@ config :logger, :explorer,
level: :warn,
path: Path.absname("logs/test/explorer.log")
secret_file =
__ENV__.file
|> Path.dirname()
|> Path.join("test.secret.exs")
if File.exists?(secret_file) do
import_config secret_file
end
config :explorer, Explorer.ExchangeRates.Source.TransactionAndLog,
secondary_source: Explorer.ExchangeRates.Source.OneCoinSource

@ -1,9 +0,0 @@
use Mix.Config
# Configure your database
config :explorer, Explorer.Repo,
database: "explorer_test",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox,
port: "5432",
ownership_timeout: 60_000

@ -21,12 +21,14 @@ defmodule Explorer.Chain do
where: 3
]
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2]
alias ABI.TypeDecoder
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi}
alias Explorer.Chain
alias Explorer.Chain.{
Address,
Address.CoinBalance,
@ -63,6 +65,7 @@ defmodule Explorer.Chain do
}
alias Explorer.Chain.Import.Runner
alias Explorer.Chain.InternalTransaction.{CallType, Type}
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
@ -2493,6 +2496,24 @@ defmodule Explorer.Chain do
"""
@spec all_transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t()
]
def all_transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
InternalTransaction
|> for_parent_transaction(hash)
|> join_associations(necessity_by_association)
|> InternalTransaction.where_nonpending_block()
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
|> order_by([internal_transaction], asc: internal_transaction.index)
|> preload(:transaction)
|> Repo.all()
end
@spec transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t()
]
@ -4033,4 +4054,128 @@ defmodule Explorer.Chain do
defp boolean_to_check_result(true), do: :ok
defp boolean_to_check_result(false), do: :not_found
@doc """
Fetches the first trace from the Parity trace URL.
"""
def fetch_first_trace(transactions_params, json_rpc_named_arguments) do
{:ok, [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]} =
EthereumJSONRPC.fetch_first_trace(transactions_params, json_rpc_named_arguments)
format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments)
end
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do
Chain.string_to_address_hash(first_trace.to_address_hash)
else
{:ok, nil}
end
{:ok, from_address_hash} = Chain.string_to_address_hash(first_trace.from_address_hash)
{:ok, created_contract_address_hash} =
if Map.has_key?(first_trace, :created_contract_address_hash) do
Chain.string_to_address_hash(first_trace.created_contract_address_hash)
else
{:ok, nil}
end
{:ok, transaction_hash} = Chain.string_to_transaction_hash(first_trace.transaction_hash)
{:ok, call_type} =
if Map.has_key?(first_trace, :call_type) do
CallType.load(first_trace.call_type)
else
{:ok, nil}
end
{:ok, type} = Type.load(first_trace.type)
{:ok, input} =
if Map.has_key?(first_trace, :input) do
Data.cast(first_trace.input)
else
{:ok, nil}
end
{:ok, output} =
if Map.has_key?(first_trace, :output) do
Data.cast(first_trace.output)
else
{:ok, nil}
end
{:ok, created_contract_code} =
if Map.has_key?(first_trace, :created_contract_code) do
Data.cast(first_trace.created_contract_code)
else
{:ok, nil}
end
{:ok, init} =
if Map.has_key?(first_trace, :init) do
Data.cast(first_trace.init)
else
{:ok, nil}
end
block_index =
get_block_index(%{
transaction_index: first_trace.transaction_index,
transaction_hash: first_trace.transaction_hash,
block_number: first_trace.block_number,
json_rpc_named_arguments: json_rpc_named_arguments
})
value = %Wei{value: Decimal.new(first_trace.value)}
first_trace_formatted =
first_trace
|> Map.merge(%{
block_index: block_index,
block_hash: block_hash,
call_type: call_type,
to_address_hash: to_address_hash,
created_contract_address_hash: created_contract_address_hash,
from_address_hash: from_address_hash,
input: input,
output: output,
created_contract_code: created_contract_code,
init: init,
transaction_hash: transaction_hash,
type: type,
value: value
})
{:ok, [first_trace_formatted]}
end
defp get_block_index(%{
transaction_index: transaction_index,
transaction_hash: transaction_hash,
block_number: block_number,
json_rpc_named_arguments: json_rpc_named_arguments
}) do
if transaction_index == 0 do
0
else
{:ok, traces} = fetch_block_internal_transactions([block_number], json_rpc_named_arguments)
sorted_traces =
traces
|> Enum.sort_by(&{&1.transaction_index, &1.index})
|> Enum.with_index()
{_, block_index} =
sorted_traces
|> Enum.find(fn {trace, _} ->
trace.transaction_index == transaction_index &&
trace.transaction_hash == transaction_hash
end)
block_index
end
end
end

@ -334,7 +334,7 @@ defmodule Explorer.Chain.Import do
end
@spec timestamps() :: timestamps
defp timestamps do
def timestamps do
now = DateTime.utc_now()
%{inserted_at: now, updated_at: now}
end

@ -49,5 +49,6 @@ defmodule Explorer.Chain.Import.Runner do
"""
@callback ecto_schema_module() :: module()
@callback run(Multi.t(), changes_list, %{optional(atom()) => term()}) :: Multi.t()
# @callback run_insert_only(Multi.t(), changes_list, %{optional(atom()) => term()}) :: Multi.t()
@callback timeout() :: timeout()
end

@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction}
alias Explorer.Chain.Import.Runner
alias Explorer.Repo, as: ExplorerRepo
import Ecto.Query, only: [from: 2, or_where: 3]
@ -114,6 +115,25 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
end)
end
def run_insert_only(changes_list, %{timestamps: timestamps} = options) when is_map(options) do
insert_options =
options
|> Map.get(option_key(), %{})
|> Map.take(~w(on_conflict timeout)a)
|> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps)
# filter out params with just `block_number` (indicating blocks without internal transactions)
internal_transactions_params = Enum.filter(changes_list, &Map.has_key?(&1, :type))
# Enforce ShareLocks tables order (see docs: sharelocks.md)
Multi.new()
|> Multi.run(:internal_transactions, fn repo, _ ->
insert(repo, internal_transactions_params, insert_options)
end)
|> ExplorerRepo.transaction()
end
@impl Runner
def timeout, do: @timeout

@ -3,7 +3,7 @@ defmodule Explorer.Etherscan do
The etherscan context.
"""
import Ecto.Query, only: [from: 2, where: 3, or_where: 3, union: 2]
import Ecto.Query, only: [from: 2, where: 3, or_where: 3, union: 2, subquery: 1]
alias Explorer.Etherscan.Logs
alias Explorer.{Chain, Repo}
@ -374,6 +374,8 @@ defmodule Explorer.Etherscan do
end
@token_transfer_fields ~w(
block_number
block_hash
token_contract_address_hash
transaction_hash
from_address_hash
@ -382,31 +384,17 @@ defmodule Explorer.Etherscan do
)a
defp list_token_transfers(address_hash, contract_address_hash, block_height, options) do
query =
tt_query =
from(
t in Transaction,
inner_join: tt in TokenTransfer,
on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash,
tt in TokenTransfer,
inner_join: tkn in assoc(tt, :token),
inner_join: b in assoc(t, :block),
where: tt.from_address_hash == ^address_hash,
or_where: tt.to_address_hash == ^address_hash,
order_by: [{^options.order_by_direction, t.block_number}],
order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.log_index}],
limit: ^options.page_size,
offset: ^offset(options),
select:
merge(map(tt, ^@token_transfer_fields), %{
transaction_nonce: t.nonce,
transaction_index: t.index,
transaction_gas: t.gas,
transaction_gas_price: t.gas_price,
transaction_gas_used: t.gas_used,
transaction_cumulative_gas_used: t.cumulative_gas_used,
transaction_input: t.input,
block_hash: b.hash,
block_number: b.number,
block_timestamp: b.timestamp,
confirmations: fragment("? - ?", ^block_height, t.block_number),
token_id: tt.token_id,
token_name: tkn.name,
token_symbol: tkn.symbol,
@ -416,10 +404,45 @@ defmodule Explorer.Etherscan do
})
)
query
tt_specific_token_query =
tt_query
|> where_contract_address_match(contract_address_hash)
wrapped_query =
from(
tt in subquery(tt_specific_token_query),
inner_join: t in Transaction,
on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash,
inner_join: b in assoc(t, :block),
select: %{
token_contract_address_hash: tt.token_contract_address_hash,
transaction_hash: tt.transaction_hash,
from_address_hash: tt.from_address_hash,
to_address_hash: tt.to_address_hash,
amount: tt.amount,
transaction_nonce: t.nonce,
transaction_index: t.index,
transaction_gas: t.gas,
transaction_gas_price: t.gas_price,
transaction_gas_used: t.gas_used,
transaction_cumulative_gas_used: t.cumulative_gas_used,
transaction_input: t.input,
block_hash: b.hash,
block_number: b.number,
block_timestamp: b.timestamp,
confirmations: fragment("? - ?", ^block_height, t.block_number),
token_id: tt.token_id,
token_name: tt.token_name,
token_symbol: tt.token_symbol,
token_decimals: tt.token_decimals,
token_type: tt.token_type,
token_log_index: tt.token_log_index
}
)
wrapped_query
|> where_start_block_match(options)
|> where_end_block_match(options)
|> where_contract_address_match(contract_address_hash)
|> Repo.all()
end
@ -450,7 +473,7 @@ defmodule Explorer.Etherscan do
defp where_contract_address_match(query, nil), do: query
defp where_contract_address_match(query, contract_address_hash) do
where(query, [_, tt], tt.token_contract_address_hash == ^contract_address_hash)
where(query, [tt, _], tt.token_contract_address_hash == ^contract_address_hash)
end
defp offset(options), do: (options.page_number - 1) * options.page_size

@ -7,7 +7,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
alias Ecto.Multi
alias Explorer.Chain.Import.Runner.{Blocks, Transactions}
alias Explorer.Chain.{Address, Block, Transaction, TokenTransfer}
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.{Chain, Repo}
describe "run/1" do
@ -312,7 +312,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
end
test "removes duplicate blocks (by hash) before inserting",
%{consensus_block: %{number: _, hash: block_hash, miner_hash: miner_hash}, options: options} do
%{consensus_block: %{number: _, hash: _block_hash, miner_hash: miner_hash}, options: options} do
new_block = params_for(:block, miner_hash: miner_hash, consensus: true)
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block)
@ -322,7 +322,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
|> Blocks.run([block_changes, block_changes], options)
|> Repo.transaction()
assert {:ok, %{blocks: [%{hash: block_hash, consensus: true}]}} = result
assert {:ok, %{blocks: [%{hash: _block_hash, consensus: true}]}} = result
end
end

@ -5,6 +5,7 @@ defmodule Explorer.ChainTest do
require Ecto.Query
import Ecto.Query
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Explorer.Factory
import Mox
@ -26,6 +27,9 @@ defmodule Explorer.ChainTest do
Wei
}
alias Explorer.Chain
alias Explorer.Chain.InternalTransaction.Type
alias Explorer.Chain.Supply.ProofOfAuthority
alias Explorer.Counters.AddressesWithBalanceCounter
alias Explorer.Counters.AddressesCounter
@ -2352,7 +2356,7 @@ defmodule Explorer.ChainTest do
assert [] = Chain.transaction_to_internal_transactions(transaction.hash)
end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do
test "with transaction with internal transactions returns all internal transactions for a given transaction hash excluding parent trace" do
block = insert(:block)
transaction =
@ -2610,6 +2614,273 @@ defmodule Explorer.ChainTest do
end
end
describe "all_transaction_to_internal_transactions/1" do
test "with transaction without internal transactions" do
transaction = insert(:transaction)
assert [] = Chain.all_transaction_to_internal_transactions(transaction.hash)
end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
first =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
second =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_hash: transaction.block_hash,
block_index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
results = [internal_transaction | _] = Chain.all_transaction_to_internal_transactions(transaction.hash)
assert 2 == length(results)
assert Enum.all?(
results,
&({&1.transaction_hash, &1.index} in [
{first.transaction_hash, first.index},
{second.transaction_hash, second.index}
])
)
assert internal_transaction.transaction.block_number == block.number
end
test "with transaction with internal transactions loads associations with in necessity_by_association" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction_create,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
assert [
%InternalTransaction{
from_address: %Ecto.Association.NotLoaded{},
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
}
] = Chain.all_transaction_to_internal_transactions(transaction.hash)
assert [
%InternalTransaction{
from_address: %Address{},
to_address: nil,
transaction: %Transaction{block: %Block{}}
}
] =
Chain.all_transaction_to_internal_transactions(
transaction.hash,
necessity_by_association: %{
:from_address => :optional,
:to_address => :optional,
[transaction: :block] => :optional
}
)
end
test "not excludes internal transaction of type call with no siblings in the transaction" do
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
result = Chain.all_transaction_to_internal_transactions(transaction.hash)
assert Enum.empty?(result) == false
end
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction_create,
index: 0,
transaction: transaction,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.all_transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "includes internal transactions of type `reward` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction,
index: 0,
transaction: transaction,
type: :reward,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.all_transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "includes internal transactions of type `selfdestruct` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected =
insert(:internal_transaction,
index: 0,
transaction: transaction,
gas: nil,
type: :selfdestruct,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
actual = Enum.at(Chain.all_transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
test "returns the internal transactions in ascending index order" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: transaction_hash, index: index} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
result =
transaction.hash
|> Chain.all_transaction_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{transaction_hash, index}, {second_transaction_hash, second_index}] == result
end
test "pages by index" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{transaction_hash: transaction_hash, index: index} =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} =
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index
)
%InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} =
insert(:internal_transaction,
transaction: transaction,
index: 2,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 2,
transaction_index: transaction.index
)
assert [{transaction_hash, index}, {second_transaction_hash, second_index}] ==
transaction.hash
|> Chain.all_transaction_to_internal_transactions(
paging_options: %PagingOptions{key: {-1}, page_size: 2}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{transaction_hash, index}] ==
transaction.hash
|> Chain.all_transaction_to_internal_transactions(
paging_options: %PagingOptions{key: {-1}, page_size: 1}
)
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{third_transaction_hash, third_index}] ==
transaction.hash
|> Chain.all_transaction_to_internal_transactions(paging_options: %PagingOptions{key: {1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
end
end
describe "transaction_to_logs/2" do
test "without logs" do
transaction = insert(:transaction)
@ -4598,4 +4869,111 @@ defmodule Explorer.ChainTest do
assert third.delta == Decimal.new(1)
end
end
describe "fetch_first_trace/2" do
test "fetched first trace", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
gas = 4_533_872
init =
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef"
value = 0
block_number = 39
block_hash = "0x74c72ccabcb98b7ebbd7b31de938212b7e8814a002263b6569564e944d88f51f"
index = 0
created_contract_address_hash = "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461"
created_contract_code =
"0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029"
gas_used = 382_953
trace_address = []
transaction_hash = "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1"
transaction_index = 0
type = "create"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
result: %{
"output" => "0x",
"stateDiff" => nil,
"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
{:ok, created_contract_address_hash_bytes} = Chain.string_to_address_hash(created_contract_address_hash)
{:ok, from_address_hash_bytes} = Chain.string_to_address_hash(from_address_hash)
{:ok, created_contract_code_bytes} = Data.cast(created_contract_code)
{:ok, init_bytes} = Data.cast(init)
{:ok, transaction_hash_bytes} = Chain.string_to_transaction_hash(transaction_hash)
{:ok, type_bytes} = Type.load(type)
value_wei = %Wei{value: Decimal.new(value)}
assert Chain.fetch_first_trace(
[
%{
hash_data: transaction_hash,
block_hash: block_hash,
block_number: block_number,
transaction_index: transaction_index
}
],
json_rpc_named_arguments
) == {
:ok,
[
%{
block_index: 0,
block_number: block_number,
block_hash: block_hash,
call_type: nil,
created_contract_address_hash: created_contract_address_hash_bytes,
created_contract_code: created_contract_code_bytes,
from_address_hash: from_address_hash_bytes,
gas: gas,
gas_used: gas_used,
index: index,
init: init_bytes,
input: nil,
output: nil,
to_address_hash: nil,
trace_address: trace_address,
transaction_hash: transaction_hash_bytes,
type: type_bytes,
value: value_wei,
transaction_index: transaction_index
}
]
}
end
end
end

@ -28,12 +28,6 @@ block_transformer =
transformer
end
max_skipping_distance =
case Integer.parse(System.get_env("MAX_SKIPPING_DISTANCE", "")) do
{num, ""} -> num
_ -> 5
end
config :indexer,
block_transformer: block_transformer,
ecto_repos: [Explorer.Repo],
@ -42,8 +36,7 @@ config :indexer,
# bytes
memory_limit: 1 <<< 30,
first_block: System.get_env("FIRST_BLOCK") || "0",
last_block: System.get_env("LAST_BLOCK") || "",
max_skipping_distance: max_skipping_distance
last_block: System.get_env("LAST_BLOCK") || ""
# config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true
# config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true

@ -4,7 +4,7 @@ config :indexer,
block_interval: :timer.seconds(5),
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -4,7 +4,7 @@ config :indexer,
block_interval: :timer.seconds(5),
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -4,7 +4,7 @@ config :indexer,
block_interval: :timer.seconds(5),
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -6,7 +6,7 @@ config :indexer,
receipts_concurrency: 1,
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -4,7 +4,7 @@ config :indexer,
block_interval: :timer.seconds(5),
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -4,7 +4,7 @@ config :indexer,
block_interval: :timer.seconds(5),
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -4,7 +4,7 @@ config :indexer,
block_interval: :timer.seconds(5),
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -6,7 +6,7 @@ config :indexer,
receipts_concurrency: 1,
json_rpc_named_arguments: [
transport:
if(System.get_env("ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT", "http") == "http",
if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
do: EthereumJSONRPC.HTTP,
else: EthereumJSONRPC.IPC
),

@ -73,12 +73,21 @@ defmodule Indexer.Block.Catchup.Fetcher do
) do
Logger.metadata(fetcher: :block_catchup)
case latest_block(json_rpc_named_arguments) do
{:ok, latest_block_number} =
case latest_block() do
nil ->
EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments)
number ->
{:ok, number}
end
case latest_block_number do
# let realtime indexer get the genesis block
0 ->
%{first_block_number: 0, missing_block_count: 0, last_block_number: 0, shrunk: false}
latest_block_number ->
_ ->
# realtime indexer gets the current latest block
first = latest_block_number - 1
last = last_block()
@ -336,23 +345,12 @@ defmodule Indexer.Block.Catchup.Fetcher do
end
end
defp latest_block(json_rpc_named_arguments) do
defp latest_block do
string_value = Application.get_env(:indexer, :last_block)
case Integer.parse(string_value) do
{integer, ""} ->
integer
_ ->
{:ok, number} = EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments)
# leave to realtime indexer the blocks in the skipping window
skipping_distance = Application.get_env(:indexer, :max_skipping_distance)
if number > skipping_distance do
number - skipping_distance
else
0
end
{integer, ""} -> integer
_ -> nil
end
end
end

@ -88,16 +88,9 @@ defmodule Indexer.Block.Realtime.Fetcher do
number = quantity_to_integer(quantity)
# Subscriptions don't support getting all the blocks and transactions data,
# so we need to go back and get the full block
{new_previous_number, new_max_number} =
case start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen) do
# The number may have not been inserted if it was part of a small skip
:skip ->
Logger.debug(["#{inspect(number)} was skipped"])
{previous_number, max_number_seen}
start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen)
_ ->
{number, new_max_number(number, max_number_seen)}
end
new_max_number = new_max_number(number, max_number_seen)
Process.cancel_timer(timer)
new_timer = schedule_polling()
@ -105,7 +98,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
{:noreply,
%{
state
| previous_number: new_previous_number,
| previous_number: number,
max_number_seen: new_max_number,
timer: new_timer
}}
@ -123,14 +116,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
{number, new_max_number} =
case EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) do
{:ok, number} when is_nil(max_number_seen) or number > max_number_seen ->
# in case of polling the realtime fetcher should take care of all the
# blocks in the skipping window, because the cathup fetcher wont
max_skipping_distance = Application.get_env(:indexer, :max_skipping_distance)
last_catchup_number = max(0, 10 - max_skipping_distance - 1)
starting_number = max(previous_number, last_catchup_number) || last_catchup_number
start_fetch_and_import(number, block_fetcher, starting_number, nil)
start_fetch_and_import(number, block_fetcher, previous_number, number)
{max_number_seen, number}
@ -225,35 +211,27 @@ defmodule Indexer.Block.Realtime.Fetcher do
end
defp start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen) do
fetching_action = determine_fetching_action(number, previous_number, max_number_seen)
start_at = determine_start_at(number, previous_number, max_number_seen)
if fetching_action != :skip do
for block_number_to_fetch <- fetching_action do
args = [block_number_to_fetch, block_fetcher, reorg?(number, max_number_seen)]
Task.Supervisor.start_child(TaskSupervisor, __MODULE__, :fetch_and_import_block, args)
end
for block_number_to_fetch <- start_at..number do
args = [block_number_to_fetch, block_fetcher, reorg?(number, max_number_seen)]
Task.Supervisor.start_child(TaskSupervisor, __MODULE__, :fetch_and_import_block, args)
end
fetching_action
end
def determine_fetching_action(number, previous_number, max_number_seen) do
cond do
reorg?(number, max_number_seen) ->
[number]
defp determine_start_at(number, nil, nil), do: number
can_be_skipped?(number, max_number_seen) ->
:skip
is_nil(previous_number) ->
[number]
defp determine_start_at(number, nil, max_number_seen) do
determine_start_at(number, number - 1, max_number_seen)
end
true ->
if number - previous_number - 1 > 10 do
(number - 10)..number
else
(previous_number + 1)..number
end
defp determine_start_at(number, previous_number, max_number_seen) do
if reorg?(number, max_number_seen) do
# set start_at to NOT fill in skipped numbers
number
else
# set start_at to fill in skipped numbers, if any
previous_number + 1
end
end
@ -263,14 +241,6 @@ defmodule Indexer.Block.Realtime.Fetcher do
defp reorg?(_, _), do: false
defp can_be_skipped?(number, max_number_seen) when is_integer(max_number_seen) and number > max_number_seen + 1 do
max_skipping_distance = Application.get_env(:indexer, :max_skipping_distance)
max_skipping_distance > 1 and number <= max_number_seen + max_skipping_distance
end
defp can_be_skipped?(_, _), do: false
@reorg_delay 5_000
@decorate trace(name: "fetch", resource: "Indexer.Block.Realtime.Fetcher.fetch_and_import_block/3", tracer: Tracer)

@ -125,6 +125,27 @@ defmodule Indexer.Fetcher.InternalTransaction do
end
end
def import_first_trace(internal_transactions_params) do
imports =
Chain.import(%{
internal_transactions: %{params: internal_transactions_params, with: :blockless_changeset},
timeout: :infinity
})
case imports do
{:error, step, reason, _changes_so_far} ->
Logger.error(
fn ->
[
"failed to import first trace for tx: ",
inspect(reason)
]
end,
step: step
)
end
end
defp fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments) do
Enum.reduce(unique_numbers, {:ok, []}, fn
block_number, {:ok, acc_list} ->

@ -28,11 +28,6 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do
setup :verify_on_exit!
# run the tests without the skipping window
setup do
Application.put_env(:indexer, :max_skipping_distance, 0)
end
describe "start_link/1" do
# See https://github.com/poanetwork/blockscout/issues/597
@tag :no_geth

@ -32,11 +32,6 @@ defmodule Indexer.Block.Catchup.FetcherTest do
}
end
setup do
# run the tests without the skipping window
Application.put_env(:indexer, :max_skipping_distance, 0)
end
describe "import/1" do
test "fetches uncles asynchronously", %{json_rpc_named_arguments: json_rpc_named_arguments} do
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)

@ -40,11 +40,6 @@ defmodule Indexer.Block.Realtime.FetcherTest do
%{block_fetcher: block_fetcher, json_rpc_named_arguments: core_json_rpc_named_arguments}
end
setup do
# run the tests with a realistic skipping window
Application.put_env(:indexer, :max_skipping_distance, 3)
end
describe "Indexer.Block.Fetcher.fetch_and_import_range/1" do
@tag :no_geth
test "in range with internal transactions", %{
@ -429,51 +424,4 @@ defmodule Indexer.Block.Realtime.FetcherTest do
}} = Indexer.Block.Fetcher.fetch_and_import_range(block_fetcher, 3_946_079..3_946_080)
end
end
describe "determine_fetching_action/4" do
test "when everything (except number) is nil results in fetching only the number" do
assert [14] == Realtime.Fetcher.determine_fetching_action(14, nil, nil)
end
test "when number is also max_number_seen results in fetching only the number" do
number = 23
assert [number] == Realtime.Fetcher.determine_fetching_action(number, nil, number)
assert [number] == Realtime.Fetcher.determine_fetching_action(number, 21, number)
end
test "when max_number_seen is nil, fetching will start from previous_number" do
# note: this is a way to force this behavior, used by `poll_latest_block_number`
number = 156
previous_number = 150
old_number = 94
assert (previous_number + 1)..number == Realtime.Fetcher.determine_fetching_action(number, previous_number, nil)
assert (number - 10)..number == Realtime.Fetcher.determine_fetching_action(number, old_number, nil)
end
test "when number immediately follows the previous_number it is fetched" do
max_number_seen = 26
number = 27
assert [number] == Realtime.Fetcher.determine_fetching_action(number, nil, max_number_seen)
end
test "when number is inside the allowed skipping window nothing is fetched" do
max_number_seen = 26
assert :skip == Realtime.Fetcher.determine_fetching_action(28, nil, max_number_seen)
assert :skip == Realtime.Fetcher.determine_fetching_action(29, nil, max_number_seen)
end
test "when number is over the allowed skipping window all the values since the previous_number will be fetched" do
max_number_seen = 390
previous_number = 381
max_skipping_distance = Application.get_env(:indexer, :max_skipping_distance)
number = max_number_seen + max_skipping_distance + 1
assert (number - 10)..number ==
Realtime.Fetcher.determine_fetching_action(number, previous_number, max_number_seen)
end
end
end

@ -22,9 +22,6 @@ endif
ifdef SUBNETWORK
BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUBNETWORK=$(SUBNETWORK)'
endif
ifdef NETWORK_ICON
BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK_ICON=$(NETWORK_ICON)'
endif
ifdef LOGO
BLOCKSCOUT_CONTAINER_PARAMS += -e 'LOGO=$(LOGO)'
endif
@ -43,8 +40,8 @@ endif
ifdef ETHEREUM_JSONRPC_WS_URL
BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_WS_URL=$(ETHEREUM_JSONRPC_WS_URL)'
endif
ifdef ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT
BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT=$(ETHEREUM_JSONRPC_JSON_RPC_TRANSPORT)'
ifdef ETHEREUM_JSONRPC_TRANSPORT
BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_TRANSPORT=$(ETHEREUM_JSONRPC_TRANSPORT)'
endif
ifdef IPC_PATH
BLOCKSCOUT_CONTAINER_PARAMS += -e 'IPC_PATH=$(IPC_PATH)'
@ -124,9 +121,6 @@ endif
ifdef LINK_TO_OTHER_EXPLORERS
BLOCKSCOUT_CONTAINER_PARAMS += -e 'LINK_TO_OTHER_EXPLORERS=$(LINK_TO_OTHER_EXPLORERS)'
endif
ifdef COINMARKETCAP_PAGES
BLOCKSCOUT_CONTAINER_PARAMS += -e 'COINMARKETCAP_PAGES=$(COINMARKETCAP_PAGES)'
endif
ifdef SUPPORTED_CHAINS
BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUPPORTED_CHAINS=$(SUPPORTED_CHAINS)'
endif
@ -172,14 +166,11 @@ endif
ifdef CHAIN_SPEC_PATH
BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHAIN_SPEC_PATH=$(CHAIN_SPEC_PATH)'
endif
ifdef COIN_GECKO_ID
BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_GECKO_ID=$(COIN_GECKO_ID)'
endif
ifdef EMISSION_FORMAT
BLOCKSCOUT_CONTAINER_PARAMS += -e 'EMISSION_FORMAT=$(EMISSION_FORMAT)'
endif
ifdef REWARDS_CONTRACT_ADDRESS
BLOCKSCOUT_CONTAINER_PARAMS += -e 'REWARDS_CONTRACT_ADDRESS=$(REWARDS_CONTRACT_ADDRESS)'
ifdef REWARDS_CONTRACT
BLOCKSCOUT_CONTAINER_PARAMS += -e 'REWARDS_CONTRACT=$(REWARDS_CONTRACT)'
endif
ifdef SHOW_ADDRESS_MARKETCAP_PERCENTAGE
BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_ADDRESS_MARKETCAP_PERCENTAGE=$(SHOW_ADDRESS_MARKETCAP_PERCENTAGE)'
@ -199,6 +190,12 @@ endif
ifdef API_PATH
BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_PATH=$(API_PATH)'
endif
ifdef CHECKSUM_ADDRESS_HASHES
BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_ADDRESS_HASHES=$(CHECKSUM_ADDRESS_HASHES)'
endif
ifdef CHECKSUM_FUNCTION
BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_FUNCTION=$(CHECKSUM_FUNCTION)'
endif
HAS_BLOCKSCOUT_IMAGE := $(shell docker images | grep ${DOCKER_IMAGE})
build:

Loading…
Cancel
Save