Add polygon edge support

mf-fix-polygon-edge
Maxim Filonov 2 years ago
parent 033ced834f
commit 56ae240340
  1. 1
      CHANGELOG.md
  2. 30
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
  3. 355
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/polygon_tracer.ex
  4. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
  5. 3
      apps/indexer/lib/indexer/block/fetcher.ex
  6. 2
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  7. 2
      apps/indexer/lib/indexer/fetcher/internal_transaction.ex

@ -3,6 +3,7 @@
## Current
### Features
- [#7513](https://github.com/blockscout/blockscout/pull/7513) - Add Polygon Edge support
### Fixes

@ -8,7 +8,7 @@ defmodule EthereumJSONRPC.Geth do
import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
alias EthereumJSONRPC.{FetchedBalance, FetchedCode, PendingTransaction, Utility.CommonHelper}
alias EthereumJSONRPC.Geth.{Calls, Tracer}
alias EthereumJSONRPC.Geth.{Calls, PolygonTracer, Tracer}
@behaviour EthereumJSONRPC.Variant
@ -94,14 +94,25 @@ defmodule EthereumJSONRPC.Geth do
tracer =
case Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] do
"js" -> @tracer
"call_tracer" -> "callTracer"
"js" ->
%{"tracer" => @tracer}
"call_tracer" ->
%{"tracer" => "callTracer"}
_ ->
%{
"enableMemory" => true,
"disableStack" => false,
"disableStorage" => true,
"enableReturnData" => false
}
end
request(%{
id: id,
method: "debug_traceTransaction",
params: [hash_data, %{tracer: tracer, timeout: debug_trace_transaction_timeout}]
params: [hash_data, %{timeout: debug_trace_transaction_timeout} |> Map.merge(tracer)]
})
end
@ -126,10 +137,15 @@ defmodule EthereumJSONRPC.Geth do
receipts_map = Enum.into(receipts, %{}, fn %{id: id, result: receipt} -> {id, receipt} end)
txs_map = Enum.into(txs, %{}, fn %{id: id, result: tx} -> {id, tx} end)
tracer =
if Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] == "polygon_edge",
do: PolygonTracer,
else: Tracer
responses
|> Enum.map(fn %{id: id, result: %{"structLogs" => _} = result} ->
debug_trace_transaction_response_to_internal_transactions_params(
%{id: id, result: Tracer.replay(result, Map.fetch!(receipts_map, id), Map.fetch!(txs_map, id))},
%{id: id, result: tracer.replay(result, Map.fetch!(receipts_map, id), Map.fetch!(txs_map, id))},
id_to_params
)
end)
@ -158,7 +174,7 @@ defmodule EthereumJSONRPC.Geth do
{id, %{created_contract_address_hash: address, block_number: block_number}} ->
FetchedCode.request(%{id: id, block_quantity: integer_to_quantity(block_number), address: address})
{id, %{type: "selfdestruct", from: hash_data, block_number: block_number}} ->
{id, %{type: "selfdestruct", from_address_hash: hash_data, block_number: block_number}} ->
FetchedBalance.request(%{id: id, block_quantity: integer_to_quantity(block_number), hash_data: hash_data})
_ ->
@ -244,7 +260,7 @@ defmodule EthereumJSONRPC.Geth do
def 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
_ -> calls
end
end

@ -0,0 +1,355 @@
defmodule EthereumJSONRPC.Geth.PolygonTracer do
@moduledoc """
Elixir implementation of a custom tracer (`priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js`)
for Polygon edge nodes that don't support specifying tracer in [debug_traceTransaction](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction) calls.
"""
import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1]
@burn_address "0x0000000000000000000000000000000000000000"
def replay(
%{"structLogs" => logs, "gas" => top_call_gas, "returnValue" => return_value} = result,
%{"contractAddress" => contract_address},
%{"from" => from, "to" => to, "value" => value, "input" => input}
)
when is_list(logs) do
top =
to
|> if do
%{
"type" => "call",
"callType" => "call",
"to" => to,
"input" => input,
"output" => Map.get(result, "return", "0x" <> Map.get(result, "returnValue", ""))
}
else
%{
"type" => "create",
"init" => input,
"createdContractAddressHash" => contract_address,
"createdContractCode" => "0x" <> return_value
}
end
|> Map.merge(%{
"from" => from,
"traceAddress" => [],
"value" => value,
"gas" => 0,
"gasUsed" => 0
})
ctx = %{
depth: 1,
stack: [top],
trace_address: [0],
calls: [[]],
descended: false
}
logs
|> Enum.reduce(ctx, &step/2)
|> finalize(top_call_gas)
end
defp step(%{"error" => _}, %{stack: [%{"error" => _} | _]} = ctx), do: ctx
defp step(
%{"error" => _} = log,
%{
depth: stack_depth,
stack: [call | stack],
trace_address: [_, trace_index | trace_address],
calls: [subsubcalls, subcalls | calls]
} = ctx
) do
call = process_return(log, Map.put(call, "error", "error"))
subsubcalls =
subsubcalls
|> Stream.map(fn
subcalls when is_list(subcalls) -> subcalls
subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]}
end)
|> Enum.reverse()
%{
ctx
| depth: stack_depth - 1,
stack: stack,
trace_address: [trace_index + 1 | trace_address],
calls: [[subsubcalls, call | subcalls] | calls]
}
end
defp step(
%{"gas" => log_gas} = log,
%{
stack: [%{"gas" => call_gas} = call | stack],
descended: true
} = ctx
) do
gas = max(call_gas, log_gas)
call = %{call | "gas" => gas}
step(log, %{ctx | stack: [call | stack], descended: false})
end
defp step(
%{"depth" => log_depth} = log,
%{
depth: stack_depth,
stack: [call | stack],
trace_address: [_, trace_index | trace_address],
calls: [subsubcalls, subcalls | calls]
} = ctx
)
when log_depth == stack_depth - 1 do
call = process_return(log, call)
subsubcalls =
subsubcalls
|> Stream.map(fn
subcalls when is_list(subcalls) -> subcalls
subcall when is_map(subcall) -> %{subcall | "from" => call["createdContractAddressHash"] || call["to"]}
end)
|> Enum.reverse()
step(log, %{
ctx
| depth: stack_depth - 1,
stack: stack,
trace_address: [trace_index + 1 | trace_address],
calls: [[subsubcalls, call | subcalls] | calls]
})
end
defp step(%{"gas" => log_gas, "gasCost" => log_gas_cost} = log, %{stack: [%{"gas" => call_gas} = call | stack]} = ctx) do
gas = max(call_gas, log_gas)
op(log, %{ctx | stack: [%{call | "gas" => gas, "gasUsed" => gas - log_gas - log_gas_cost} | stack]})
end
defp op(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx)
defp op(%{"op" => "CREATE2"} = log, ctx), do: create_op(log, ctx, "create2")
defp op(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx)
defp op(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx)
defp op(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx)
defp op(%{"op" => "DELEGATECALL"} = log, ctx), do: call_op(log, "delegatecall", ctx)
defp op(%{"op" => "STATICCALL"} = log, ctx), do: call_op(log, "staticcall", ctx)
defp op(%{"op" => "REVERT"}, ctx), do: revert_op(ctx)
defp op(_, ctx), do: ctx
defp process_return(
%{"stack" => log_stack},
%{"type" => create} = call
)
when create in ~w(create create2) do
[ret | _] = Enum.reverse(log_stack)
ret
|> quantity_to_integer()
|> case do
0 ->
Map.put(call, "error", call["error"] || "internal failure")
_ ->
%{call | "createdContractAddressHash" => ret}
end
end
defp process_return(
%{"stack" => log_stack, "memory" => log_memory},
%{"outputOffset" => out_off, "outputLength" => out_len} = call
) do
[ret | _] = Enum.reverse(log_stack)
ret
|> quantity_to_integer()
|> case do
0 ->
Map.put(call, "error", call["error"] || "internal failure")
_ ->
output =
log_memory
|> IO.iodata_to_binary()
|> String.slice(out_off, out_len)
%{call | "output" => "0x" <> output}
end
|> Map.drop(["outputOffset", "outputLength"])
end
defp process_return(_log, call) do
call
end
defp create_op(
%{"stack" => log_stack, "memory" => log_memory},
%{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx,
type \\ "create"
) do
[value, input_length | _] = Enum.reverse(log_stack)
init =
log_memory
|> IO.iodata_to_binary()
|> String.slice(0, quantity_to_integer(input_length) * 2)
call = %{
"type" => type,
"from" => nil,
"traceAddress" => Enum.reverse(trace_address),
"init" => "0x" <> init,
"gas" => 0,
"gasUsed" => 0,
"value" => value,
"createdContractAddressHash" => nil,
"createdContractCode" => "0x"
}
%{
ctx
| depth: stack_depth + 1,
stack: [call | stack],
trace_address: [0 | trace_address],
calls: [[] | calls],
descended: true
}
end
defp self_destruct_op(
%{"stack" => log_stack, "gas" => log_gas, "gasCost" => log_gas_cost},
%{trace_address: [trace_index | trace_address], calls: [subcalls | calls]} = ctx
) do
[to | _] = Enum.reverse(log_stack)
if quantity_to_integer(to) in 1..8 do
ctx
else
call = %{
"type" => "selfdestruct",
"from" => nil,
"to" => to,
"traceAddress" => Enum.reverse([trace_index | trace_address]),
"gas" => log_gas,
"gasUsed" => log_gas_cost,
"value" => "0x0"
}
%{ctx | trace_address: [trace_index + 1 | trace_address], calls: [[call | subcalls] | calls]}
end
end
defp call_op(
%{"stack" => call_stack},
call_type,
%{
depth: stack_depth,
stack: stack,
trace_address: trace_address,
calls: calls
} = ctx
)
when length(call_stack) < 3 do
call = %{
"type" => "call",
"callType" => call_type,
"from" => nil,
"to" => @burn_address,
"traceAddress" => Enum.reverse(trace_address),
"input" => "0x",
"output" => "0x",
"outputOffset" => 0,
"outputLength" => 0,
"gas" => 0,
"gasUsed" => 0,
"value" => "0x0"
}
%{
ctx
| depth: stack_depth + 1,
stack: [call | stack],
trace_address: [0 | trace_address],
calls: [[] | calls],
descended: true
}
end
defp call_op(
%{"stack" => log_stack, "memory" => log_memory},
call_type,
%{
depth: stack_depth,
stack: [%{"value" => parent_value} = parent | stack],
trace_address: trace_address,
calls: calls
} = ctx
) do
log_stack = Enum.reverse(log_stack)
{value, [input_length, output_length | _]} =
case call_type do
"delegatecall" ->
{parent_value, log_stack}
"staticcall" ->
{"0x0", log_stack}
_ ->
[value | rest] = log_stack
{value, rest}
end
input =
log_memory
|> IO.iodata_to_binary()
|> String.slice(0, quantity_to_integer(input_length) * 2)
call = %{
"type" => "call",
"callType" => call_type,
"from" => nil,
"to" => @burn_address,
"traceAddress" => Enum.reverse(trace_address),
"input" => "0x" <> input,
"output" => "0x",
"outputOffset" => quantity_to_integer(input_length) * 2,
"outputLength" => quantity_to_integer(output_length) * 2,
"gas" => 0,
"gasUsed" => 0,
"value" => value
}
%{
ctx
| depth: stack_depth + 1,
stack: [call, parent | stack],
trace_address: [0 | trace_address],
calls: [[] | calls],
descended: true
}
end
defp revert_op(%{stack: [last | stack]} = ctx) do
%{ctx | stack: [Map.put(last, "error", "execution reverted") | stack]}
end
defp finalize(%{stack: [top], calls: [calls]}, top_call_gas) do
calls =
Enum.map(calls, fn
subcalls when is_list(subcalls) ->
subcalls
subcall when is_map(subcall) ->
%{subcall | "from" => top["createdContractAddressHash"] || top["to"]}
end)
[%{top | "gasUsed" => top_call_gas} | Enum.reverse(calls)]
|> List.flatten()
|> Enum.map(fn %{"gas" => gas, "gasUsed" => gas_used} = call ->
%{call | "gas" => integer_to_quantity(gas), "gasUsed" => gas_used |> max(0) |> integer_to_quantity()}
end)
end
end

@ -311,7 +311,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do
[%{top | "gasUsed" => top_call_gas} | Enum.reverse(calls)]
|> List.flatten()
|> Enum.map(fn %{"gas" => gas, "gasUsed" => gas_used} = call ->
%{call | "gas" => integer_to_quantity(gas), "gasUsed" => gas_used |> max(0) |> integer_to_quantity()}
%{call | "gas" => integer_to_quantity(gas), "gasUsed" => integer_to_quantity(gas_used)}
end)
end
end

@ -558,6 +558,7 @@ defmodule Indexer.Block.Fetcher do
hash: hash
} = address_params
) do
{{hash, fetched_coin_balance_block_number}, Map.delete(address_params, :fetched_coin_balance_block_number)}
{{String.downcase(hash), fetched_coin_balance_block_number},
Map.delete(address_params, :fetched_coin_balance_block_number)}
end
end

@ -485,7 +485,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
block_number
_ ->
Map.fetch!(address_hash_to_block_number, address_hash)
Map.fetch!(address_hash_to_block_number, String.downcase(address_hash))
end
%{hash_data: address_hash, block_quantity: integer_to_quantity(block_number)}

@ -220,7 +220,7 @@ defmodule Indexer.Fetcher.InternalTransaction do
address_hash_to_block_number =
Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} ->
{hash, block_number}
{String.downcase(hash), block_number}
end)
empty_block_numbers =

Loading…
Cancel
Save