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. 55
      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. 62
      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: eslint:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/node:12.15.0-browsers-legacy - image: circleci/node:12.16.1-browsers-legacy
working_directory: ~/app working_directory: ~/app
@ -275,7 +275,7 @@ jobs:
jest: jest:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/node:12.15.0-browsers-legacy - image: circleci/node:12.16.1-browsers-legacy
working_directory: ~/app working_directory: ~/app

@ -2,15 +2,14 @@
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0 :0: Unknown type 'Elixir.Map':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
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/explorer/repo/prometheus_logger.ex:8
lib/block_scout_web/views/layout_view.ex:175 lib/block_scout_web/views/layout_view.ex:175
lib/explorer/smart_contract/publisher_worker.ex:6 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: 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/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 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' 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/block_scout_web/router.ex:1
lib/phoenix/router.ex:324 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 ## Current
### Features ### 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 - [#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 - [#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 - [#2834](https://github.com/poanetwork/blockscout/pull/2834) - always redirect to checksummed hash
### Fixes ### 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 - [#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 - [#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 - [#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 ## Machine Requirements
* Erlang/OTP 20.2+ * Erlang/OTP 21+
* Elixir 1.5+ * Elixir 1.9+
* Postgres 10.0 * Postgres 10.3
## Required Accounts ## 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: To get BlockScout Web interface up and running locally:
* Setup `../explorer` * 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 ..` * 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.) * 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 end
config :block_scout_web, BlockScoutWeb.Endpoint, config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base:
System.get_env("SECRET_KEY_BASE") || "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5",
http: [ http: [
port: port || 4000 port: port || 4000
], ],
@ -79,5 +81,3 @@ config :logger, :block_scout_web,
# Set a higher stacktrace during development. Avoid configuring such # Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive. # in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20 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 if checksummed_hash != id do
conn = %{conn | params: Map.merge(conn.params, %{param_name => checksummed_hash})} 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 conn
|> Controller.redirect(to: new_path) |> Controller.redirect(to: new_path)

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.HolderController do
alias BlockScoutWeb.Tokens.HolderView alias BlockScoutWeb.Tokens.HolderView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, import BlockScoutWeb.Chain,
@ -52,7 +53,7 @@ defmodule BlockScoutWeb.Tokens.HolderController do
"index.html", "index.html",
current_path: current_path(conn), current_path: current_path(conn),
token: Market.add_price(token), 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 else
:error -> :error ->

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

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
alias BlockScoutWeb.Tokens.InventoryView alias BlockScoutWeb.Tokens.InventoryView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.TokenTransfer alias Explorer.Chain.{Address, TokenTransfer}
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0] 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( token_inventory_path(
conn, conn,
:index, :index,
address_hash_string, Address.checksum(address_hash_string),
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end
@ -71,7 +71,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
"index.html", "index.html",
current_path: current_path(conn), current_path: current_path(conn),
token: Market.add_price(token), 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 else
:error -> :error ->

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

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.TransferController do
alias BlockScoutWeb.Tokens.TransferView alias BlockScoutWeb.Tokens.TransferView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] 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 nil
next_page_params -> 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 end
transfers_json = transfers_json =
@ -51,7 +57,7 @@ defmodule BlockScoutWeb.Tokens.TransferController do
render( render(
conn, conn,
"index.html", "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), current_path: current_path(conn),
token: Market.add_price(token) token: Market.add_price(token)
) )

@ -2,7 +2,10 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.TransactionView alias BlockScoutWeb.TransactionView
alias EthereumJSONRPC
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Import
alias Explorer.Chain.Import.Runner.InternalTransactions
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
def index(conn, %{"transaction_id" => hash_string}) do def index(conn, %{"transaction_id" => hash_string}) do
@ -19,7 +22,40 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
:token_transfers => :optional :token_transfers => :optional
} }
) do ) 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( render(
conn, conn,

@ -18,7 +18,7 @@
data-placement="top" data-placement="top"
data-toggle="tooltip" data-toggle="tooltip"
title='<%= gettext("View Contract") %>' 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)"> <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"/> <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 %> <%= if @token_instance.instance do %>
<%= link( <%= link(
gettext("Metadata"), 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)}") class: "card-tab #{tab_status("metadata", @conn.request_path)}")
%> %>
<% end %> <% end %>

@ -11,7 +11,7 @@
<% end %> <% end %>
<!-- buttons --> <!-- buttons -->
<span class="overview-title-buttons float-right"> <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 <span
aria-label='<%= gettext("Copy Address") %>' aria-label='<%= gettext("Copy Address") %>'
class="btn-copy-icon" class="btn-copy-icon"
@ -44,12 +44,12 @@
</span> </span>
</h1> </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"> <div class="d-flex flex-column flex-md-row justify-content-start text-muted">
<span class="mr-4 mb-3 mb-md-0"> <span class="mr-4 mb-3 mb-md-0">
<%= link to: <%= 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" "data-test": "token_contract_address"
do %> do %>
<%= gettext "View Contract" %> <%= gettext "View Contract" %>
@ -108,7 +108,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <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>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"><%= gettext "Close" %></button> <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" %> <%= "#{token_name(@token)} (#{token_symbol(@token)}) - #{LayoutView.subnetwork_title()} - BlockScout" %>
</title> </title>
<meta name="description" content="<%= token_name(@token) %>, balances, and analytics on the <%= LayoutView.network_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"), gettext("Token Holders"),
class: "card-tab #{tab_status("token_holders", @conn.request_path)}", class: "card-tab #{tab_status("token_holders", @conn.request_path)}",
"data-test": "token_holders_tab", "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 %> <%= if display_inventory?(@token) do %>
<%= link( <%= link(
gettext("Inventory"), gettext("Inventory"),
class: "card-tab #{tab_status("inventory", @conn.request_path)}", 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 %> <% end %>
<%= if smart_contract_with_read_only_functions?(@token) do %> <%= if smart_contract_with_read_only_functions?(@token) do %>
<%= link( <%= link(
gettext("Read Contract"), 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)}") class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%> %>
<% end %> <% end %>

@ -11,7 +11,7 @@
<div class="card"> <div class="card">
<%= render OverviewView, "_tabs.html", assigns %> <%= render OverviewView, "_tabs.html", assigns %>
<!-- loaded via AJAX --> <!-- 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"> <div class="tile tile-muted text-center">
<span class="loading-spinner-small mr-2"> <span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span> <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"> <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 %> <%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @token_transfer.transaction_hash %>
<span class="text-nowrap"> <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( <%= render(
BlockScoutWeb.AddressView, BlockScoutWeb.AddressView,
"_responsive_hash.html", "_responsive_hash.html",
@ -18,7 +18,7 @@
) %> ) %>
<% end %> <% end %>
&rarr; &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( <%= render(
BlockScoutWeb.AddressView, BlockScoutWeb.AddressView,
"_responsive_hash.html", "_responsive_hash.html",
@ -32,7 +32,7 @@
<span class="tile-title"> <span class="tile-title">
<%= case token_transfer_amount(@token_transfer) do %> <%= case token_transfer_amount(@token_transfer) do %>
<% {:ok, :erc721_instance} -> %> <% {: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} -> %> <% {:ok, value} -> %>
<%= value %> <%= value %>
<% end %> <% end %>

@ -34,7 +34,21 @@ defmodule BlockScoutWeb.APIDocsView do
end) end)
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] url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
host = url_params[:host] host = url_params[:host]
@ -57,17 +71,19 @@ defmodule BlockScoutWeb.APIDocsView do
def api_url do def api_url do
is_api = true is_api = true
set_path = true
is_api set_path
|> blockscout_url() |> blockscout_url(is_api)
|> Path.join("api") |> Path.join("api")
end end
def eth_rpc_api_url do def eth_rpc_api_url do
is_api = true is_api = true
set_path = true
is_api set_path
|> blockscout_url() |> blockscout_url(is_api)
|> Path.join("api/eth_rpc") |> Path.join("api/eth_rpc")
end end
end end

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

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
alias BlockScoutWeb.CurrencyHelpers alias BlockScoutWeb.CurrencyHelpers
alias Explorer.Chain.{Address, SmartContract, Token} 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"] @tabs ["token_transfers", "metadata"]
@ -55,8 +55,26 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
def qr_code(conn, token_id, hash) do def qr_code(conn, token_id, hash) do
token_instance_path = token_instance_path(conn, :show, to_string(hash), to_string(token_id)) token_instance_path = token_instance_path(conn, :show, to_string(hash), to_string(token_id))
is_api = false url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
url = Path.join(blockscout_url(is_api), token_instance_path) 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 url
|> QRCode.to_png() |> QRCode.to_png()

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

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

@ -898,7 +898,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: 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/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:308 #: 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/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:314 #: lib/block_scout_web/views/transaction_view.ex:314
msgid "Token Transfers" msgid "Token Transfers"
@ -1774,7 +1774,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: 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/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" msgid "Metadata"
msgstr "" msgstr ""

@ -898,7 +898,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: 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/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:308 #: 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/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:314 #: lib/block_scout_web/views/transaction_view.ex:314
msgid "Token Transfers" msgid "Token Transfers"
@ -1774,7 +1774,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: 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/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" msgid "Metadata"
msgstr "" msgstr ""

@ -3,10 +3,26 @@ defmodule BlockScoutWeb.AddressControllerTest do
# ETS tables are shared in `Explorer.Counters.*` # ETS tables are shared in `Explorer.Counters.*`
async: false async: false
import Mox
alias Explorer.Chain.Address alias Explorer.Chain.Address
alias Explorer.Counters.AddressesCounter alias Explorer.Counters.AddressesCounter
describe "GET index/2" do 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 test "returns top addresses", %{conn: conn} do
address_hashes = address_hashes =
4..1 4..1
@ -38,8 +54,21 @@ defmodule BlockScoutWeb.AddressControllerTest do
end end
describe "GET show/3" do 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 test "redirects to address/:address_id/transactions", %{conn: conn} do
insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") insert(:address, hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
conn = get(conn, "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") conn = get(conn, "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
@ -48,7 +77,7 @@ defmodule BlockScoutWeb.AddressControllerTest do
end end
describe "GET address_counters/2" do describe "GET address_counters/2" do
test "returns address counters" do test "returns address counters", %{conn: conn} do
address = insert(:address) address = insert(:address)
conn = get(conn, "/address_counters", %{"id" => Address.checksum(address.hash)}) conn = get(conn, "/address_counters", %{"id" => Address.checksum(address.hash)})

@ -4,7 +4,22 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address alias Explorer.Chain.Address
import Mox
describe "GET index/3" do 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 test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address")) 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 use BlockScoutWeb.ConnCase, async: true
import BlockScoutWeb.WebRouter.Helpers, only: [address_token_path: 3] import BlockScoutWeb.WebRouter.Helpers, only: [address_token_path: 3]
import Mox
alias Explorer.Chain.{Address, Token} alias Explorer.Chain.{Address, Token}
describe "GET index/2" do 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 test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_token_path(conn, :index, "invalid_address")) conn = get(conn, address_token_path(conn, :index, "invalid_address"))

@ -2,11 +2,25 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
use BlockScoutWeb.ConnCase, async: true use BlockScoutWeb.ConnCase, async: true
import BlockScoutWeb.WebRouter.Helpers, only: [address_transaction_path: 3, address_transaction_path: 4] import BlockScoutWeb.WebRouter.Helpers, only: [address_transaction_path: 3, address_transaction_path: 4]
import Mox
alias Explorer.Chain.{Address, Transaction} alias Explorer.Chain.{Address, Transaction}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
describe "GET index/2" do 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 test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_transaction_path(conn, :index, "invalid_address")) conn = get(conn, address_transaction_path(conn, :index, "invalid_address"))

@ -1,9 +1,23 @@
defmodule BlockScoutWeb.Schema.Subscription.TokenTransfersTest do defmodule BlockScoutWeb.Schema.Subscription.TokenTransfersTest do
use BlockScoutWeb.SubscriptionCase use BlockScoutWeb.SubscriptionCase
import Mox
alias BlockScoutWeb.Notifier alias BlockScoutWeb.Notifier
describe "token_transfers field" do 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 test "with valid argument, returns all expected fields", %{socket: socket} do
transaction = insert(:transaction) transaction = insert(:transaction)
token_transfer = insert(:token_transfer, transaction: transaction) token_transfer = insert(:token_transfer, transaction: transaction)

@ -38,6 +38,40 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
end end
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 describe "eth_rpc_api_url/1" do
setup do setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint) original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)

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

@ -301,6 +301,16 @@ defmodule EthereumJSONRPC do
) )
end 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 """ @doc """
Fetches pending transactions from variant API. Fetches pending transactions from variant API.
""" """

@ -36,4 +36,12 @@ defmodule EthereumJSONRPC.Ganache do
""" """
@impl EthereumJSONRPC.Variant @impl EthereumJSONRPC.Variant
def fetch_pending_transactions(_json_rpc_named_arguments), do: :ignore 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 end

@ -37,6 +37,12 @@ defmodule EthereumJSONRPC.Geth do
end end
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 """ @doc """
Internal transaction fetching for entire blocks is not currently supported for Geth. Internal transaction fetching for entire blocks is not currently supported for Geth.

@ -49,6 +49,24 @@ defmodule EthereumJSONRPC.Parity do
end end
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 """ @doc """
Fetches the pending transactions from the Parity node. 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 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"]]}) request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
end 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 end

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

@ -12,6 +12,7 @@ defmodule EthereumJSONRPC.Variant do
@type t :: module @type t :: module
@type internal_transaction_params :: map() @type internal_transaction_params :: map()
@type raw_trace_params :: map()
@doc """ @doc """
Fetch the block reward contract beneficiaries for a given blocks from the variant of the Ethereum JSONRPC API. 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()) :: @callback fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) ::
{:ok, [Transaction.params()]} | {:error, reason :: term} | :ignore {: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 end

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

@ -104,6 +104,107 @@ defmodule EthereumJSONRPC.ParityTest do
end end
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 describe "fetch_beneficiaries/1" do
test "with valid block range, returns {:ok, addresses}", %{ test "with valid block range, returns {:ok, addresses}", %{
json_rpc_named_arguments: json_rpc_named_arguments 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 ## Machine Requirements
* Erlang/OTP 20.2+ * Erlang/OTP 21+
* Elixir 1.5+ * Elixir 1.9+
* Postgres 10.0 * Postgres 10.3
## Required Accounts ## 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: 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` * Install dependencies with `$ mix do deps.get, local.rebar, deps.compile, compile`
* Create and migrate your database with `$ mix ecto.create && mix ecto.migrate` * 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` * Run IEx (Interactive Elixir) to access the index and explore: `$ iex -S mix`

@ -45,7 +45,7 @@ config :explorer, Explorer.ChainSpec.GenesisData,
enabled: true, enabled: true,
chain_spec_path: System.get_env("CHAIN_SPEC_PATH"), chain_spec_path: System.get_env("CHAIN_SPEC_PATH"),
emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"), 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, config :explorer, Explorer.Chain.Cache.BlockNumber,
enabled: true, enabled: true,

@ -2,9 +2,8 @@ use Mix.Config
# Configure your database # Configure your database
config :explorer, Explorer.Repo, config :explorer, Explorer.Repo,
database: "explorer_dev", url: System.get_env("DATABASE_URL"),
hostname: "localhost", pool_size: String.to_integer(System.get_env("POOL_SIZE", "50")),
pool_size: 20,
timeout: :timer.seconds(80) timeout: :timer.seconds(80)
config :explorer, Explorer.Tracer, env: "dev", disabled?: true 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"), path: Path.absname("logs/dev/explorer/tokens/token_instances.log"),
metadata_filter: [fetcher: :token_instances] metadata_filter: [fetcher: :token_instances]
import_config "dev.secret.exs"
variant = variant =
if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do
"ganache" "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 # Configures the database
config :explorer, Explorer.Repo, config :explorer, Explorer.Repo,
url: System.get_env("DATABASE_URL"), 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"), ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"),
prepare: :unnamed, prepare: :unnamed,
timeout: :timer.seconds(60) timeout: :timer.seconds(60)

@ -31,15 +31,6 @@ config :logger, :explorer,
level: :warn, level: :warn,
path: Path.absname("logs/test/explorer.log") 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, config :explorer, Explorer.ExchangeRates.Source.TransactionAndLog,
secondary_source: Explorer.ExchangeRates.Source.OneCoinSource 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 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 ABI.TypeDecoder
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias Explorer.Chain
alias Explorer.Chain.{ alias Explorer.Chain.{
Address, Address,
Address.CoinBalance, Address.CoinBalance,
@ -63,6 +65,7 @@ defmodule Explorer.Chain do
} }
alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner
alias Explorer.Chain.InternalTransaction.{CallType, Type}
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter} alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo} 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]) :: [ @spec transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t() InternalTransaction.t()
] ]
@ -4033,4 +4054,128 @@ defmodule Explorer.Chain do
defp boolean_to_check_result(true), do: :ok defp boolean_to_check_result(true), do: :ok
defp boolean_to_check_result(false), do: :not_found 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 end

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

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

@ -10,6 +10,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
alias Ecto.{Changeset, Multi, Repo} alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction} alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction}
alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner
alias Explorer.Repo, as: ExplorerRepo
import Ecto.Query, only: [from: 2, or_where: 3] import Ecto.Query, only: [from: 2, or_where: 3]
@ -114,6 +115,25 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
end) end)
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 @impl Runner
def timeout, do: @timeout def timeout, do: @timeout

@ -3,7 +3,7 @@ defmodule Explorer.Etherscan do
The etherscan context. 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.Etherscan.Logs
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
@ -374,6 +374,8 @@ defmodule Explorer.Etherscan do
end end
@token_transfer_fields ~w( @token_transfer_fields ~w(
block_number
block_hash
token_contract_address_hash token_contract_address_hash
transaction_hash transaction_hash
from_address_hash from_address_hash
@ -382,20 +384,42 @@ defmodule Explorer.Etherscan do
)a )a
defp list_token_transfers(address_hash, contract_address_hash, block_height, options) do defp list_token_transfers(address_hash, contract_address_hash, block_height, options) do
query = tt_query =
from( from(
t in Transaction, tt in TokenTransfer,
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,
inner_join: tkn in assoc(tt, :token), inner_join: tkn in assoc(tt, :token),
inner_join: b in assoc(t, :block),
where: tt.from_address_hash == ^address_hash, where: tt.from_address_hash == ^address_hash,
or_where: tt.to_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, limit: ^options.page_size,
offset: ^offset(options), offset: ^offset(options),
select: select:
merge(map(tt, ^@token_transfer_fields), %{ merge(map(tt, ^@token_transfer_fields), %{
token_id: tt.token_id,
token_name: tkn.name,
token_symbol: tkn.symbol,
token_decimals: tkn.decimals,
token_type: tkn.type,
token_log_index: tt.log_index
})
)
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_nonce: t.nonce,
transaction_index: t.index, transaction_index: t.index,
transaction_gas: t.gas, transaction_gas: t.gas,
@ -408,18 +432,17 @@ defmodule Explorer.Etherscan do
block_timestamp: b.timestamp, block_timestamp: b.timestamp,
confirmations: fragment("? - ?", ^block_height, t.block_number), confirmations: fragment("? - ?", ^block_height, t.block_number),
token_id: tt.token_id, token_id: tt.token_id,
token_name: tkn.name, token_name: tt.token_name,
token_symbol: tkn.symbol, token_symbol: tt.token_symbol,
token_decimals: tkn.decimals, token_decimals: tt.token_decimals,
token_type: tkn.type, token_type: tt.token_type,
token_log_index: tt.log_index token_log_index: tt.token_log_index
}) }
) )
query wrapped_query
|> where_start_block_match(options) |> where_start_block_match(options)
|> where_end_block_match(options) |> where_end_block_match(options)
|> where_contract_address_match(contract_address_hash)
|> Repo.all() |> Repo.all()
end end
@ -450,7 +473,7 @@ defmodule Explorer.Etherscan do
defp where_contract_address_match(query, nil), do: query defp where_contract_address_match(query, nil), do: query
defp where_contract_address_match(query, contract_address_hash) do 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 end
defp offset(options), do: (options.page_number - 1) * options.page_size 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 Ecto.Multi
alias Explorer.Chain.Import.Runner.{Blocks, Transactions} alias Explorer.Chain.Import.Runner.{Blocks, Transactions}
alias Explorer.Chain.{Address, Block, Transaction, TokenTransfer} alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
describe "run/1" do describe "run/1" do
@ -312,7 +312,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
end end
test "removes duplicate blocks (by hash) before inserting", 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) new_block = params_for(:block, miner_hash: miner_hash, consensus: true)
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block) %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) |> Blocks.run([block_changes, block_changes], options)
|> Repo.transaction() |> Repo.transaction()
assert {:ok, %{blocks: [%{hash: block_hash, consensus: true}]}} = result assert {:ok, %{blocks: [%{hash: _block_hash, consensus: true}]}} = result
end end
end end

@ -5,6 +5,7 @@ defmodule Explorer.ChainTest do
require Ecto.Query require Ecto.Query
import Ecto.Query import Ecto.Query
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Explorer.Factory import Explorer.Factory
import Mox import Mox
@ -26,6 +27,9 @@ defmodule Explorer.ChainTest do
Wei Wei
} }
alias Explorer.Chain
alias Explorer.Chain.InternalTransaction.Type
alias Explorer.Chain.Supply.ProofOfAuthority alias Explorer.Chain.Supply.ProofOfAuthority
alias Explorer.Counters.AddressesWithBalanceCounter alias Explorer.Counters.AddressesWithBalanceCounter
alias Explorer.Counters.AddressesCounter alias Explorer.Counters.AddressesCounter
@ -2352,7 +2356,7 @@ defmodule Explorer.ChainTest do
assert [] = Chain.transaction_to_internal_transactions(transaction.hash) assert [] = Chain.transaction_to_internal_transactions(transaction.hash)
end 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) block = insert(:block)
transaction = transaction =
@ -2610,6 +2614,273 @@ defmodule Explorer.ChainTest do
end end
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 describe "transaction_to_logs/2" do
test "without logs" do test "without logs" do
transaction = insert(:transaction) transaction = insert(:transaction)
@ -4598,4 +4869,111 @@ defmodule Explorer.ChainTest do
assert third.delta == Decimal.new(1) assert third.delta == Decimal.new(1)
end end
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 end

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

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

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

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

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

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

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

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

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

@ -73,12 +73,21 @@ defmodule Indexer.Block.Catchup.Fetcher do
) do ) do
Logger.metadata(fetcher: :block_catchup) 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 # let realtime indexer get the genesis block
0 -> 0 ->
%{first_block_number: 0, missing_block_count: 0, last_block_number: 0, shrunk: false} %{first_block_number: 0, missing_block_count: 0, last_block_number: 0, shrunk: false}
latest_block_number -> _ ->
# realtime indexer gets the current latest block # realtime indexer gets the current latest block
first = latest_block_number - 1 first = latest_block_number - 1
last = last_block() last = last_block()
@ -336,23 +345,12 @@ defmodule Indexer.Block.Catchup.Fetcher do
end end
end end
defp latest_block(json_rpc_named_arguments) do defp latest_block do
string_value = Application.get_env(:indexer, :last_block) string_value = Application.get_env(:indexer, :last_block)
case Integer.parse(string_value) do case Integer.parse(string_value) do
{integer, ""} -> {integer, ""} -> integer
integer _ -> nil
_ ->
{: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
end end
end end
end end

@ -88,16 +88,9 @@ defmodule Indexer.Block.Realtime.Fetcher do
number = quantity_to_integer(quantity) number = quantity_to_integer(quantity)
# Subscriptions don't support getting all the blocks and transactions data, # Subscriptions don't support getting all the blocks and transactions data,
# so we need to go back and get the full block # so we need to go back and get the full block
{new_previous_number, new_max_number} = start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen)
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}
_ -> new_max_number = new_max_number(number, max_number_seen)
{number, new_max_number(number, max_number_seen)}
end
Process.cancel_timer(timer) Process.cancel_timer(timer)
new_timer = schedule_polling() new_timer = schedule_polling()
@ -105,7 +98,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
{:noreply, {:noreply,
%{ %{
state state
| previous_number: new_previous_number, | previous_number: number,
max_number_seen: new_max_number, max_number_seen: new_max_number,
timer: new_timer timer: new_timer
}} }}
@ -123,14 +116,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
{number, new_max_number} = {number, new_max_number} =
case EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) do 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 -> {: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 start_fetch_and_import(number, block_fetcher, previous_number, number)
# 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)
{max_number_seen, number} {max_number_seen, number}
@ -225,35 +211,27 @@ defmodule Indexer.Block.Realtime.Fetcher do
end end
defp start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen) do 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 <- start_at..number do
for block_number_to_fetch <- fetching_action do
args = [block_number_to_fetch, block_fetcher, reorg?(number, max_number_seen)] args = [block_number_to_fetch, block_fetcher, reorg?(number, max_number_seen)]
Task.Supervisor.start_child(TaskSupervisor, __MODULE__, :fetch_and_import_block, args) Task.Supervisor.start_child(TaskSupervisor, __MODULE__, :fetch_and_import_block, args)
end end
end end
fetching_action defp determine_start_at(number, nil, nil), do: number
end
def determine_fetching_action(number, previous_number, max_number_seen) do
cond do
reorg?(number, max_number_seen) ->
[number]
can_be_skipped?(number, max_number_seen) -> defp determine_start_at(number, nil, max_number_seen) do
:skip determine_start_at(number, number - 1, max_number_seen)
end
is_nil(previous_number) ->
[number]
true -> defp determine_start_at(number, previous_number, max_number_seen) do
if number - previous_number - 1 > 10 do if reorg?(number, max_number_seen) do
(number - 10)..number # set start_at to NOT fill in skipped numbers
number
else else
(previous_number + 1)..number # set start_at to fill in skipped numbers, if any
end previous_number + 1
end end
end end
@ -263,14 +241,6 @@ defmodule Indexer.Block.Realtime.Fetcher do
defp reorg?(_, _), do: false 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 @reorg_delay 5_000
@decorate trace(name: "fetch", resource: "Indexer.Block.Realtime.Fetcher.fetch_and_import_block/3", tracer: Tracer) @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
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 defp fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments) do
Enum.reduce(unique_numbers, {:ok, []}, fn Enum.reduce(unique_numbers, {:ok, []}, fn
block_number, {:ok, acc_list} -> block_number, {:ok, acc_list} ->

@ -28,11 +28,6 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do
setup :verify_on_exit! 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 describe "start_link/1" do
# See https://github.com/poanetwork/blockscout/issues/597 # See https://github.com/poanetwork/blockscout/issues/597
@tag :no_geth @tag :no_geth

@ -32,11 +32,6 @@ defmodule Indexer.Block.Catchup.FetcherTest do
} }
end end
setup do
# run the tests without the skipping window
Application.put_env(:indexer, :max_skipping_distance, 0)
end
describe "import/1" do describe "import/1" do
test "fetches uncles asynchronously", %{json_rpc_named_arguments: json_rpc_named_arguments} 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) 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} %{block_fetcher: block_fetcher, json_rpc_named_arguments: core_json_rpc_named_arguments}
end 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 describe "Indexer.Block.Fetcher.fetch_and_import_range/1" do
@tag :no_geth @tag :no_geth
test "in range with internal transactions", %{ 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) }} = Indexer.Block.Fetcher.fetch_and_import_range(block_fetcher, 3_946_079..3_946_080)
end end
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 end

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

Loading…
Cancel
Save