commit
cd38bfe58f
@ -0,0 +1,119 @@ |
||||
defmodule Indexer.Temporary.FailedCreatedAddresses do |
||||
@moduledoc """ |
||||
Temporary module to fix internal transactions and their created transactions if a parent transaction has failed. |
||||
""" |
||||
use GenServer |
||||
|
||||
require Logger |
||||
|
||||
import Ecto.Query |
||||
|
||||
alias Explorer.Chain.{InternalTransaction, Transaction} |
||||
alias Explorer.Repo |
||||
alias Indexer.Temporary.FailedCreatedAddresses.TaskSupervisor |
||||
|
||||
@task_options [max_concurrency: 3, timeout: :infinity] |
||||
@query_timeout :infinity |
||||
|
||||
def start_link([json_rpc_named_arguments, gen_server_options]) do |
||||
GenServer.start_link(__MODULE__, json_rpc_named_arguments, gen_server_options) |
||||
end |
||||
|
||||
@impl GenServer |
||||
def init(json_rpc_named_arguments) do |
||||
run(json_rpc_named_arguments) |
||||
|
||||
{:ok, json_rpc_named_arguments} |
||||
end |
||||
|
||||
def run(json_rpc_named_arguments) do |
||||
Logger.debug( |
||||
[ |
||||
"Started query to fetch internal transactions that need to be fixed" |
||||
], |
||||
fetcher: :failed_created_addresses |
||||
) |
||||
|
||||
query = |
||||
from(t in Transaction, |
||||
left_join: it in InternalTransaction, |
||||
on: it.transaction_hash == t.hash, |
||||
where: t.status == ^0 and not is_nil(it.created_contract_address_hash) |
||||
) |
||||
|
||||
found_transactions = Repo.all(query, timeout: @query_timeout) |
||||
|
||||
Logger.debug( |
||||
[ |
||||
"Finished query to fetch internal transactions that need to be fixed. Number of records is #{ |
||||
Enum.count(found_transactions) |
||||
}" |
||||
], |
||||
fetcher: :failed_created_addresses |
||||
) |
||||
|
||||
TaskSupervisor |
||||
|> Task.Supervisor.async_stream_nolink( |
||||
found_transactions, |
||||
fn transaction -> fix_internal_transaction(transaction, json_rpc_named_arguments) end, |
||||
@task_options |
||||
) |
||||
|> Enum.to_list() |
||||
end |
||||
|
||||
def fix_internal_transaction(transaction, json_rpc_named_arguments) do |
||||
# credo:disable-for-next-line |
||||
try do |
||||
Logger.debug( |
||||
[ |
||||
"Started fixing transaction #{to_string(transaction.hash)}" |
||||
], |
||||
fetcher: :failed_created_addresses |
||||
) |
||||
|
||||
transaction_with_internal_transactions = Repo.preload(transaction, [:internal_transactions]) |
||||
|
||||
transaction_with_internal_transactions.internal_transactions |
||||
|> Enum.filter(fn internal_transaction -> |
||||
internal_transaction.created_contract_address_hash |
||||
end) |
||||
|> Enum.each(fn internal_transaction -> |
||||
:ok = |
||||
internal_transaction |
||||
|> code_entry() |
||||
|> Indexer.Code.Fetcher.run(json_rpc_named_arguments) |
||||
end) |
||||
|
||||
:ok = |
||||
transaction |
||||
|> transaction_entry() |
||||
|> Indexer.InternalTransaction.Fetcher.run(json_rpc_named_arguments) |
||||
|
||||
Logger.debug( |
||||
[ |
||||
"Finished fixing transaction #{to_string(transaction.hash)}" |
||||
], |
||||
fetcher: :failed_created_addresses |
||||
) |
||||
rescue |
||||
e -> |
||||
Logger.debug( |
||||
[ |
||||
"Failed fixing transaction #{to_string(transaction.hash)} because of #{inspect(e)}" |
||||
], |
||||
fetcher: :failed_created_addresses |
||||
) |
||||
end |
||||
end |
||||
|
||||
def code_entry(%InternalTransaction{ |
||||
block_number: block_number, |
||||
created_contract_address_hash: %{bytes: created_contract_bytes} |
||||
}) do |
||||
[{block_number, created_contract_bytes, <<>>}] |
||||
end |
||||
|
||||
def transaction_entry(%Transaction{hash: %{bytes: bytes}, index: index, block_number: block_number}) do |
||||
[{block_number, bytes, index}] |
||||
end |
||||
end |
@ -0,0 +1,38 @@ |
||||
defmodule Indexer.Temporary.FailedCreatedAddresses.Supervisor do |
||||
@moduledoc """ |
||||
Supervises `Indexer.Temporary.FailedCreatedAddresses`. |
||||
""" |
||||
|
||||
use Supervisor |
||||
|
||||
alias Indexer.Temporary.FailedCreatedAddresses |
||||
|
||||
def child_spec([init_arguments]) do |
||||
child_spec([init_arguments, []]) |
||||
end |
||||
|
||||
def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do |
||||
default = %{ |
||||
id: __MODULE__, |
||||
start: {__MODULE__, :start_link, start_link_arguments}, |
||||
type: :supervisor |
||||
} |
||||
|
||||
Supervisor.child_spec(default, []) |
||||
end |
||||
|
||||
def start_link(json_rpc_named_arguments, gen_server_options \\ []) do |
||||
Supervisor.start_link(__MODULE__, json_rpc_named_arguments, gen_server_options) |
||||
end |
||||
|
||||
@impl Supervisor |
||||
def init(json_rpc_named_arguments) do |
||||
Supervisor.init( |
||||
[ |
||||
{Task.Supervisor, name: Indexer.Temporary.FailedCreatedAddresses.TaskSupervisor}, |
||||
{FailedCreatedAddresses, [json_rpc_named_arguments, [name: FailedCreatedAddresses]]} |
||||
], |
||||
strategy: :rest_for_one |
||||
) |
||||
end |
||||
end |
@ -0,0 +1,139 @@ |
||||
defmodule Indexer.Temporary.FailedCreatedAddressesTest do |
||||
use Explorer.DataCase, async: false |
||||
use EthereumJSONRPC.Case, async: false |
||||
|
||||
import Mox |
||||
|
||||
import Ecto.Query |
||||
|
||||
alias Explorer.Repo |
||||
alias Explorer.Chain.{Address, Transaction} |
||||
alias Indexer.Temporary.FailedCreatedAddresses.Supervisor |
||||
alias Indexer.CoinBalance |
||||
|
||||
@moduletag capture_log: true |
||||
|
||||
setup :set_mox_global |
||||
|
||||
setup :verify_on_exit! |
||||
|
||||
describe "run/1" do |
||||
@tag :no_parity |
||||
@tag :no_geth |
||||
test "updates failed replaced transactions", %{json_rpc_named_arguments: json_rpc_named_arguments} do |
||||
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
|
||||
block = insert(:block) |
||||
|
||||
transaction = |
||||
:transaction |
||||
|> insert( |
||||
status: 0, |
||||
error: "Reverted", |
||||
internal_transactions_indexed_at: DateTime.utc_now(), |
||||
block: block, |
||||
block_number: block.number, |
||||
cumulative_gas_used: 200, |
||||
gas_used: 100, |
||||
index: 0 |
||||
) |
||||
|
||||
address = insert(:address, contract_code: "0x0102030405") |
||||
|
||||
insert(:internal_transaction, |
||||
block_number: transaction.block_number, |
||||
transaction: transaction, |
||||
index: 0, |
||||
created_contract_address_hash: address.hash |
||||
) |
||||
|
||||
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do |
||||
EthereumJSONRPC.Mox |
||||
|> expect(:json_rpc, fn _json, _options -> |
||||
{:ok, [%{id: 0, jsonrpc: "2.0", result: "0x"}]} |
||||
end) |
||||
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [_address, _block_quantity]}], _options -> |
||||
{:ok, [%{id: id, result: "0x0"}]} |
||||
end) |
||||
|> expect(:json_rpc, fn _json, _options -> |
||||
{:ok, |
||||
[ |
||||
%{ |
||||
id: 0, |
||||
jsonrpc: "2.0", |
||||
result: %{ |
||||
"output" => "0x", |
||||
"stateDiff" => nil, |
||||
"trace" => [ |
||||
%{ |
||||
"action" => %{ |
||||
"callType" => "call", |
||||
"from" => "0xc73add416e2119d20ce80e0904fc1877e33ef246", |
||||
"gas" => "0x13388", |
||||
"input" => "0xc793bf97", |
||||
"to" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340", |
||||
"value" => "0x0" |
||||
}, |
||||
"error" => "Reverted", |
||||
"subtraces" => 1, |
||||
"traceAddress" => [], |
||||
"type" => "call" |
||||
}, |
||||
%{ |
||||
"action" => %{ |
||||
"from" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340", |
||||
"gas" => "0xb2ab", |
||||
"init" => "0x4bb278f3", |
||||
"value" => "0x0" |
||||
}, |
||||
"result" => %{ |
||||
"address" => "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75", |
||||
"code" => |
||||
"0x6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029", |
||||
"gasUsed" => "0xa535" |
||||
}, |
||||
"subtraces" => 0, |
||||
"traceAddress" => [0], |
||||
"type" => "create" |
||||
} |
||||
], |
||||
"vmTrace" => nil |
||||
} |
||||
} |
||||
]} |
||||
end) |
||||
end |
||||
|
||||
params = [json_rpc_named_arguments, [name: TestFailedCreatedAddresses]] |
||||
|
||||
params |
||||
|> Supervisor.child_spec() |
||||
|> ExUnit.Callbacks.start_supervised!() |
||||
|
||||
Process.sleep(3_000) |
||||
|
||||
query = |
||||
from(t in Transaction, |
||||
where: t.hash == ^transaction.hash, |
||||
preload: [internal_transactions: :created_contract_address] |
||||
) |
||||
|
||||
fetched_transaction = Repo.one(query) |
||||
|
||||
assert Enum.count(fetched_transaction.internal_transactions) == 2 |
||||
|
||||
assert Enum.all?(fetched_transaction.internal_transactions, fn it -> |
||||
it.error && is_nil(it.created_contract_address_hash) |
||||
end) |
||||
|
||||
fetched_address = |
||||
Repo.one( |
||||
from(a in Address, |
||||
where: a.hash == ^address.hash |
||||
) |
||||
) |
||||
|
||||
assert fetched_address.contract_code == %Explorer.Chain.Data{bytes: ""} |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue