diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b590f8e884a..716d727d4d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth - [#6721](https://github.com/blockscout/blockscout/pull/6721) - Implement fetching internal transactions from callTracer - [#5561](https://github.com/blockscout/blockscout/pull/5561), [#6523](https://github.com/blockscout/blockscout/pull/6523) - Improve working with contracts implementations - [#6401](https://github.com/blockscout/blockscout/pull/6401) - Add Sol2Uml contract visualization diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 9a9258a742ad..8a7106b595b3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -25,6 +25,8 @@ defmodule EthereumJSONRPC do documentation for `EthereumJSONRPC.RequestCoordinator`. """ + require Logger + alias EthereumJSONRPC.{ Block, Blocks, @@ -380,6 +382,29 @@ defmodule EthereumJSONRPC do |> Enum.into(%{}, fn {params, id} -> {id, params} end) end + @doc """ + Assigns not matched ids between requests and responses to responses with incorrect ids + """ + def sanitize_responses(responses, id_to_params) do + responses + |> Enum.reduce( + {[], Map.keys(id_to_params) -- Enum.map(responses, & &1.id)}, + fn + %{id: nil} = res, {result_res, [id | rest]} -> + Logger.error( + "Empty id in response: #{inspect(res)}, stacktrace: #{inspect(Process.info(self(), :current_stacktrace))}" + ) + + {[%{res | id: id} | result_res], rest} + + res, {result_res, non_matched} -> + {[res | result_res], non_matched} + end + ) + |> elem(0) + |> Enum.reverse() + end + @doc """ 1. POSTs JSON `payload` to `url` 2. Decodes the response @@ -404,7 +429,7 @@ defmodule EthereumJSONRPC do @doc """ Converts `t:quantity/0` to `t:non_neg_integer/0`. """ - @spec quantity_to_integer(quantity) :: non_neg_integer() | :error + @spec quantity_to_integer(quantity) :: non_neg_integer() | nil def quantity_to_integer("0x" <> hexadecimal_digits) do String.to_integer(hexadecimal_digits, 16) end @@ -414,10 +439,12 @@ defmodule EthereumJSONRPC do def quantity_to_integer(string) when is_binary(string) do case Integer.parse(string) do {integer, ""} -> integer - _ -> :error + _ -> nil end end + def quantity_to_integer(_), do: nil + @doc """ Converts `t:non_neg_integer/0` to `t:quantity/0` """ @@ -489,7 +516,7 @@ defmodule EthereumJSONRPC do """ def timestamp_to_datetime(timestamp) do case quantity_to_integer(timestamp) do - :error -> + nil -> nil quantity -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index dc1740a4aaa5..4d157b317749 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -32,6 +32,7 @@ defmodule EthereumJSONRPC.Blocks do def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do %{errors: errors, blocks: blocks} = responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&Block.from_response(&1, id_to_params)) |> Enum.reduce(%{errors: [], blocks: []}, fn {:ok, block}, %{blocks: blocks} = acc -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex index dc629c720a39..da769b5e5a2d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex @@ -19,6 +19,7 @@ defmodule EthereumJSONRPC.FetchedBalances do """ def from_responses(responses, id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&FetchedBalance.from_response(&1, id_to_params)) |> Enum.reduce( %__MODULE__{}, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex index 4e55e6e2a249..60e65665d967 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex @@ -77,6 +77,7 @@ defmodule EthereumJSONRPC.FetchedBeneficiaries do """ def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&response_to_params_set(&1, id_to_params)) |> Enum.reduce( %EthereumJSONRPC.FetchedBeneficiaries{}, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex index 8369285194a3..257037990931 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex @@ -34,6 +34,7 @@ defmodule EthereumJSONRPC.FetchedCodes do """ def from_responses(responses, id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&FetchedCode.from_response(&1, id_to_params)) |> Enum.reduce( %__MODULE__{}, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 599dbb32e262..3758de6252b8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -46,12 +46,65 @@ defmodule EthereumJSONRPC.Geth do def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore @doc """ - Internal transaction fetching for entire blocks is not currently supported for Geth. - - To signal to the caller that fetching is not supported, `:ignore` is returned. + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Geth trace URL. """ @impl EthereumJSONRPC.Variant - def fetch_block_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore + def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) do + id_to_params = id_to_params(block_numbers) + + with {:ok, blocks_responses} <- + id_to_params + |> debug_trace_block_by_number_requests() + |> json_rpc(json_rpc_named_arguments), + :ok <- check_errors_exist(blocks_responses, id_to_params) do + transactions_params = to_transactions_params(blocks_responses, id_to_params) + + {transactions_id_to_params, transactions_responses} = + Enum.reduce(transactions_params, {%{}, []}, fn {params, calls}, {id_to_params_acc, calls_acc} -> + {Map.put(id_to_params_acc, params[:id], params), [calls | calls_acc]} + end) + + debug_trace_transaction_responses_to_internal_transactions_params( + transactions_responses, + transactions_id_to_params, + json_rpc_named_arguments + ) + end + end + + defp check_errors_exist(blocks_responses, id_to_params) do + blocks_responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) + |> Enum.reduce([], fn + %{result: _result}, acc -> acc + %{error: error}, acc -> [error | acc] + end) + |> case do + [] -> :ok + errors -> {:error, errors} + end + end + + defp to_transactions_params(blocks_responses, id_to_params) do + Enum.reduce(blocks_responses, [], fn %{id: id, result: tx_result}, blocks_acc -> + extract_transactions_params(Map.fetch!(id_to_params, id), tx_result) ++ blocks_acc + end) + end + + defp extract_transactions_params(block_number, tx_result) do + tx_result + |> Enum.reduce({[], 0}, fn %{"txHash" => tx_hash, "result" => calls_result}, {tx_acc, counter} -> + { + [ + {%{block_number: block_number, hash_data: tx_hash, transaction_index: counter, id: counter}, + %{id: counter, result: calls_result}} + | tx_acc + ], + counter + 1 + } + end) + |> elem(0) + end @doc """ Fetches the pending transactions from the Geth node. @@ -73,6 +126,10 @@ defmodule EthereumJSONRPC.Geth do end) end + defp debug_trace_block_by_number_requests(id_to_params) do + Enum.map(id_to_params, &debug_trace_block_by_number_request/1) + end + @tracer_path "priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js" @external_resource @tracer_path @tracer File.read!(@tracer_path) @@ -80,22 +137,52 @@ defmodule EthereumJSONRPC.Geth do defp debug_trace_transaction_request(%{id: id, hash_data: hash_data}) do timeout = Application.get_env(:ethereum_jsonrpc, :internal_transaction_timeout) - tracer = - case Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] do - "js" -> @tracer - "call_tracer" -> "callTracer" - end - request(%{ id: id, method: "debug_traceTransaction", params: [ hash_data, - %{tracer: tracer, disableStack: true, disableMemory: true, disableStorage: true, timeout: timeout} + %{timeout: timeout} |> Map.merge(tracer_params()) ] }) end + defp debug_trace_block_by_number_request({id, block_number}) do + timeout = Application.get_env(:ethereum_jsonrpc, :internal_transaction_timeout) + + request(%{ + id: id, + method: "debug_traceBlockByNumber", + params: [ + integer_to_quantity(block_number), + %{timeout: timeout} |> Map.merge(tracer_params()) + ] + }) + end + + defp tracer_params do + cond do + tracer_type() == "js" -> + %{"tracer" => @tracer} + + tracer_type() in ~w(opcode polygon_edge) -> + %{ + "enableMemory" => true, + "disableStack" => false, + "disableStorage" => true, + "enableReturnData" => false + } + + true -> + %{ + "tracer" => "callTracer", + "disableStack" => true, + "disableMemory" => true, + "disableStorage" => true + } + end + end + defp debug_trace_transaction_responses_to_internal_transactions_params( [%{result: %{"structLogs" => _}} | _] = responses, id_to_params, @@ -136,6 +223,7 @@ defmodule EthereumJSONRPC.Geth do ) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1, id_to_params)) |> reduce_internal_transactions_params() end @@ -328,4 +416,8 @@ defmodule EthereumJSONRPC.Geth do defp finalize_internal_transactions_params({:error, acc_reasons}) do {:error, Enum.reverse(acc_reasons)} end + + defp tracer_type do + Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] + end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index 5f01093dae0f..226145ca64d4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -159,15 +159,18 @@ defmodule EthereumJSONRPC.HTTP do standardized = %{jsonrpc: jsonrpc, id: id} - case unstandardized do - %{"result" => _, "error" => _} -> + case {id, unstandardized} do + {_id, %{"result" => _, "error" => _}} -> raise ArgumentError, "result and error keys are mutually exclusive in JSONRPC 2.0 response objects, but got #{inspect(unstandardized)}" - %{"result" => result} -> + {nil, %{"result" => error}} -> + Map.put(standardized, :error, standardize_error(error)) + + {_id, %{"result" => result}} -> Map.put(standardized, :result, result) - %{"error" => error} -> + {_id, %{"error" => error}} -> Map.put(standardized, :error, standardize_error(error)) end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index b3d177411e42..dc1478ecb0a4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -265,6 +265,7 @@ defmodule EthereumJSONRPC.Receipts do defp reduce_responses(responses, id_to_transaction_params) when is_list(responses) and is_map(id_to_transaction_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_transaction_params) |> Stream.map(&response_to_receipt(&1, id_to_transaction_params)) |> Enum.reduce({:ok, []}, &reduce_receipt(&1, &2)) end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex index 49b395b86b04..8fc6adfead5d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex @@ -66,6 +66,7 @@ defmodule EthereumJSONRPC.TraceReplayBlockTransactions do defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params)) |> Enum.reduce( {:ok, []}, @@ -158,6 +159,7 @@ defmodule EthereumJSONRPC.TraceReplayBlockTransactions do defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params)) |> Enum.reduce( {:ok, []}, diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 214ed1bd6a1b..cf139b276353 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -437,8 +437,172 @@ defmodule EthereumJSONRPC.GethTest do end describe "fetch_block_internal_transactions/1" do - test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do - EthereumJSONRPC.Geth.fetch_block_internal_transactions([], json_rpc_named_arguments) + setup do + EthereumJSONRPC.Case.Geth.Mox.setup() + end + + test "is supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number = 3_287_375 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" + + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^block_quantity, %{"tracer" => "callTracer"}]}], + _ -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "result" => %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + }, + "txHash" => transaction_hash + } + ] + } + ]} + end) + + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + + assert {:ok, + [ + %{ + block_number: 3_287_375, + call_type: "call", + from_address_hash: "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + gas: 1_000_000, + gas_used: 46841, + index: 0, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x4200000000000000000000000000000000000015", + trace_address: [], + transaction_hash: ^transaction_hash, + transaction_index: 0, + type: "call", + value: 0 + }, + %{ + block_number: 3_287_375, + call_type: "delegatecall", + from_address_hash: "0x4200000000000000000000000000000000000015", + gas: 956_988, + gas_used: 18984, + index: 1, + input: + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + output: "0x", + to_address_hash: "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + trace_address: [0], + transaction_hash: ^transaction_hash, + transaction_index: 0, + type: "call", + value: 0 + } + ]} = Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) + end + + test "result is the same as fetch_internal_transactions/2", %{json_rpc_named_arguments: json_rpc_named_arguments} do + block_number = 3_287_375 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" + + expect(EthereumJSONRPC.Mox, :json_rpc, 2, fn + [%{id: id, params: [^block_quantity, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: [ + %{ + "result" => %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + }, + "txHash" => transaction_hash + } + ] + } + ]} + + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: %{ + "calls" => [ + %{ + "from" => "0x4200000000000000000000000000000000000015", + "gas" => "0xe9a3c", + "gasUsed" => "0x4a28", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x6df83a19647a398d48e77a6835f4a28eb7e2f7c0", + "type" => "DELEGATECALL", + "value" => "0x0" + } + ], + "from" => "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas" => "0xf4240", + "gasUsed" => "0xb6f9", + "input" => + "0x015d8eb900000000000000000000000000000000000000000000000000000000009cb0d80000000000000000000000000000000000000000000000000000000065898738000000000000000000000000000000000000000000000000000000000000001b65f7961a6893850c1f001edeaa0aa4f1fb36b67eee61a8623f8f4da81be25c0000000000000000000000000000000000000000000000000000000000000000050000000000000000000000007431310e026b69bfc676c0013e12a1a11411eec9000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240", + "to" => "0x4200000000000000000000000000000000000015", + "type" => "CALL", + "value" => "0x0" + } + } + ]} + end) + + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + + assert Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) == + Geth.fetch_internal_transactions( + [%{block_number: block_number, transaction_index: 0, hash_data: transaction_hash}], + json_rpc_named_arguments + ) end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index b0a2ec4b3136..fdf034ee5f61 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -973,7 +973,7 @@ defmodule EthereumJSONRPCSyncTest do params_list: [ %{ address_hash: hash, - block_number: :error, + block_number: nil, value: expected_fetched_balance } ] diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index ee9bcfbdce9d..b5714dab442f 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -89,24 +89,7 @@ defmodule Indexer.Fetcher.InternalTransaction do json_rpc_named_arguments |> Keyword.fetch!(:variant) - |> case do - EthereumJSONRPC.Nethermind -> - EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) - - EthereumJSONRPC.Erigon -> - EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) - - EthereumJSONRPC.Besu -> - EthereumJSONRPC.fetch_block_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) - - _jsonrpc_variant -> - try do - fetch_block_internal_transactions_by_transactions(filtered_unique_numbers, json_rpc_named_arguments) - rescue - error -> - {:error, error} - end - end + |> fetch_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) |> case do {:ok, internal_transactions_params} -> import_internal_transaction(internal_transactions_params, filtered_unique_numbers) @@ -126,6 +109,33 @@ defmodule Indexer.Fetcher.InternalTransaction do end end + defp fetch_internal_transactions(variant, block_numbers, json_rpc_named_arguments) do + if variant in block_traceable_variants() do + EthereumJSONRPC.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) + else + try do + fetch_block_internal_transactions_by_transactions(block_numbers, json_rpc_named_arguments) + rescue + error -> + {:error, error, __STACKTRACE__} + end + end + end + + @default_block_traceable_variants [ + EthereumJSONRPC.Nethermind, + EthereumJSONRPC.Erigon, + EthereumJSONRPC.Besu, + EthereumJSONRPC.RSK + ] + defp block_traceable_variants do + if Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth)[:block_traceable?] do + [EthereumJSONRPC.Geth | @default_block_traceable_variants] + else + @default_block_traceable_variants + end + end + def import_first_trace(internal_transactions_params) do imports = Chain.import(%{ @@ -232,7 +242,28 @@ defmodule Indexer.Fetcher.InternalTransaction do end defp import_internal_transaction(internal_transactions_params, unique_numbers) do - internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) + unique_hashes = + unique_numbers + |> Enum.map(fn block_number -> + case Chain.number_to_any_block(block_number) do + {:ok, block} -> block.hash + {:error, :not_found} -> nil + end + end) + + block_number_to_hash_map = + unique_numbers + |> Enum.zip(unique_hashes) + |> Map.new() + + # enrich internal TXs with block hashes + enriched_itx_params = + internal_transactions_params + |> Enum.map(fn itx -> + Map.put(itx, :block_hash, block_number_to_hash_map[itx.block_number]) + end) + + internal_transactions_params_without_failed_creations = remove_failed_creations(enriched_itx_params) addresses_params = Addresses.extract_addresses(%{ diff --git a/config/runtime.exs b/config/runtime.exs index f6bbfaf01803..fe3981712cea 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -194,6 +194,7 @@ debug_trace_transaction_timeout = System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_T config :ethereum_jsonrpc, :internal_transaction_timeout, debug_trace_transaction_timeout config :ethereum_jsonrpc, EthereumJSONRPC.Geth, + block_traceable?: System.get_env("ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK", "false") == "true", tracer: System.get_env("INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE", "js") config :ethereum_jsonrpc, EthereumJSONRPC.PendingTransaction, diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 7f496a61b154..f8acffc27e94 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -10,6 +10,7 @@ LOGO_FOOTER=/images/blockscout_logo.svg # ETHEREUM_JSONRPC_WS_URL= ETHEREUM_JSONRPC_TRANSPORT=http ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false +# ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK= IPC_PATH= NETWORK_PATH=/ API_PATH=/