fix conflict

pull/2208/head
Andrew Gross 6 years ago
commit 00f110607d
No known key found for this signature in database
GPG Key ID: 0DD4DAD72164DFDB
  1. 4
      CHANGELOG.md
  2. 4
      apps/block_scout_web/assets/css/theme/_sokol_variables.scss
  3. 5
      apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg
  4. 4
      apps/block_scout_web/assets/static/images/sokol_logo.svg
  5. 5
      apps/block_scout_web/config/config.exs
  6. 10
      apps/block_scout_web/config/dev.exs
  7. 268
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex
  8. 6
      apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
  9. 286
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
  10. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex
  11. 2
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
  12. 26
      apps/explorer/lib/explorer/chain.ex
  13. 41
      apps/explorer/lib/explorer/etherscan/logs.ex
  14. 10
      apps/indexer/lib/indexer/fetcher/uncle_block.ex
  15. 14
      apps/indexer/test/indexer/block/fetcher/receipts_test.exs
  16. 30
      apps/indexer/test/indexer/fetcher/uncle_block_test.exs
  17. 14
      docs/metrics.md

@ -4,8 +4,10 @@
- [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch
- [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache
- [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty
- [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint
### Fixes
- [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix
- [#2179](https://github.com/poanetwork/blockscout/pull/2179) - fix docker build error
- [#2165](https://github.com/poanetwork/blockscout/pull/2165) - sort blocks by timestamp when calculating average block time
- [#2175](https://github.com/poanetwork/blockscout/pull/2175) - fix coinmarketcap response errors
@ -32,8 +34,10 @@
- [#2123](https://github.com/poanetwork/blockscout/pull/2123) - fix coins percentage view
- [#2119](https://github.com/poanetwork/blockscout/pull/2119) - fix map logging
- [#2130](https://github.com/poanetwork/blockscout/pull/2130) - fix navigation
- [#2148](https://github.com/poanetwork/blockscout/pull/2148) - filter pending logs
- [#2147](https://github.com/poanetwork/blockscout/pull/2147) - add rsk format of checksum
- [#2149](https://github.com/poanetwork/blockscout/pull/2149) - remove pending transaction count
- [#2177](https://github.com/poanetwork/blockscout/pull/2177) - remove duplicate entries from UncleBlock's Fetcher
- [#2169](https://github.com/poanetwork/blockscout/pull/2169) - add more validator reward types for xDai
- [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions
- [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining

@ -40,6 +40,10 @@ $dropdown-menu-item-hover-background: rgba($sub-accent-color, .1) !default;
$header-icon-color-hover: $sub-accent-color;
$header-icon-border-color-hover: $sub-accent-color;
// Logo Size
$footer-logo-height: 20px;
$navbar-logo-height: 20px;
// buttons
$btn-line-bg: #fff; // button bg
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

@ -1 +1,3 @@
<svg id="_-e-logo_top" data-name="-e-logo_top" xmlns="http://www.w3.org/2000/svg" width="96" height="19"><defs><style>.cls-1{fill:#40bfb2;fill-rule:evenodd}</style></defs><path id="_2" data-name="2" class="cls-1" d="M108.627 22h14.51a3.182 3.182 0 0 1 2.886 2.269l3.888 14.463A1.683 1.683 0 0 1 128.245 41h-14.509a3.183 3.183 0 0 1-2.887-2.269l-3.889-14.462A1.683 1.683 0 0 1 108.627 22zm-21.354 0A2.273 2.273 0 1 1 85 24.274 2.273 2.273 0 0 1 87.273 22zm-1.133 9.656A1.125 1.125 0 1 1 85 32.781a1.133 1.133 0 0 1 1.141-1.125zM85 24.266h25.609v8.516H85v-8.516zM87.281 22h23.031v2.266H87.281V22zM86 32h25v2H86v-2zm18.471-.075h6.259L113.166 41h-6.259zM98 25h5.521L105 31.009h-5.521zm-9 0h5.521L96 31.009h-5.525zm24 0h10.239l2.751 10h-10.24zm3 11.01h2.363L119 38h-2.362zm8.007 0h2.363L127 38h-2.362z" transform="translate(-85 -22)"/><path id="_3" data-name="3" class="cls-1" d="M141.538 37a4.271 4.271 0 0 0 2.554-.7 2.164 2.164 0 0 0 .968-1.839 1.96 1.96 0 0 0-1.269-1.993 9.24 9.24 0 0 0-1.925-.6 7.486 7.486 0 0 1-1.286-.372 1.588 1.588 0 0 1-.639-.435 1.012 1.012 0 0 1-.191-.643 1.2 1.2 0 0 1 .52-1.015 2.276 2.276 0 0 1 1.377-.381 3.092 3.092 0 0 1 1.132.181 5.827 5.827 0 0 1 .967.507 1.366 1.366 0 0 0 .657.308.45.45 0 0 0 .346-.163.573.573 0 0 0 .146-.4 1.179 1.179 0 0 0-.62-.906 4.12 4.12 0 0 0-1.177-.534 4.988 4.988 0 0 0-1.378-.19 4.3 4.3 0 0 0-1.761.344 2.944 2.944 0 0 0-1.213.942 2.27 2.27 0 0 0-.438 1.377 1.947 1.947 0 0 0 .692 1.58 5.439 5.439 0 0 0 2.3.924 9.919 9.919 0 0 1 1.441.389 1.662 1.662 0 0 1 .694.435.987.987 0 0 1 .2.643 1.107 1.107 0 0 1-.547 1 2.876 2.876 0 0 1-1.533.344 4.065 4.065 0 0 1-1.341-.19 6.079 6.079 0 0 1-1.086-.516 1.368 1.368 0 0 0-.639-.272.452.452 0 0 0-.355.154.562.562 0 0 0-.137.389.765.765 0 0 0 .137.462 1.786 1.786 0 0 0 .465.408 4.872 4.872 0 0 0 1.341.562 6.254 6.254 0 0 0 1.6.2zm10.641 0a4.3 4.3 0 0 0 2.211-.562 3.73 3.73 0 0 0 1.477-1.6 5.928 5.928 0 0 0 0-4.838 3.733 3.733 0 0 0-1.477-1.6 4.3 4.3 0 0 0-2.211-.562 4.248 4.248 0 0 0-2.2.562 3.737 3.737 0 0 0-1.468 1.6 5.928 5.928 0 0 0 0 4.838 3.735 3.735 0 0 0 1.468 1.6 4.246 4.246 0 0 0 2.2.562zm0-1.214a2.418 2.418 0 0 1-2-.87 3.915 3.915 0 0 1-.7-2.5 3.886 3.886 0 0 1 .707-2.491 2.7 2.7 0 0 1 3.986 0 3.88 3.88 0 0 1 .707 2.491 3.913 3.913 0 0 1-.7 2.51 2.43 2.43 0 0 1-2 .861zm14.731-.072l-3.83-3.414 3.493-3.189a.728.728 0 0 0 .249-.544.652.652 0 0 0-.195-.471.626.626 0 0 0-.461-.2.655.655 0 0 0-.479.217l-4.238 3.968v-7.352a.7.7 0 0 0-.2-.534.711.711 0 0 0-.5-.19.734.734 0 0 0-.523.19.692.692 0 0 0-.2.534v11.506a.691.691 0 0 0 .2.535.735.735 0 0 0 .523.19.712.712 0 0 0 .5-.19.7.7 0 0 0 .2-.535v-3.57l4.628 4.077a.741.741 0 0 0 .479.217.6.6 0 0 0 .452-.2.668.668 0 0 0 .186-.471.8.8 0 0 0-.284-.58zM173.158 37a4.275 4.275 0 0 0 2.2-.562 3.726 3.726 0 0 0 1.472-1.6 5.949 5.949 0 0 0 0-4.838 3.728 3.728 0 0 0-1.472-1.6 4.277 4.277 0 0 0-2.2-.562 4.222 4.222 0 0 0-2.194.562 3.739 3.739 0 0 0-1.463 1.6 5.938 5.938 0 0 0 0 4.838 3.737 3.737 0 0 0 1.463 1.6 4.22 4.22 0 0 0 2.194.562zm0-1.214a2.407 2.407 0 0 1-2-.87 3.927 3.927 0 0 1-.695-2.5 3.9 3.9 0 0 1 .7-2.491 2.686 2.686 0 0 1 3.974 0 3.9 3.9 0 0 1 .7 2.491 3.919 3.919 0 0 1-.695 2.51 2.418 2.418 0 0 1-2 .861zm7.116 1.178a.735.735 0 0 0 .515-.19.69.69 0 0 0 .211-.539V24.729a.69.69 0 0 0-.208-.534.734.734 0 0 0-.515-.19.759.759 0 0 0-.533.19.69.69 0 0 0-.207.534v11.506a.69.69 0 0 0 .207.535.76.76 0 0 0 .533.19z" transform="translate(-85 -22)"/><path id="_1" data-name="1" class="cls-1" d="M87.273 36.453A2.273 2.273 0 1 1 85 38.727a2.273 2.273 0 0 1 2.273-2.274zM86 36a1 1 0 1 1-1 1 1 1 0 0 1 1-1zm-1 1.5h1.5V39H85v-1.5zm2.5-1.5h14v5h-14v-5zm12.494.008h3.266L104.5 41h-3.267zM86 36h1.5v1.5H86V36z" transform="translate(-85 -22)"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="38">
<path fill="#40BFB2" fill-rule="evenodd" d="M191.578 29.54c-.277.253-.621.38-1.03.38-.433 0-.789-.127-1.065-.38-.278-.254-.416-.61-.416-1.069V5.458c0-.458.138-.815.416-1.069.276-.253.632-.38 1.065-.38.409 0 .753.127 1.03.38.276.254.415.611.415 1.069v23.013c0 .459-.139.815-.415 1.069zm-10.855-.671c-1.276.749-2.745 1.124-4.407 1.124-1.661 0-3.124-.375-4.389-1.124-1.264-.748-2.239-1.817-2.926-3.207-.686-1.389-1.029-3.002-1.029-4.838 0-1.836.343-3.448 1.029-4.838.687-1.389 1.662-2.458 2.926-3.207 1.265-.749 2.728-1.124 4.389-1.124 1.662 0 3.131.375 4.407 1.124 1.276.749 2.258 1.818 2.944 3.207.687 1.39 1.03 3.002 1.03 4.838 0 1.836-.343 3.449-1.03 4.838-.686 1.39-1.668 2.459-2.944 3.207zm-.433-13.028c-.939-1.171-2.264-1.758-3.974-1.758-1.71 0-3.034.587-3.973 1.758-.939 1.172-1.409 2.833-1.409 4.983 0 2.175.463 3.841 1.391 5.001.926 1.16 2.258 1.74 3.991 1.74 1.734 0 3.065-.574 3.992-1.722.927-1.147 1.391-2.82 1.391-5.019 0-2.15-.47-3.811-1.409-4.983zM163.111 29.92c-.307 0-.626-.145-.957-.435l-9.256-8.154v7.14c0 .459-.136.815-.408 1.069-.272.253-.609.38-1.011.38-.425 0-.774-.127-1.046-.38-.272-.254-.408-.61-.408-1.069V5.458c0-.458.136-.815.408-1.069.272-.253.621-.38 1.046-.38.402 0 .739.127 1.011.38.272.254.408.611.408 1.069v14.714l8.476-7.937c.283-.29.603-.435.957-.435.355 0 .662.134.922.399.26.266.39.58.39.942 0 .411-.165.774-.496 1.087l-6.986 6.379 7.66 6.813c.378.362.567.749.567 1.159 0 .363-.124.677-.372.943-.248.265-.55.398-.905.398zm-24.331-1.051c-1.281.749-2.754 1.124-4.421 1.124-1.667 0-3.135-.375-4.404-1.124-1.268-.748-2.246-1.817-2.935-3.207-.689-1.389-1.033-3.002-1.033-4.838 0-1.836.344-3.448 1.033-4.838.689-1.389 1.667-2.458 2.935-3.207 1.269-.749 2.737-1.124 4.404-1.124 1.667 0 3.14.375 4.421 1.124 1.28.749 2.265 1.818 2.953 3.207.689 1.39 1.033 3.002 1.033 4.838 0 1.836-.344 3.449-1.033 4.838-.688 1.39-1.673 2.459-2.953 3.207zm-.435-13.028c-.942-1.171-2.271-1.758-3.986-1.758-1.716 0-3.045.587-3.987 1.758-.942 1.172-1.413 2.833-1.413 4.983 0 2.175.465 3.841 1.395 5.001.93 1.16 2.265 1.74 4.005 1.74 1.739 0 3.074-.574 4.004-1.722.93-1.147 1.395-2.82 1.395-5.019 0-2.15-.471-3.811-1.413-4.983zm-27.186 3.153c.596.254 1.454.502 2.573.743 1.655.362 2.938.761 3.851 1.196.912.435 1.563.966 1.952 1.594.389.629.584 1.426.584 2.392 0 1.522-.645 2.749-1.934 3.678-1.29.931-2.993 1.396-5.109 1.396a12.49 12.49 0 0 1-3.194-.399c-1.01-.266-1.904-.64-2.682-1.123-.438-.29-.748-.562-.931-.816-.182-.254-.273-.561-.273-.924 0-.314.091-.573.273-.779a.91.91 0 0 1 .712-.308c.292 0 .717.181 1.277.543.73.435 1.454.78 2.172 1.033.717.254 1.611.381 2.682.381 1.314 0 2.336-.229 3.066-.689.729-.458 1.094-1.123 1.094-1.993 0-.531-.133-.96-.401-1.286-.268-.327-.73-.616-1.387-.87s-1.618-.513-2.883-.779c-2.141-.459-3.674-1.075-4.598-1.848-.925-.773-1.387-1.824-1.387-3.153 0-1.039.292-1.957.876-2.755.584-.797 1.392-1.425 2.427-1.884 1.033-.459 2.208-.689 3.522-.689.948 0 1.866.127 2.755.381a8.24 8.24 0 0 1 2.354 1.069c.827.58 1.241 1.184 1.241 1.812 0 .314-.098.58-.292.797a.9.9 0 0 1-.694.326c-.292 0-.73-.204-1.314-.616a11.668 11.668 0 0 0-1.934-1.014c-.608-.242-1.363-.363-2.262-.363-1.144 0-2.063.254-2.756.761-.693.508-1.04 1.184-1.04 2.03 0 .532.128.96.383 1.286.256.326.681.616 1.277.87zM86 38H44l-4-14H2c-1.965 0-2-2-2-2V4C0 .176 4 0 4 0h74c2.34 0 4 6 4 6l8 28s.047 4-4 4zM19.031 5.992H7.99l2.959 12.025h11.042l-2.96-12.025zm18.009 0H25.998l2.96 12.025h11.041L37.04 5.992zM63.27 31.99h4.724l-1.269-3.977h-4.724l1.269 3.977zM76.484 5.993H56.005l5.501 20.003h20.479L76.484 5.993zm6.255 22.02h-4.724l1.269 3.977h4.724l-1.269-3.977zM39 38H4c-3.883 0-4-4-4-4v-6h36l3 10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -35,6 +35,11 @@ config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: t
# Configures the endpoint
config :block_scout_web, BlockScoutWeb.Endpoint,
instrumenters: [BlockScoutWeb.Prometheus.Instrumenter, SpandexPhoenix.Instrumenter],
http: [
protocol_options: [
idle_timeout: 90_000
]
],
url: [
host: "localhost",
path: System.get_env("NETWORK_PATH") || "/"

@ -15,8 +15,16 @@ port =
end
config :block_scout_web, BlockScoutWeb.Endpoint,
http: [port: port || 4000],
http: [
protocol_options: [
idle_timeout: 90_000
],
port: port || 4000
],
https: [
protocol_options: [
idle_timeout: 90_000
],
port: (port && port + 1) || 4001,
cipher_suite: :strong,
certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem",

@ -1,8 +1,33 @@
defmodule BlockScoutWeb.API.RPC.EthController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.Wei
alias Ecto.Type, as: EctoType
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei}
alias Explorer.Etherscan.Logs
@methods %{
"eth_getBalance" => %{
action: :eth_get_balance,
notes: """
the `earliest` parameter will not work as expected currently, because genesis block balances
are not currently imported
"""
},
"eth_getLogs" => %{
action: :eth_get_logs,
notes: """
Will never return more than 1000 log entries.
"""
}
}
@index_to_word %{
0 => "first",
1 => "second",
2 => "third",
3 => "fourth"
}
def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do
responses = responses(requests)
@ -39,6 +64,138 @@ defmodule BlockScoutWeb.API.RPC.EthController do
|> render("response.json", %{response: response})
end
def eth_get_balance(address_param, block_param \\ nil) do
with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)},
{:block, {:ok, block}} <- {:block, block_param(block_param)},
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do
{:ok, Wei.hex_format(balance)}
else
{:address, :error} ->
{:error, "Query parameter 'address' is invalid"}
{:block, :error} ->
{:error, "Query parameter 'block' is invalid"}
{:balance, {:error, :not_found}} ->
{:error, "Balance not found"}
end
end
def eth_get_logs(filter_options) do
with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options),
{:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options),
{:ok, from_block} <- cast_block(from_block_param),
{:ok, to_block} <- cast_block(to_block_param) do
filter =
address_or_topic_params
|> Map.put(:from_block, from_block)
|> Map.put(:to_block, to_block)
|> Map.put(:allow_non_consensus, true)
{:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)}
else
{:error, message} when is_bitstring(message) ->
{:error, message}
{:error, :empty} ->
{:ok, []}
_ ->
{:error, "Something went wrong."}
end
end
defp render_log(log) do
topics = Enum.reject([log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], &is_nil/1)
%{
"address" => to_string(log.address_hash),
"blockHash" => to_string(log.block_hash),
"blockNumber" => Integer.to_string(log.block_number, 16),
"data" => to_string(log.data),
"logIndex" => Integer.to_string(log.index, 16),
"removed" => log.block_consensus == false,
"topics" => topics,
"transactionHash" => to_string(log.transaction_hash),
"transactionIndex" => log.transaction_index,
"transactionLogIndex" => log.index,
"type" => "mined"
}
end
defp cast_block("0x" <> hexadecimal_digits = input) do
case Integer.parse(hexadecimal_digits, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, input <> " is not a valid block number"}
end
end
defp cast_block(integer) when is_integer(integer), do: {:ok, integer}
defp cast_block(_), do: {:error, "invalid block number"}
defp address_or_topic_params(filter_options) do
address_param = Map.get(filter_options, "address")
topics_param = Map.get(filter_options, "topics")
with {:ok, address} <- validate_address(address_param),
{:ok, topics} <- validate_topics(topics_param) do
address_and_topics(address, topics)
end
end
defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"}
defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}}
defp address_and_topics(nil, topics), do: {:ok, topics}
defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)}
defp validate_address(nil), do: {:ok, nil}
defp validate_address(address) do
case Address.cast(address) do
{:ok, address} -> {:ok, address}
:error -> {:error, "invalid address"}
end
end
defp validate_topics(nil), do: {:ok, nil}
defp validate_topics([]), do: []
defp validate_topics(topics) when is_list(topics) do
topics
|> Stream.with_index()
|> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} ->
case cast_topics(topic) do
{:ok, data} ->
with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data)
{:ok, add_operator(with_filter, index)}
:error ->
{:error, "invalid topics"}
end
end)
end
defp add_operator(filters, 0), do: filters
defp add_operator(filters, index) do
Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and")
end
defp cast_topics(topics) when is_list(topics) do
case EctoType.cast({:array, Data}, topics) do
{:ok, data} -> {:ok, Enum.map(data, &to_string/1)}
:error -> :error
end
end
defp cast_topics(topic) do
case Data.cast(topic) do
{:ok, data} -> {:ok, to_string(data)}
:error -> :error
end
end
defp responses(requests) do
Enum.map(requests, fn request ->
with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")},
@ -51,6 +208,85 @@ defmodule BlockScoutWeb.API.RPC.EthController do
end)
end
defp logs_blocks_filter(filter_options) do
with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options},
{:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)},
{:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do
{:ok, number, number}
else
{:filter, filters} ->
from_block = Map.get(filters, "fromBlock", "latest")
to_block = Map.get(filters, "toBlock", "latest")
max_block_number =
if from_block == "latest" || to_block == "latest" do
max_consensus_block_number()
end
pending_block_number =
if from_block == "pending" || to_block == "pending" do
max_non_consensus_block_number(max_block_number)
end
if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do
{:error, :empty}
else
to_block_numbers(from_block, to_block, max_block_number, pending_block_number)
end
{:block, _} ->
{:error, "Invalid Block Hash"}
{:block_hash, _} ->
{:error, "Invalid Block Hash"}
end
end
defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do
actual_pending_block_number = pending_block_number || max_block_number
with {:ok, from} <- to_block_number(from_block, max_block_number, actual_pending_block_number),
{:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do
{:ok, from, to}
end
end
defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer}
defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0}
defp to_block_number("earliest", _, _), do: {:ok, 0}
defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0}
defp to_block_number("pending", _, pending), do: {:ok, pending}
defp to_block_number("0x" <> number, _, _) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(number, _, _) when is_bitstring(number) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(_, _, _), do: {:error, "invalid block number"}
defp max_non_consensus_block_number(max) do
case Chain.max_non_consensus_block_number(max) do
{:ok, number} -> number
_ -> nil
end
end
defp max_consensus_block_number do
case Chain.max_consensus_block_number() do
{:ok, number} -> number
_ -> nil
end
end
defp format_success(result, id) do
%{result: result, id: id}
end
@ -66,9 +302,13 @@ defmodule BlockScoutWeb.API.RPC.EthController do
defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params})
when is_list(params) do
with {:ok, action} <- get_action(method),
true <- :erlang.function_exported(__MODULE__, action, Enum.count(params)) do
{:correct_arity, true} <-
{:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do
apply(__MODULE__, action, params)
else
{:correct_arity, _} ->
{:error, "Incorrect number of params."}
_ ->
{:error, "Action not found."}
end
@ -82,26 +322,16 @@ defmodule BlockScoutWeb.API.RPC.EthController do
{:error, "Method, params, and jsonrpc, are all required parameters."}
end
def eth_get_balance(address_param, block_param \\ nil) do
with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)},
{:block, {:ok, block}} <- {:block, block_param(block_param)},
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do
{:ok, Wei.hex_format(balance)}
else
{:address, :error} ->
{:error, "Query parameter 'address' is invalid"}
{:block, :error} ->
{:error, "Query parameter 'block' is invalid"}
defp get_action(action) do
case Map.get(@methods, action) do
%{action: action} ->
{:ok, action}
{:balance, {:error, :not_found}} ->
{:error, "Balance not found"}
_ ->
:error
end
end
defp get_action("eth_getBalance"), do: {:ok, :eth_get_balance}
defp get_action(_), do: :error
defp block_param("latest"), do: {:ok, :latest}
defp block_param("earliest"), do: {:ok, :earliest}
defp block_param("pending"), do: {:ok, :pending}

@ -10,10 +10,10 @@
</div>
<% other_explorers = other_explorers() %>
<% col_size = if Enum.empty?(other_explorers), do: 3, else: 4 %>
<% col_size = if Enum.empty?(other_explorers), do: 3, else: 2 %>
<div class="row">
<div class="col-md-<%= col_size %>">
<div class="col-md-3">
<p class="footer-info-text"><%= gettext("Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks.") %></p>
<div class="footer-social-icons">
<a href="https://github.com/poanetwork/blockscout" rel="noreferrer" target="_blank" class="footer-social-icon" title='<%= gettext("Github") %>'>
@ -84,4 +84,4 @@
</div>
<% end %>
</div>
</footer>
</footer>

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
use BlockScoutWeb.ConnCase, async: false
alias Explorer.Counters.{AddressesWithBalanceCounter, AverageBlockTime}
alias Explorer.Repo
alias Indexer.Fetcher.CoinBalanceOnDemand
setup do
@ -26,6 +27,291 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
defp params(api_params, params), do: Map.put(api_params, "params", params)
describe "eth_get_logs" do
setup do
%{
api_params: %{
"method" => "eth_getLogs",
"jsonrpc" => "2.0",
"id" => 0
}
}
end
test "with an invalid address", %{conn: conn, api_params: api_params} do
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [%{"address" => "badhash"}]))
|> json_response(200)
assert %{"error" => "invalid address"} = response
end
test "address with no logs", %{conn: conn, api_params: api_params} do
insert(:block)
address = insert(:address)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [%{"address" => to_string(address.hash)}]))
|> json_response(200)
assert %{"result" => []} = response
end
test "address but no logs and no toBlock provided", %{conn: conn, api_params: api_params} do
address = insert(:address)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [%{"address" => to_string(address.hash)}]))
|> json_response(200)
assert %{"result" => []} = response
end
test "with a matching address", %{conn: conn, api_params: api_params} do
address = insert(:address)
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, transaction: transaction, data: "0x010101")
params = params(api_params, [%{"address" => to_string(address.hash)}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert %{"result" => [%{"data" => "0x010101"}]} = response
end
test "with a matching address and matching topic", %{conn: conn, api_params: api_params} do
address = insert(:address)
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01")
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert %{"result" => [%{"data" => "0x010101"}]} = response
end
test "with a matching address and multiple topic matches", %{conn: conn, api_params: api_params} do
address = insert(:address)
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01")
insert(:log, address: address, transaction: transaction, data: "0x020202", first_topic: "0x00")
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data"))
end
test "with a matching address and multiple topic matches in different positions", %{
conn: conn,
api_params: api_params
} do
address = insert(:address)
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log,
address: address,
transaction: transaction,
data: "0x010101",
first_topic: "0x01",
second_topic: "0x02"
)
insert(:log, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01")
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", "0x02"]}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x010101"}] = response["result"]
end
test "with a matching address and multiple topic matches in different positions and multiple matches in the second position",
%{conn: conn, api_params: api_params} do
address = insert(:address)
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log,
address: address,
transaction: transaction,
data: "0x010101",
first_topic: "0x01",
second_topic: "0x02"
)
insert(:log,
address: address,
transaction: transaction,
data: "0x020202",
first_topic: "0x01",
second_topic: "0x03"
)
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data"))
end
test "with a block range filter",
%{conn: conn, api_params: api_params} do
address = insert(:address)
block1 = insert(:block, number: 0)
block2 = insert(:block, number: 1)
block3 = insert(:block, number: 2)
block4 = insert(:block, number: 3)
transaction1 = insert(:transaction, from_address: address) |> with_block(block1)
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
transaction4 = insert(:transaction, from_address: address) |> with_block(block4)
insert(:log, address: address, transaction: transaction1, data: "0x010101")
insert(:log, address: address, transaction: transaction2, data: "0x020202")
insert(:log, address: address, transaction: transaction3, data: "0x030303")
insert(:log, address: address, transaction: transaction4, data: "0x040404")
params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => 1, "toBlock" => 2}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x020202"}, %{"data" => "0x030303"}] = Enum.sort_by(response["result"], &Map.get(&1, "data"))
end
test "with a block hash filter",
%{conn: conn, api_params: api_params} do
address = insert(:address)
block1 = insert(:block, number: 0)
block2 = insert(:block, number: 1)
block3 = insert(:block, number: 2)
transaction1 = insert(:transaction, from_address: address) |> with_block(block1)
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
insert(:log, address: address, transaction: transaction1, data: "0x010101")
insert(:log, address: address, transaction: transaction2, data: "0x020202")
insert(:log, address: address, transaction: transaction3, data: "0x030303")
params = params(api_params, [%{"address" => to_string(address.hash), "blockHash" => to_string(block2.hash)}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x020202"}] = response["result"]
end
test "with an earliest block filter",
%{conn: conn, api_params: api_params} do
address = insert(:address)
block1 = insert(:block, number: 0)
block2 = insert(:block, number: 1)
block3 = insert(:block, number: 2)
transaction1 = insert(:transaction, from_address: address) |> with_block(block1)
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
insert(:log, address: address, transaction: transaction1, data: "0x010101")
insert(:log, address: address, transaction: transaction2, data: "0x020202")
insert(:log, address: address, transaction: transaction3, data: "0x030303")
params =
params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "earliest", "toBlock" => "earliest"}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x010101"}] = response["result"]
end
test "with a pending block filter",
%{conn: conn, api_params: api_params} do
address = insert(:address)
block1 = insert(:block, number: 0)
block2 = insert(:block, number: 1)
block3 = insert(:block, number: 2)
transaction1 = insert(:transaction, from_address: address) |> with_block(block1)
transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
insert(:log, address: address, transaction: transaction1, data: "0x010101")
insert(:log, address: address, transaction: transaction2, data: "0x020202")
insert(:log, address: address, transaction: transaction3, data: "0x030303")
changeset = Ecto.Changeset.change(block3, %{consensus: false})
Repo.update!(changeset)
params =
params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "pending", "toBlock" => "pending"}])
assert response =
conn
|> post("/api/eth_rpc", params)
|> json_response(200)
assert [%{"data" => "0x030303"}] = response["result"]
end
end
describe "eth_get_balance" do
setup do
%{

@ -61,7 +61,9 @@ defmodule EthereumJSONRPC.Receipts do
"""
@spec elixir_to_logs(elixir) :: Logs.elixir()
def elixir_to_logs(elixir) when is_list(elixir) do
Enum.flat_map(elixir, &Receipt.elixir_to_logs/1)
elixir
|> Enum.flat_map(&Receipt.elixir_to_logs/1)
|> Enum.filter(&(Map.get(&1, "type") != "pending"))
end
@doc """

@ -57,7 +57,7 @@ defmodule EthereumJSONRPCTest do
"invalid argument 0: json: cannot unmarshal hex string of odd length into Go value of type common.Address"
EthereumJSONRPC.Parity ->
"Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
"Invalid params: invalid length 1, expected a 0x-prefixed hex string with length of 40."
_ ->
raise ArgumentError, "Unsupported variant (#{variant}})"

@ -1700,6 +1700,32 @@ defmodule Explorer.Chain do
end
end
@spec max_non_consensus_block_number(integer | nil) :: {:ok, Block.block_number()} | {:error, :not_found}
def max_non_consensus_block_number(max_consensus_block_number \\ nil) do
max =
if max_consensus_block_number do
{:ok, max_consensus_block_number}
else
max_consensus_block_number()
end
case max do
{:ok, number} ->
query =
from(block in Block,
where: block.consensus == false,
where: block.number > ^number
)
query
|> Repo.aggregate(:max, :number)
|> case do
nil -> {:error, :not_found}
number -> {:ok, number}
end
end
end
@doc """
The height of the chain.

@ -34,7 +34,8 @@ defmodule Explorer.Etherscan.Logs do
:fourth_topic,
:index,
:address_hash,
:transaction_hash
:transaction_hash,
:type
]
@doc """
@ -114,16 +115,26 @@ defmodule Explorer.Etherscan.Logs do
from(log_transaction_data in subquery(all_transaction_logs_query),
join: block in Block,
on: block.number == log_transaction_data.block_number,
where: block.consensus == true,
where: log_transaction_data.address_hash == ^address_hash,
order_by: block.number,
limit: 1000,
select_merge: %{
block_timestamp: block.timestamp
block_timestamp: block.timestamp,
block_consensus: block.consensus,
block_hash: block.hash
}
)
Repo.all(query_with_blocks)
query_with_consensus =
if Map.get(filter, :allow_non_consensus) do
query_with_blocks
else
from([_, block] in query_with_blocks,
where: block.consensus == true
)
end
Repo.all(query_with_consensus)
end
# Since address_hash was not present, we know that a
@ -140,20 +151,30 @@ defmodule Explorer.Etherscan.Logs do
join: block in assoc(transaction, :block),
where: block.number >= ^prepared_filter.from_block,
where: block.number <= ^prepared_filter.to_block,
where: block.consensus == true,
select: %{
transaction_hash: transaction.hash,
gas_price: transaction.gas_price,
gas_used: transaction.gas_used,
transaction_index: transaction.index,
block_hash: block.hash,
block_number: block.number,
block_timestamp: block.timestamp
block_timestamp: block.timestamp,
block_consensus: block.consensus
}
)
query_with_consensus =
if Map.get(filter, :allow_non_consensus) do
block_transaction_query
else
from([_, block] in block_transaction_query,
where: block.consensus == true
)
end
query_with_block_transaction_data =
from(log in logs_query,
join: block_transaction_data in subquery(block_transaction_query),
join: block_transaction_data in subquery(query_with_consensus),
on: block_transaction_data.transaction_hash == log.transaction_hash,
order_by: block_transaction_data.block_number,
limit: 1000,
@ -186,7 +207,7 @@ defmodule Explorer.Etherscan.Logs do
query
[topic] ->
where(query, [l], field(l, ^topic) == ^filter[topic])
where(query, [l], field(l, ^topic) in ^List.wrap(filter[topic]))
_ ->
where_multiple_topics_match(query, filter)
@ -201,12 +222,12 @@ defmodule Explorer.Etherscan.Logs do
defp where_multiple_topics_match(query, filter, topic_operation, "and") do
{topic_a, topic_b} = @topic_operations[topic_operation]
where(query, [l], field(l, ^topic_a) == ^filter[topic_a] and field(l, ^topic_b) == ^filter[topic_b])
where(query, [l], field(l, ^topic_a) == ^filter[topic_a] and field(l, ^topic_b) in ^List.wrap(filter[topic_b]))
end
defp where_multiple_topics_match(query, filter, topic_operation, "or") do
{topic_a, topic_b} = @topic_operations[topic_operation]
where(query, [l], field(l, ^topic_a) == ^filter[topic_a] or field(l, ^topic_b) == ^filter[topic_b])
where(query, [l], field(l, ^topic_a) == ^filter[topic_a] or field(l, ^topic_b) in ^List.wrap(filter[topic_b]))
end
defp where_multiple_topics_match(query, _, _, _), do: query

@ -71,17 +71,19 @@ defmodule Indexer.Fetcher.UncleBlock do
@impl BufferedTask
@decorate trace(name: "fetch", resource: "Indexer.Fetcher.UncleBlock.run/2", service: :indexer, tracer: Tracer)
def run(entries, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} = block_fetcher) do
entry_count = Enum.count(entries)
unique_entries = Enum.uniq(entries)
entry_count = Enum.count(unique_entries)
Logger.metadata(count: entry_count)
Logger.debug("fetching")
entries
unique_entries
|> Enum.map(&entry_to_params/1)
|> EthereumJSONRPC.fetch_uncle_blocks(json_rpc_named_arguments)
|> case do
{:ok, blocks} ->
run_blocks(blocks, block_fetcher, entries)
run_blocks(blocks, block_fetcher, unique_entries)
{:error, reason} ->
Logger.error(
@ -91,7 +93,7 @@ defmodule Indexer.Fetcher.UncleBlock do
error_count: entry_count
)
{:retry, entries}
{:retry, unique_entries}
end
end

@ -84,6 +84,18 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do
"transactionIndex" => "0x0",
"transactionLogIndex" => "0x0",
"type" => "mined"
},
%{
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415c",
"blockHash" => nil,
"blockNumber" => nil,
"data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
"logIndex" => "0x1",
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => "0x0",
"transactionLogIndex" => "0x0",
"type" => "pending"
}
],
"logsBloom" =>
@ -146,6 +158,8 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do
log[:transaction_hash] == "0x43bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" &&
log[:block_number] == 46147
end)
refute Enum.find(logs, fn log -> log[:type] == "pending" end)
end
end
end

@ -169,6 +169,36 @@ defmodule Indexer.Fetcher.UncleBlockTest do
assert {:retry, ^entries} =
UncleBlock.run(entries, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments})
end
test "retries only unique uncles on failed request", %{json_rpc_named_arguments: json_rpc_named_arguments} do
%Hash{bytes: block_hash_bytes} = block_hash()
entry = {block_hash_bytes, 0}
entries = [entry, entry]
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: id,
method: "eth_getUncleByBlockHashAndIndex"
}
],
_ ->
{:ok,
[
%{
id: id,
error: %{
code: 404,
data: %{index: 0, nephew_hash: "0xa0814f0478fe90c82852f812fd74c96df148654c326d2600d836e6908ebb62b4"},
message: "Not Found"
}
}
]}
end)
assert {:retry, [entry]} =
UncleBlock.run(entries, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments})
end
end
describe "run_blocks/2" do

@ -2,19 +2,23 @@
## Metrics
BlockScout is setup to export [Prometheus](https://prometheus.io/) metrics at `/metrics`.
[Wobserver](https://github.com/shinyscorpion/) is available for metrics display at `/wobserver`. For example, `https://blockscout.com/eth/mainnet/wobserver/`
### Wobserver
[Wobserver](https://github.com/shinyscorpion/wobserver) is configured to display data from the `/metrics` endpoint in a web interface. To view, go to `/wobserver` for the chain you would like to view.
For example `https://blockscout.com/eth/mainnet/wobserver`
### Prometheus
BlockScout is setup to export [Prometheus](https://prometheus.io/) metrics at `/metrics`.
1. Install prometheus: `brew install prometheus`
2. Start the web server `iex -S mix phx.server`
3. Start prometheus: `prometheus --config.file=prometheus.yml`
### Grafana
Grafana dashboards may also be used for metrics display.
The Grafana dashboard may also be used for metrics display.
1. Install grafana: `brew install grafana`
2. Install Pie Chart panel plugin: `grafana-cli plugins install grafana-piechart-panel`
@ -32,3 +36,5 @@ Grafana dashboards may also be used for metrics display.
2. Copy the contents of the JSON file in the "Or paste JSON" entry
3. Click "Load"
6. View the dashboards. (You will need to click-around and use BlockScout for the web-related metrics to show up.)

Loading…
Cancel
Save