diff --git a/CHANGELOG.md b/CHANGELOG.md
index edb75b6440..8227c01b41 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## Current
### Features
+- [#3154](https://github.com/poanetwork/blockscout/pull/3154) - Support of Hyperledger Besu client
- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: logs decoding using implementation ABI
- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: methods decoding using implementation ABI
- [#3149](https://github.com/poanetwork/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint.
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
index ce01098d43..d54f71fb41 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
@@ -55,12 +55,16 @@
class: "dropdown-item #{tab_status("txs", @conn.request_path)}",
to: transaction_path(@conn, :index)
) %>
- <%= link(
- gettext("Pending"),
- class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}",
- "data-test": "pending_transactions_link",
- to: pending_transaction_path(@conn, :index)
- ) %>
+ <% json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)%>
+ <% variant = Keyword.fetch!(json_rpc_named_arguments, :variant) %>
+ <%= if variant !== EthereumJSONRPC.Besu do %>
+ <%= link(
+ gettext("Pending"),
+ class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}",
+ "data-test": "pending_transactions_link",
+ to: pending_transaction_path(@conn, :index)
+ ) %>
+ <% end %>
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index 9157634e2c..ac3a6b36cb 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:81
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:71
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts"
msgstr ""
@@ -636,7 +636,7 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:95
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC"
msgstr ""
@@ -737,7 +737,7 @@ msgid "Github"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL"
msgstr ""
@@ -1158,7 +1158,7 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:62
#: lib/block_scout_web/views/transaction_view.ex:250
#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
@@ -1196,7 +1196,7 @@ msgid "Query"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC"
msgstr ""
@@ -1254,14 +1254,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:141
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:145
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
#: lib/block_scout_web/templates/layout/_topnav.html.eex:139
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:143
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index 9157634e2c..ac3a6b36cb 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:81
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:71
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts"
msgstr ""
@@ -636,7 +636,7 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:95
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC"
msgstr ""
@@ -737,7 +737,7 @@ msgid "Github"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL"
msgstr ""
@@ -1158,7 +1158,7 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:62
#: lib/block_scout_web/views/transaction_view.ex:250
#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
@@ -1196,7 +1196,7 @@ msgid "Query"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC"
msgstr ""
@@ -1254,14 +1254,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:141
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:145
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
#: lib/block_scout_web/templates/layout/_topnav.html.eex:139
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:143
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
new file mode 100644
index 0000000000..4e834d78a8
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
@@ -0,0 +1,305 @@
+# credo:disable-for-this-file
+defmodule EthereumJSONRPC.Besu do
+ @moduledoc """
+ Ethereum JSONRPC methods that are only supported by [Besu](https://besu.hyperledger.org/en/stable/Reference/API-Methods).
+ """
+ require Logger
+ import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
+
+ alias EthereumJSONRPC.Parity.{FetchedBeneficiaries, Traces}
+ alias EthereumJSONRPC.{Transaction, Transactions}
+
+ @behaviour EthereumJSONRPC.Variant
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_beneficiaries(block_numbers, json_rpc_named_arguments)
+ when is_list(block_numbers) and is_list(json_rpc_named_arguments) do
+ id_to_params =
+ block_numbers
+ |> block_numbers_to_params_list()
+ |> id_to_params()
+
+ with {:ok, responses} <-
+ id_to_params
+ |> FetchedBeneficiaries.requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)}
+ end
+ end
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
+
+ @doc """
+ Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Besu trace URL.
+ """
+ @impl EthereumJSONRPC.Variant
+ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
+ id_to_params = id_to_params(block_numbers)
+
+ with {:ok, responses} <-
+ id_to_params
+ |> trace_replay_block_transactions_requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
+ 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)
+
+ trace_replay_transaction_response =
+ id_to_params
+ |> trace_replay_transaction_requests()
+ |> json_rpc(json_rpc_named_arguments)
+
+ case trace_replay_transaction_response do
+ {:ok, responses} ->
+ case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do
+ {:ok, [first_trace]} ->
+ %{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}]}
+
+ {:error, error} ->
+ Logger.error(inspect(error))
+ {:error, error}
+ end
+
+ {:error, :econnrefused} ->
+ {:error, :econnrefused}
+
+ {:error, [error]} ->
+ Logger.error(inspect(error))
+ {:error, error}
+ end
+ end
+
+ @doc """
+ Fetches the pending transactions from the Besu node.
+
+ *NOTE*: The pending transactions are local to the node that is contacted and may not be consistent across nodes based
+ on the transactions that each node has seen and how each node prioritizes collating transactions into the next block.
+ """
+ @impl EthereumJSONRPC.Variant
+ @spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) ::
+ {:ok, [Transaction.params()]} | {:error, reason :: term}
+ def fetch_pending_transactions(json_rpc_named_arguments) do
+ with {:ok, transactions} <-
+ %{id: 1, method: "txpool_besuTransactions", params: []}
+ |> request()
+ |> json_rpc(json_rpc_named_arguments) do
+ transactions_params =
+ transactions
+ |> Transactions.to_elixir()
+ |> Transactions.elixir_to_params()
+
+ {:ok, transactions_params}
+ end
+ end
+
+ defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do
+ Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
+ end
+
+ defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do
+ params =
+ traces
+ |> Traces.to_elixir()
+ |> Traces.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&trace_replay_block_transactions_response_to_traces(&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_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
+ when is_list(results) and is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_traces =
+ results
+ |> Stream.with_index()
+ |> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
+ traces
+ |> Stream.with_index()
+ |> Enum.map(fn {trace, index} ->
+ Map.merge(trace, %{
+ "blockNumber" => block_number,
+ "transactionHash" => transaction_hash,
+ "transactionIndex" => transaction_index,
+ "index" => index
+ })
+ end)
+ end)
+
+ {:ok, annotated_traces}
+ end
+
+ defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockNumber" => block_number
+ })
+
+ {:error, annotated_error}
+ end
+
+ defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, block_number} ->
+ trace_replay_block_transactions_request(%{id: id, block_number: block_number})
+ end)
+ end
+
+ defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
+ request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
+ end
+
+ def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do
+ params =
+ traces
+ |> Traces.to_elixir()
+ |> Traces.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params))
+ |> Enum.reduce(
+ {:ok, []},
+ fn
+ {:ok, traces}, {:ok, acc_traces_list} ->
+ {:ok, [traces | acc_traces_list]}
+
+ {:ok, _}, {:error, _} = acc_error ->
+ acc_error
+
+ {:error, reason}, {:ok, _} ->
+ {:error, [reason]}
+
+ {:error, reason}, {:error, acc_reason} ->
+ {:error, [reason | acc_reason]}
+ end
+ )
+ |> case do
+ {:ok, traces_list} ->
+ traces =
+ traces_list
+ |> Enum.reverse()
+ |> List.flatten()
+
+ {:ok, traces}
+
+ {:error, reverse_reasons} ->
+ reasons = Enum.reverse(reverse_reasons)
+ {:error, reasons}
+ end
+ end
+
+ defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params)
+ when is_list(traces) and is_map(id_to_params) do
+ %{
+ block_hash: block_hash,
+ block_number: block_number,
+ hash_data: transaction_hash,
+ transaction_index: transaction_index
+ } = Map.fetch!(id_to_params, id)
+
+ first_trace =
+ traces
+ |> Stream.with_index()
+ |> Enum.map(fn {trace, index} ->
+ Map.merge(trace, %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "index" => index,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash
+ })
+ end)
+ |> Enum.filter(fn trace ->
+ Map.get(trace, "index") == 0
+ end)
+
+ {:ok, first_trace}
+ end
+
+ defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ %{
+ block_hash: block_hash,
+ block_number: block_number,
+ hash_data: transaction_hash,
+ transaction_index: transaction_index
+ } = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash
+ })
+
+ {:error, annotated_error}
+ end
+
+ defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} ->
+ trace_replay_transaction_request(%{id: id, hash_data: hash_data})
+ end)
+ end
+
+ defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
+ request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
new file mode 100644
index 0000000000..cb569cf9f2
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
@@ -0,0 +1,196 @@
+defmodule EthereumJSONRPC.Besu.FetchedBeneficiaries do
+ @moduledoc """
+ Beneficiaries and errors from batch requests to `trace_block`.
+ """
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+
+ @doc """
+ Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`.
+
+ responses - List with trace_block responses
+ id_to_params - Maps request id to query params
+
+ ## Examples
+ iex> EthereumJSONRPC.Besu.FetchedBeneficiaries.from_responses(
+ ...> [
+ ...> %{
+ ...> id: 0,
+ ...> result: [
+ ...> %{
+ ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"},
+ ...> "blockHash" => "0xFFF",
+ ...> "blockNumber" => 12,
+ ...> "result" => nil,
+ ...> "subtraces" => 0,
+ ...> "traceAddress" => [],
+ ...> "transactionHash" => nil,
+ ...> "transactionPosition" => nil,
+ ...> "type" => "reward"
+ ...> },
+ ...> %{
+ ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"},
+ ...> "blockHash" => "0xFFF",
+ ...> "blockNumber" => 12,
+ ...> "result" => nil,
+ ...> "subtraces" => 0,
+ ...> "traceAddress" => [],
+ ...> "transactionHash" => nil,
+ ...> "transactionPosition" => nil,
+ ...> "type" => "reward"
+ ...> }
+ ...> ]
+ ...> }
+ ...> ],
+ ...> %{0 => %{block_quantity: "0xC"}}
+ ...> )
+ %EthereumJSONRPC.FetchedBeneficiaries{
+ errors: [],
+ params_set: #MapSet<[
+ %{
+ address_hash: "0x1",
+ address_type: :validator,
+ block_hash: "0xFFF",
+ block_number: 12,
+ reward: "0x0"
+ },
+ %{
+ address_hash: "0x2",
+ address_type: :emission_funds,
+ block_hash: "0xFFF",
+ block_number: 12,
+ reward: "0x0"
+ }
+ ]>
+ }
+ """
+ def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&response_to_params_set(&1, id_to_params))
+ |> Enum.reduce(
+ %EthereumJSONRPC.FetchedBeneficiaries{},
+ fn
+ {:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc ->
+ %EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)}
+
+ {:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc ->
+ %EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]}
+ end
+ )
+ end
+
+ @doc """
+ `trace_block` requests for `id_to_params`.
+ """
+ def requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, %{block_quantity: block_quantity}} ->
+ request(%{id: id, block_quantity: block_quantity})
+ end)
+ end
+
+ @spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) ::
+ {:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}}
+ when id: non_neg_integer(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+
+ {:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}}
+ end
+
+ @spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) ::
+ {:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())}
+ when id: non_neg_integer(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+ block_number = quantity_to_integer(block_quantity)
+ params_set = traces_to_params_set(traces, block_number)
+
+ {:ok, params_set}
+ end
+
+ @spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{
+ id => %{block_quantity: block_quantity}
+ }) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}}
+ when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+
+ annotated_error = Map.put(error, :data, %{block_quantity: block_quantity})
+
+ {:error, annotated_error}
+ end
+
+ defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do
+ EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]})
+ end
+
+ defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do
+ traces
+ |> Stream.filter(&(&1["type"] == "reward"))
+ |> Stream.with_index()
+ |> Enum.reduce(MapSet.new(), fn {trace, index}, acc ->
+ MapSet.union(acc, trace_to_params_set(trace, block_number, index))
+ end)
+ end
+
+ defp trace_to_params_set(
+ %{
+ "action" => %{
+ "rewardType" => reward_type,
+ "author" => address_hash_data,
+ "value" => reward_value
+ },
+ "blockHash" => block_hash,
+ "blockNumber" => block_number
+ },
+ block_number,
+ index
+ )
+ when is_integer(block_number) and reward_type in ~w(block external uncle) do
+ MapSet.new([
+ %{
+ address_hash: address_hash_data,
+ block_hash: block_hash,
+ block_number: block_number,
+ reward: reward_value,
+ address_type: get_address_type(reward_type, index)
+ }
+ ])
+ end
+
+ # Beneficiary's address type will depend on the responses' action.rewardType,
+ # which will vary depending on which network is being indexed
+ #
+ # On POA networks, rewardType will always be external and the type of the address being
+ # rewarded will depend on its position.
+ # First address will always be the validator's while the second will be the EmissionsFunds address
+ #
+ # On PoW networks, like Ethereum, the reward type will already specify the type for the
+ # address being rewarded
+ # The rewardType "block" will show the reward for the consensus block validator
+ # The rewardType "uncle" will show reward for validating an uncle block
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 2, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 3, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 4, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 5, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 6, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 7, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 8, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 9, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 10, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 11, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 12, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 13, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 14, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 15, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 16, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 17, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 18, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 19, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 20, do: :validator
+ defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator
+ defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle
+ defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
index 03e8c8457c..05e32348f2 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
@@ -38,7 +38,7 @@ defmodule EthereumJSONRPC.Geth do
end
@doc """
- Fetches the first trace from the Parity trace URL.
+ Fetches the first trace from the trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
index 84a1b94702..8165651ad5 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
@@ -1,3 +1,4 @@
+# credo:disable-for-this-file
defmodule EthereumJSONRPC.Parity do
@moduledoc """
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/).
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index 8424590e95..57d5030cf5 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -207,6 +207,11 @@ config :explorer, Explorer.Chain.Cache.Accounts,
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.PendingTransactions,
+ enabled:
+ if(System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu",
+ do: false,
+ else: true
+ ),
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs
new file mode 100644
index 0000000000..e014ac960c
--- /dev/null
+++ b/apps/explorer/config/dev/besu.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :explorer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
+ method_to_url: [
+ eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ ],
+ http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs
new file mode 100644
index 0000000000..4b69e1363e
--- /dev/null
+++ b/apps/explorer/config/prod/besu.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :explorer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
+ method_to_url: [
+ eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
+ ],
+ http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/config/test/besu.exs b/apps/explorer/config/test/besu.exs
new file mode 100644
index 0000000000..f5c234c7e1
--- /dev/null
+++ b/apps/explorer/config/test/besu.exs
@@ -0,0 +1,14 @@
+use Mix.Config
+
+config :explorer,
+ transport: EthereumJSONRPC.HTTP,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs
index 071f741a74..24b9edd909 100644
--- a/apps/indexer/config/config.exs
+++ b/apps/indexer/config/config.exs
@@ -38,6 +38,9 @@ config :indexer,
first_block: System.get_env("FIRST_BLOCK") || "0",
last_block: System.get_env("LAST_BLOCK") || ""
+config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor,
+ disabled?: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu"
+
# config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true
# config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true
config :indexer, Indexer.Fetcher.StakingPools.Supervisor, disabled?: true
diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs
new file mode 100644
index 0000000000..dc61acea33
--- /dev/null
+++ b/apps/indexer/config/dev/besu.exs
@@ -0,0 +1,30 @@
+use Mix.Config
+
+config :indexer,
+ block_interval: :timer.seconds(5),
+ json_rpc_named_arguments: [
+ transport:
+ if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
+ do: EthereumJSONRPC.HTTP,
+ else: EthereumJSONRPC.IPC
+ ),
+ else: EthereumJSONRPC.IPC,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
+ method_to_url: [
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ ],
+ http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ]
+ ]
diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs
new file mode 100644
index 0000000000..aad6529e5c
--- /dev/null
+++ b/apps/indexer/config/prod/besu.exs
@@ -0,0 +1,29 @@
+use Mix.Config
+
+config :indexer,
+ block_interval: :timer.seconds(5),
+ json_rpc_named_arguments: [
+ transport:
+ if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
+ do: EthereumJSONRPC.HTTP,
+ else: EthereumJSONRPC.IPC
+ ),
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
+ method_to_url: [
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
+ ],
+ http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ]
+ ]
diff --git a/apps/indexer/config/test/besu.exs b/apps/indexer/config/test/besu.exs
new file mode 100644
index 0000000000..ceaac3a9b4
--- /dev/null
+++ b/apps/indexer/config/test/besu.exs
@@ -0,0 +1,8 @@
+use Mix.Config
+
+config :indexer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
index e9d914bd8c..4ed5a85888 100644
--- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
+++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
@@ -105,6 +105,9 @@ defmodule Indexer.Fetcher.InternalTransaction do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
+ EthereumJSONRPC.Besu ->
+ EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
+
_ ->
fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments)
end