Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fetching internal transactions from callTracer #885

Merged
merged 11 commits into from
Jul 18, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- [#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
- [#6481](https://github.com/blockscout/blockscout/pull/6481) - Smart contract verification improvements
Expand Down Expand Up @@ -29,6 +30,7 @@

### Fixes

- [#6827](https://github.com/blockscout/blockscout/pull/6827) - Fix handling unknown calls from `callTracer`
- [#6532](https://github.com/blockscout/blockscout/pull/6532) - Fix index creation migration
- [#6473](https://github.com/blockscout/blockscout/pull/6473) - Fix state changes for contract creation transactions
- [#6475](https://github.com/blockscout/blockscout/pull/6475) - Fix token name with unicode graphemes shortening
Expand Down
76 changes: 75 additions & 1 deletion apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule EthereumJSONRPC.Geth do
Ethereum JSONRPC methods that are only supported by [Geth](https://github.com/ethereum/go-ethereum/wiki/geth).
"""

require Logger

import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]

alias EthereumJSONRPC.{FetchedBalance, FetchedCode, PendingTransaction}
Expand Down Expand Up @@ -78,12 +80,18 @@ 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}
%{tracer: tracer, disableStack: true, disableMemory: true, disableStorage: true, timeout: timeout}
]
})
end
Expand Down Expand Up @@ -177,6 +185,7 @@ defmodule EthereumJSONRPC.Geth do

internal_transaction_params =
calls
|> prepare_calls()
|> Stream.with_index()
|> Enum.map(fn {trace, index} ->
Map.merge(trace, %{
Expand Down Expand Up @@ -223,6 +232,71 @@ defmodule EthereumJSONRPC.Geth do
{:error, annotated_error}
end

defp prepare_calls(calls) do
case Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] do
"call_tracer" -> {calls, 0} |> parse_call_tracer_calls([], [], false) |> Enum.reverse()
"js" -> calls
end
end

defp parse_call_tracer_calls(calls, acc, trace_address, inner? \\ true)
defp parse_call_tracer_calls([], acc, _trace_address, _inner?), do: acc
defp parse_call_tracer_calls({%{"type" => 0}, _}, acc, _trace_address, _inner?), do: acc

defp parse_call_tracer_calls(
{%{"type" => type, "from" => from} = call, index},
acc,
trace_address,
inner?
)
when type in ~w(CALL CALLCODE DELEGATECALL STATICCALL CREATE CREATE2 SELFDESTRUCT REWARD) do
new_trace_address = [index | trace_address]

formatted_call =
%{
"type" => if(type in ~w(CALL CALLCODE DELEGATECALL STATICCALL), do: "call", else: String.downcase(type)),
"callType" => String.downcase(type),
"from" => from,
"to" => Map.get(call, "to", "0x"),
"createdContractAddressHash" => Map.get(call, "to", "0x"),
"value" => Map.get(call, "value", "0x0"),
"gas" => Map.get(call, "gas", "0x0"),
"gasUsed" => Map.get(call, "gasUsed", "0x0"),
"input" => Map.get(call, "input", "0x"),
"init" => Map.get(call, "input", "0x"),
"createdContractCode" => Map.get(call, "output", "0x"),
"traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []),
"error" => call["error"]
}
|> case do
%{"error" => nil} = ok_call ->
ok_call
|> Map.delete("error")
# to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1
|> Map.put("output", Map.get(call, "output", "0x"))

error_call ->
error_call
end

parse_call_tracer_calls(
Map.get(call, "calls", []),
[formatted_call | acc],
if(inner?, do: new_trace_address, else: [])
)
end

defp parse_call_tracer_calls({call, _}, acc, _trace_address, _inner?) do
Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}")
acc
end

defp parse_call_tracer_calls(calls, acc, trace_address, _inner) when is_list(calls) do
calls
|> Stream.with_index()
|> Enum.reduce(acc, &parse_call_tracer_calls(&1, &2, trace_address))
end

defp reduce_internal_transactions_params(internal_transactions_params) when is_list(internal_transactions_params) do
internal_transactions_params
|> Enum.reduce({:ok, []}, &internal_transactions_params_reducer/2)
Expand Down
Loading