diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex index 71353c6e30..10e95c1741 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex @@ -19,7 +19,7 @@ defmodule EthereumJSONRPC.Ganache do To signal to the caller that fetching is not supported, `:ignore` is returned. """ @impl EthereumJSONRPC.Variant - def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore + def fetch_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore @doc """ Pending transaction fetching is not supported currently for Ganache. diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 246c4005ad..e799603899 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -3,10 +3,6 @@ defmodule EthereumJSONRPC.Geth do Ethereum JSONRPC methods that are only supported by [Geth](https://github.com/ethereum/go-ethereum/wiki/geth). """ - import EthereumJSONRPC, only: [id_to_params: 1, json_rpc: 2, request: 1] - - alias EthereumJSONRPC.Geth.Calls - @behaviour EthereumJSONRPC.Variant @doc """ @@ -18,19 +14,12 @@ defmodule EthereumJSONRPC.Geth do def fetch_beneficiaries(_block_range, _json_rpc_named_arguments), do: :ignore @doc """ - Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params. + 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. """ @impl EthereumJSONRPC.Variant - def fetch_internal_transactions(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do - id_to_params = id_to_params(transactions_params) - - with {:ok, responses} <- - id_to_params - |> debug_trace_transaction_requests() - |> json_rpc(json_rpc_named_arguments) do - debug_trace_transaction_responses_to_internal_transactions_params(responses, id_to_params) - end - end + def fetch_internal_transactions(_block_range, _json_rpc_named_arguments), do: :ignore @doc """ Pending transaction fetching is not supported currently for Geth. @@ -39,110 +28,4 @@ defmodule EthereumJSONRPC.Geth do """ @impl EthereumJSONRPC.Variant def fetch_pending_transactions(_json_rpc_named_arguments), do: :ignore - - defp debug_trace_transaction_requests(id_to_params) when is_map(id_to_params) do - Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} -> - debug_trace_transaction_request(%{id: id, hash_data: hash_data}) - end) - end - - @tracer_path "priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js" - @external_resource @tracer_path - @tracer File.read!(@tracer_path) - - defp debug_trace_transaction_request(%{id: id, hash_data: hash_data}) do - request(%{id: id, method: "debug_traceTransaction", params: [hash_data, %{tracer: @tracer}]}) - end - - defp debug_trace_transaction_responses_to_internal_transactions_params(responses, id_to_params) - when is_list(responses) and is_map(id_to_params) do - responses - |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1, id_to_params)) - |> reduce_internal_transactions_params() - end - - defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, result: calls}, id_to_params) - when is_map(id_to_params) do - %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = - Map.fetch!(id_to_params, id) - - internal_transaction_params = - calls - |> Stream.with_index() - |> Enum.map(fn {trace, index} -> - Map.merge(trace, %{ - "blockNumber" => block_number, - "index" => index, - "transactionIndex" => transaction_index, - "transactionHash" => transaction_hash - }) - end) - |> Calls.to_internal_transactions_params() - - {:ok, internal_transaction_params} - end - - defp debug_trace_transaction_response_to_internal_transactions_params(%{id: id, error: error}, id_to_params) - when is_map(id_to_params) do - %{ - block_number: block_number, - hash_data: "0x" <> transaction_hash_digits = transaction_hash, - transaction_index: transaction_index - } = Map.fetch!(id_to_params, id) - - not_found_message = "transaction " <> transaction_hash_digits <> " not found" - - normalized_error = - case error do - %{code: -32_000, message: ^not_found_message} -> - %{message: :not_found} - - %{code: -32_000, message: "execution timeout"} -> - %{message: :timeout} - - _ -> - error - end - - annotated_error = - Map.put(normalized_error, :data, %{ - block_number: block_number, - transaction_index: transaction_index, - transaction_hash: transaction_hash - }) - - {:error, annotated_error} - 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) - |> finalize_internal_transactions_params() - end - - defp internal_transactions_params_reducer( - {:ok, internal_transactions_params}, - {:ok, acc_internal_transactions_params_list} - ), - do: {:ok, [internal_transactions_params, acc_internal_transactions_params_list]} - - defp internal_transactions_params_reducer({:ok, _}, {:error, _} = acc_error), do: acc_error - defp internal_transactions_params_reducer({:error, reason}, {:ok, _}), do: {:error, [reason]} - - defp internal_transactions_params_reducer({:error, reason}, {:error, acc_reasons}) when is_list(acc_reasons), - do: {:error, [reason | acc_reasons]} - - defp finalize_internal_transactions_params({:ok, acc_internal_transactions_params_list}) - when is_list(acc_internal_transactions_params_list) do - internal_transactions_params = - acc_internal_transactions_params_list - |> Enum.reverse() - |> List.flatten() - - {:ok, internal_transactions_params} - end - - defp finalize_internal_transactions_params({:error, acc_reasons}) do - {:error, Enum.reverse(acc_reasons)} - end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex index e6a6008fd3..a8d2c9a138 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex @@ -30,14 +30,14 @@ defmodule EthereumJSONRPC.Parity do Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. """ @impl EthereumJSONRPC.Variant - def fetch_internal_transactions(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do - id_to_params = id_to_params(transactions_params) + def fetch_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_transaction_requests() + |> trace_replay_block_transactions_requests() |> json_rpc(json_rpc_named_arguments) do - trace_replay_transaction_responses_to_internal_transactions_params(responses, id_to_params) + trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) end end @@ -68,9 +68,9 @@ defmodule EthereumJSONRPC.Parity do Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) end - defp trace_replay_transaction_responses_to_internal_transactions_params(responses, id_to_params) + 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_transaction_responses_to_traces(responses, id_to_params) do + with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do params = traces |> Traces.to_elixir() @@ -80,10 +80,10 @@ defmodule EthereumJSONRPC.Parity do end end - defp trace_replay_transaction_responses_to_traces(responses, id_to_params) + 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_transaction_response_to_traces(&1, id_to_params)) + |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params)) |> Enum.reduce( {:ok, []}, fn @@ -115,48 +115,48 @@ defmodule EthereumJSONRPC.Parity do end end - defp trace_replay_transaction_response_to_traces(%{id: id, result: %{"trace" => traces}}, id_to_params) - when is_list(traces) and is_map(id_to_params) do - %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = - Map.fetch!(id_to_params, id) + 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 = - traces + results |> Stream.with_index() - |> Enum.map(fn {trace, index} -> - Map.merge(trace, %{ - "blockNumber" => block_number, - "index" => index, - "transactionIndex" => transaction_index, - "transactionHash" => transaction_hash - }) + |> 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_transaction_response_to_traces(%{id: id, error: error}, id_to_params) + defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do - %{block_number: block_number, hash_data: transaction_hash, transaction_index: transaction_index} = - Map.fetch!(id_to_params, id) + block_number = Map.fetch!(id_to_params, id) annotated_error = Map.put(error, :data, %{ - "blockNumber" => block_number, - "transactionIndex" => transaction_index, - "transactionHash" => transaction_hash + "blockNumber" => block_number }) {: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}) + 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_transaction_request(%{id: id, hash_data: hash_data}) do - request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]}) + 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 end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex index afbcfba8e1..61dbf6f443 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex @@ -33,13 +33,13 @@ defmodule EthereumJSONRPC.Variant do ## Returns - * `{:ok, [internal_transaction_params]}` - internal transactions were successfully fetched for all transactions - * `{:error, reason}` - there was one or more errors with `reason` in fetching at least one of the transaction's + * `{:ok, [internal_transaction_params]}` - internal transactions were successfully fetched for all blocks + * `{:error, reason}` - there was one or more errors with `reason` in fetching at least one of the blocks' internal transactions * `:ignore` - the variant does not support fetching internal transactions. """ @callback fetch_internal_transactions( - [%{hash_data: EthereumJSONRPC.hash()}], + [EthereumJSONRPC.block_number()], EthereumJSONRPC.json_rpc_named_arguments() ) :: {:ok, [internal_transaction_params]} | {:error, reason :: term} | :ignore diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 4ae4c2ed62..d7b1ef1f80 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -8,72 +8,9 @@ defmodule EthereumJSONRPC.GethTest do @moduletag :no_parity describe "fetch_internal_transactions/2" do - # Infura Mainnet does not support debug_traceTransaction, so this cannot be tested expect in Mox - setup do - EthereumJSONRPC.Case.Geth.Mox.setup() - end - - setup :verify_on_exit! - - # Data taken from Rinkeby - test "is supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do + test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do block_number = 3_287_375 - transaction_index = 13 - transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" - tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") - - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> - {:ok, - [ - %{ - id: id, - result: [ - %{ - "traceAddress" => [], - "type" => "call", - "callType" => "call", - "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", - "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", - "gas" => "0x8600", - "gasUsed" => "0x7d37", - "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", - "output" => "0x", - "value" => "0x174876e800" - } - ] - } - ]} - end) - - assert {:ok, - [ - %{ - block_number: ^block_number, - transaction_index: ^transaction_index, - transaction_hash: ^transaction_hash, - index: 0, - trace_address: [], - type: "call", - call_type: "call", - from_address_hash: "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", - to_address_hash: "0x1469b17ebf82fedf56f04109e5207bdc4554288c", - gas: 34304, - gas_used: 32055, - input: "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", - output: "0x", - value: 100_000_000_000 - } - ]} = - Geth.fetch_internal_transactions( - [ - %{ - block_number: block_number, - transaction_index: transaction_index, - hash_data: transaction_hash - } - ], - json_rpc_named_arguments - ) + EthereumJSONRPC.Geth.fetch_internal_transactions(block_number, json_rpc_named_arguments) end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs index 5eb12fd550..f8d6fc1688 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs @@ -103,27 +103,18 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do test "transparently splits batch payloads that would trigger a 504 Gateway Timeout", %{ json_rpc_named_arguments: json_rpc_named_arguments } do - transaction_hashes = ~w(0x196c2579f30077e8df0994e185d724331350c2bdb0f5d4e48b9e83f1e149cc28 - 0x19eb948514a971bcd3ab737083bbdb32da233fff2ba70490bb0a36937a418006 - 0x1a1039899fd07a5fd81faf2ec11ca24fc6023d486d4156095688a29b3bf06b7b - 0x1a942061ed6cf0736b194732bb6e1edfcbc50cc004e0cdad79335b3e40e23c9c - 0x1bdec995deaa0e5b53cc7a0b84eaff39da90f5e507fdb4360881ff31f824d918 - 0x1c26758e003b0bc89ac7e3e6e87c6fc76dfb8d878dc530055e6a34f4d557cb1c - 0x1d592be82979bd1cc320eb70d4bb1d61226d78baa9e57e2a12b24345f81ce3bd - 0x1e57e7ce2941c6108e899f786fe339fa50ab053e47fbdcbf5979f475042c6dd8 - 0x1ec1f9c31a0f43f7e684bfa20e422d7d8a343f81c517be1e30f149618ae306f2 - 0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c) + block_numbers = [862_272, 862_273, 862_274, 862_275, 862_276, 862_277, 862_278, 862_279, 862_280, 862_281] if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do EthereumJSONRPC.HTTP.Mox |> expect(:json_rpc, fn _url, _json, _options -> - {:ok, %{body: "504 Gateway Timeout", status_code: 413}} + {:ok, %{body: "504 Gateway Timeout", status_code: 504}} end) |> expect(:json_rpc, fn _url, json, _options -> json_binary = IO.iodata_to_binary(json) - refute json_binary =~ "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c" - assert json_binary =~ "0x1bdec995deaa0e5b53cc7a0b84eaff39da90f5e507fdb4360881ff31f824d918" + refute json_binary =~ "0xD2849" + assert json_binary =~ "0xD2844" body = 0..4 @@ -131,16 +122,19 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do %{ jsonrpc: "2.0", id: id, - result: %{ - "trace" => [ - %{ - "type" => "create", - "action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"}, - "traceAddress" => "0x", - "result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"} - } - ] - } + result: [ + %{ + "trace" => [ + %{ + "type" => "create", + "action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"}, + "traceAddress" => "0x", + "result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"} + } + ], + "transactionHash" => "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c" + } + ] } end) |> Jason.encode!() @@ -150,9 +144,9 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do |> expect(:json_rpc, fn _url, json, _options -> json_binary = IO.iodata_to_binary(json) - refute json_binary =~ "0x1bdec995deaa0e5b53cc7a0b84eaff39da90f5e507fdb4360881ff31f824d918" - assert json_binary =~ "0x1c26758e003b0bc89ac7e3e6e87c6fc76dfb8d878dc530055e6a34f4d557cb1c" - assert json_binary =~ "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c" + refute json_binary =~ "0xD2844" + assert json_binary =~ "0xD2845" + assert json_binary =~ "0xD2849" body = 5..9 @@ -160,16 +154,19 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do %{ jsonrpc: "2.0", id: id, - result: %{ - "trace" => [ - %{ - "type" => "create", - "action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"}, - "traceAddress" => "0x", - "result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"} - } - ] - } + result: [ + %{ + "trace" => [ + %{ + "type" => "create", + "action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"}, + "traceAddress" => "0x", + "result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"} + } + ], + "transactionHash" => "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c" + } + ] } end) |> Jason.encode!() @@ -178,24 +175,18 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do end) end - transactions_params = - Enum.map(transaction_hashes, fn hash_data -> - %{block_number: 0, hash_data: hash_data, gas: 1_000_000, transaction_index: 0} - end) - - assert {:ok, responses} = - EthereumJSONRPC.fetch_internal_transactions(transactions_params, json_rpc_named_arguments) + assert {:ok, responses} = EthereumJSONRPC.fetch_internal_transactions(block_numbers, json_rpc_named_arguments) - assert Enum.count(responses) == Enum.count(transactions_params) + assert Enum.count(responses) == Enum.count(block_numbers) - transaction_hash_set = MapSet.new(transaction_hashes) + block_number_set = MapSet.new(block_numbers) - response_transaction_hash_set = - Enum.into(responses, MapSet.new(), fn %{transaction_hash: transaction_hash} -> - transaction_hash + response_block_number_set = + Enum.into(responses, MapSet.new(), fn %{block_number: block_number} -> + block_number end) - assert MapSet.equal?(response_transaction_hash_set, transaction_hash_set) + assert MapSet.equal?(response_block_number_set, block_number_set) end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs index 76e8195241..0c2dcff109 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs @@ -14,7 +14,7 @@ defmodule EthereumJSONRPC.ParityTest do @moduletag :no_geth describe "fetch_internal_transactions/1" do - test "with all valid transaction_params returns {:ok, transactions_params}", %{ + test "with all valid block_numbers returns {:ok, transactions_params}", %{ json_rpc_named_arguments: json_rpc_named_arguments } do from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" @@ -24,7 +24,7 @@ defmodule EthereumJSONRPC.ParityTest do "0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef" value = 0 - block_number = 1 + block_number = 39 index = 0 created_contract_address_hash = "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461" @@ -43,48 +43,43 @@ defmodule EthereumJSONRPC.ParityTest do [ %{ id: 0, - result: %{ - "trace" => [ - %{ - "action" => %{ - "from" => from_address_hash, - "gas" => integer_to_quantity(gas), - "init" => init, - "value" => integer_to_quantity(value) - }, - "blockNumber" => block_number, - "index" => index, - "result" => %{ - "address" => created_contract_address_hash, - "code" => created_contract_code, - "gasUsed" => integer_to_quantity(gas_used) - }, - "traceAddress" => trace_address, - "transactionHash" => transaction_hash, - "transactionIndex" => transaction_index, - "type" => type - } - ] - } + result: [ + %{ + "trace" => [ + %{ + "action" => %{ + "from" => from_address_hash, + "gas" => integer_to_quantity(gas), + "init" => init, + "value" => integer_to_quantity(value) + }, + "blockNumber" => block_number, + "index" => index, + "result" => %{ + "address" => created_contract_address_hash, + "code" => created_contract_code, + "gasUsed" => integer_to_quantity(gas_used) + }, + "traceAddress" => trace_address, + "type" => type + } + ], + "transactionHash" => transaction_hash + } + ] } ]} end) end assert EthereumJSONRPC.Parity.fetch_internal_transactions( - [ - %{ - block_number: block_number, - hash_data: transaction_hash, - transaction_index: transaction_index - } - ], + [block_number], json_rpc_named_arguments ) == { :ok, [ %{ - block_number: 1, + block_number: block_number, created_contract_address_hash: created_contract_address_hash, created_contract_code: created_contract_code, from_address_hash: from_address_hash, @@ -101,140 +96,6 @@ defmodule EthereumJSONRPC.ParityTest do ] } end - - test "with all invalid transaction_params returns {:error, reasons}", %{ - json_rpc_named_arguments: json_rpc_named_arguments - } do - if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do - expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> - {:ok, - [ - %{ - id: 0, - error: %{ - code: -32603, - message: "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." - } - } - ]} - end) - end - - assert EthereumJSONRPC.Parity.fetch_internal_transactions( - [ - %{ - block_number: 1, - hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001", - transaction_index: 0 - } - ], - json_rpc_named_arguments - ) == - {:error, - [ - %{ - code: -32603, - data: %{ - "blockNumber" => 1, - "transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001", - "transactionIndex" => 0 - }, - message: - "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." - } - ]} - end - - test "with a mix of valid and invalid transaction_params returns {:error, reasons}", %{ - json_rpc_named_arguments: json_rpc_named_arguments - } do - if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do - expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> - {:ok, - [ - %{ - id: 0, - result: %{ - "trace" => [] - } - }, - %{ - id: 1, - result: %{ - "trace" => [] - } - }, - %{ - id: 2, - error: %{ - code: -32603, - message: "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." - } - }, - %{ - id: 3, - error: %{ - code: -32603, - message: "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." - } - } - ]} - end) - end - - assert EthereumJSONRPC.Parity.fetch_internal_transactions( - [ - # start with :ok - %{ - block_number: 1, - hash_data: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1", - transaction_index: 0 - }, - # :ok, :ok clause - %{ - block_number: 34, - hash_data: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", - transaction_index: 0 - }, - # :ok, :error clause - %{ - block_number: 1, - hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001", - transaction_index: 0 - }, - # :error, :error clause - %{ - block_number: 2, - hash_data: "0x0000000000000000000000000000000000000000000000000000000000000002", - transaction_index: 0 - } - ], - json_rpc_named_arguments - ) == - {:error, - [ - %{ - code: -32603, - data: %{ - "blockNumber" => 1, - "transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001", - "transactionIndex" => 0 - }, - message: - "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." - }, - %{ - code: -32603, - data: %{ - "blockNumber" => 2, - "transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000002", - "transactionIndex" => 0 - }, - message: - "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." - } - ]} - end end describe "fetch_beneficiaries/1" do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index b40af9a343..e241539382 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1143,62 +1143,55 @@ defmodule Explorer.Chain do end @doc """ - Returns a stream of all collated transactions with unfetched internal transactions. + Returns a stream of all blocks with unfetched internal transactions. - Only transactions that have been collated into a block are returned; pending transactions not in a block are filtered - out. + Only blocks with consensus are returned. - iex> pending = insert(:transaction) - iex> unfetched_collated = - ...> :transaction |> - ...> insert() |> - ...> with_block() - iex> fetched_collated = - ...> :transaction |> - ...> insert() |> - ...> with_block(internal_transactions_indexed_at: DateTime.utc_now()) - iex> {:ok, hash_set} = Explorer.Chain.stream_transactions_with_unfetched_internal_transactions( - ...> [:hash], + iex> non_consensus = insert(:block, consensus: false) + iex> unfetched = insert(:block) + iex> fetched = insert(:block, internal_transactions_indexed_at: DateTime.utc_now()) + iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions( + ...> [:number], ...> MapSet.new(), - ...> fn %Explorer.Chain.Transaction{hash: hash}, acc -> - ...> MapSet.put(acc, hash) + ...> fn %Explorer.Chain.Block{number: number}, acc -> + ...> MapSet.put(acc, number) ...> end ...> ) - iex> pending.hash in hash_set + iex> non_consensus.number in number_set false - iex> unfetched_collated.hash in hash_set + iex> unfetched.number in number_set true - iex> fetched_collated.hash in hash_set + iex> fetched.hash in number_set false """ - @spec stream_transactions_with_unfetched_internal_transactions( + @spec stream_blocks_with_unfetched_internal_transactions( fields :: [ - :block_hash - | :internal_transactions_indexed_at - | :from_address_hash - | :gas - | :gas_price + :consensus + | :difficulty + | :gas_limit + | :gas_used | :hash - | :index - | :input + | :miner + | :miner_hash | :nonce - | :r - | :s - | :to_address_hash - | :v - | :value + | :number + | :parent_hash + | :size + | :timestamp + | :total_difficulty + | :transactions + | :internal_transactions_indexed_at ], initial :: accumulator, reducer :: (entry :: term(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_transactions_with_unfetched_internal_transactions(fields, initial, reducer) when is_function(reducer, 2) do + def stream_blocks_with_unfetched_internal_transactions(fields, initial, reducer) when is_function(reducer, 2) do query = from( - t in Transaction, - # exclude pending transactions and replaced transactions - where: not is_nil(t.block_hash) and is_nil(t.internal_transactions_indexed_at), + b in Block, + where: b.consensus and is_nil(b.internal_transactions_indexed_at), select: ^fields ) diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 6e76f4801c..c716dd27ff 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -10,6 +10,8 @@ defmodule Explorer.Chain.Block do alias Explorer.Chain.{Address, Gas, Hash, Transaction} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} + @optional_attrs ~w(internal_transactions_indexed_at)a + @required_attrs ~w(consensus difficulty gas_limit gas_used hash miner_hash nonce number parent_hash size timestamp total_difficulty)a @@ -45,6 +47,7 @@ defmodule Explorer.Chain.Block do * `timestamp` - When the block was collated * `total_difficulty` - the total `difficulty` of the chain until this block. * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. + * `internal_transactions_indexed_at` - when `internal_transactions` were fetched by `Indexer`. """ @type t :: %__MODULE__{ consensus: boolean(), @@ -60,7 +63,8 @@ defmodule Explorer.Chain.Block do size: non_neg_integer(), timestamp: DateTime.t(), total_difficulty: difficulty(), - transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()] + transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], + internal_transactions_indexed_at: DateTime.t() } @primary_key {:hash, Hash.Full, autogenerate: false} @@ -74,6 +78,7 @@ defmodule Explorer.Chain.Block do field(:size, :integer) field(:timestamp, :utc_datetime_usec) field(:total_difficulty, :decimal) + field(:internal_transactions_indexed_at, :utc_datetime_usec) timestamps() @@ -95,7 +100,7 @@ defmodule Explorer.Chain.Block do def changeset(%__MODULE__{} = block, attrs) do block - |> cast(attrs, @required_attrs) + |> cast(attrs, @required_attrs ++ @optional_attrs) |> validate_required(@required_attrs) |> foreign_key_constraint(:parent_hash) |> unique_constraint(:hash, name: :blocks_pkey) diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 228b06eee3..e9b05bc4c0 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -249,6 +249,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do difficulty: fragment("EXCLUDED.difficulty"), gas_limit: fragment("EXCLUDED.gas_limit"), gas_used: fragment("EXCLUDED.gas_used"), + internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"), miner_hash: fragment("EXCLUDED.miner_hash"), nonce: fragment("EXCLUDED.nonce"), number: fragment("EXCLUDED.number"), @@ -267,7 +268,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do fragment("EXCLUDED.miner_hash <> ?", block.miner_hash) or fragment("EXCLUDED.nonce <> ?", block.nonce) or fragment("EXCLUDED.number <> ?", block.number) or fragment("EXCLUDED.parent_hash <> ?", block.parent_hash) or fragment("EXCLUDED.size <> ?", block.size) or fragment("EXCLUDED.timestamp <> ?", block.timestamp) or - fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty) + fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty) or + fragment("EXCLUDED.internal_transactions_indexed_at <> ?", block.internal_transactions_indexed_at) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 45bd9e3f43..7fbb844749 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do require Ecto.Query alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.{Hash, Import, InternalTransaction, Transaction} + alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, Transaction} alias Explorer.Chain.Import.Runner import Ecto.Query, only: [from: 2] @@ -54,6 +54,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do |> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, _ -> update_transactions(repo, changes_list, update_transactions_options) end) + |> Multi.run(:internal_transactions_indexed_at_blocks, fn repo, _ -> + update_blocks(repo, changes_list, update_transactions_options) + end) end @impl Runner @@ -192,4 +195,37 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:error, %{exception: postgrex_error, transaction_hashes: ordered_transaction_hashes}} end end + + defp update_blocks(repo, internal_transactions, %{ + timeout: timeout, + timestamps: timestamps + }) + when is_list(internal_transactions) do + ordered_block_numbers = + internal_transactions + |> MapSet.new(& &1.block_number) + |> Enum.sort() + + query = + from( + b in Block, + where: b.number in ^ordered_block_numbers and b.consensus, + update: [ + set: [ + internal_transactions_indexed_at: ^timestamps.updated_at + ] + ] + ) + + block_count = Enum.count(ordered_block_numbers) + + try do + {^block_count, result} = repo.update_all(query, [], timeout: timeout) + + {:ok, result} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error, block_numbers: ordered_block_numbers}} + end + end end diff --git a/apps/explorer/priv/repo/migrations/20190228220746_add_internal_transactions_indexed_at_to_blocks.exs b/apps/explorer/priv/repo/migrations/20190228220746_add_internal_transactions_indexed_at_to_blocks.exs new file mode 100644 index 0000000000..2e913f4d98 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190228220746_add_internal_transactions_indexed_at_to_blocks.exs @@ -0,0 +1,10 @@ +defmodule Explorer.Repo.Migrations.AddInternalTransactionsIndexedAtToBlocks do + use Ecto.Migration + + def change do + alter table(:blocks) do + # `null` when `internal_transactions` has never been fetched + add(:internal_transactions_indexed_at, :utc_datetime_usec, null: true) + end + end +end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 982f84c3c2..edb20be7ef 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -49,7 +49,7 @@ defmodule Explorer.Chain.ImportTest do internal_transactions: %{ params: [ %{ - block_number: 35, + block_number: 37, transaction_index: 0, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", index: 0, @@ -65,7 +65,7 @@ defmodule Explorer.Chain.ImportTest do value: 0 }, %{ - block_number: 35, + block_number: 37, transaction_index: 1, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", index: 1, @@ -519,7 +519,7 @@ defmodule Explorer.Chain.ImportTest do from_address_hash = "0x8cc2e4b51b4340cb3727cffe3f1878756e732cee" from_address = insert(:address, hash: from_address_hash) - block = insert(:block) + block = insert(:block, number: 37) transaction_string_hash = "0x0705ea0a5b997d9aafd5c531e016d9aabe3297a28c0bd4ef005fe6ea329d301b" @@ -551,7 +551,7 @@ defmodule Explorer.Chain.ImportTest do transaction_hash: transaction_string_hash, type: "create", value: 0, - block_number: 35, + block_number: 37, transaction_index: 0 } ] @@ -569,7 +569,7 @@ defmodule Explorer.Chain.ImportTest do address_hash = "0x1c494fa496f1cfd918b5ff190835af3aaf609899" from_address = insert(:address, hash: address_hash) - block = insert(:block, consensus: true) + block = insert(:block, consensus: true, number: 37) transaction = :transaction @@ -578,6 +578,7 @@ defmodule Explorer.Chain.ImportTest do internal_transacton = insert(:internal_transaction, + block_number: 37, transaction_hash: transaction.hash, error: "Bad Instruction", index: 0, @@ -677,7 +678,7 @@ defmodule Explorer.Chain.ImportTest do internal_transactions: %{ params: [ %{ - block_number: 35, + block_number: block_number, transaction_index: 0, transaction_hash: transaction_hash, index: 0, @@ -768,7 +769,7 @@ defmodule Explorer.Chain.ImportTest do internal_transactions: %{ params: [ %{ - block_number: 35, + block_number: block_number, call_type: "call", created_contract_code: smart_contract_bytecode, created_contract_address_hash: created_contract_address_hash, @@ -874,7 +875,7 @@ defmodule Explorer.Chain.ImportTest do transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", type: "create", value: 0, - block_number: 35, + block_number: 37, transaction_index: 0 }, %{ @@ -891,7 +892,7 @@ defmodule Explorer.Chain.ImportTest do transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", type: "create", value: 0, - block_number: 35, + block_number: 37, transaction_index: 1 } ], @@ -1556,7 +1557,7 @@ defmodule Explorer.Chain.ImportTest do index: 0, from_address_hash: from_address_hash, to_address_hash: to_address_hash, - block_number: 35, + block_number: block_number, transaction_index: 0 ) ], @@ -1824,7 +1825,7 @@ defmodule Explorer.Chain.ImportTest do value: 0, input: "0x", error: error, - block_number: 35, + block_number: block_number, transaction_index: 0 } ] diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 77fcb4b2e8..ebff844f5c 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -996,6 +996,7 @@ defmodule Explorer.ChainTest do internal_transactions: %{ params: [ %{ + block_number: 37, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", index: 0, trace_address: [], diff --git a/apps/indexer/config/dev/parity.exs b/apps/indexer/config/dev/parity.exs index b63f1d8ccf..bdda087359 100644 --- a/apps/indexer/config/dev/parity.exs +++ b/apps/indexer/config/dev/parity.exs @@ -10,9 +10,9 @@ config :indexer, 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_replayTransaction: 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(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Parity ], diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index c85652d4ff..04123cd596 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -180,28 +180,21 @@ defmodule Indexer.Block.Catchup.Fetcher do defp async_import_created_contract_codes(_), do: :ok - defp async_import_internal_transactions(%{transactions: transactions}, json_rpc_named_arguments) do - transaction_data = - Enum.flat_map(transactions, fn - %Transaction{block_number: block_number, index: index, hash: hash, internal_transactions_indexed_at: nil} -> - [%{block_number: block_number, index: index, hash: hash}] - - %Transaction{internal_transactions_indexed_at: %DateTime{}} -> - [] - end) + defp async_import_internal_transactions(%{blocks: blocks}, json_rpc_named_arguments) do + block_data = Enum.map(blocks, fn %Chain.Block{number: block_number} -> %{number: block_number} end) - filtered_transaction_data = + filtered_block_data = if Keyword.get(json_rpc_named_arguments, :variant) == EthereumJSONRPC.Geth do {_, max_block_number} = Chain.fetch_min_and_max_block_numbers() - Enum.filter(transaction_data, fn %{block_number: block_number} -> + Enum.filter(block_data, fn %{number: block_number} -> max_block_number - block_number < @geth_block_limit end) else - transaction_data + block_data end - InternalTransaction.Fetcher.async_fetch(filtered_transaction_data, 10_000) + InternalTransaction.Fetcher.async_fetch(filtered_block_data, 10_000) end defp async_import_internal_transactions(_, _), do: :ok diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index dcf4ecb4bd..899bb3f529 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -20,11 +20,9 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_replaced_transactions: 1 ] - alias ABI.TypeDecoder alias Ecto.Changeset alias EthereumJSONRPC.{FetchedBalances, Subscription} alias Explorer.Chain - alias Explorer.Chain.TokenTransfer alias Explorer.Counters.AverageBlockTime alias Indexer.{AddressExtraction, Block, TokenBalances, Tracer} alias Indexer.Block.Realtime.{ConsensusEnsurer, TaskSupervisor} @@ -166,8 +164,7 @@ defmodule Indexer.Block.Realtime.Fetcher do address_token_balances: %{params: address_token_balances_params}, addresses: %{params: addresses_params}, block_rewards: block_rewards, - transactions: %{params: transactions_params}, - token_transfers: %{params: token_transfers_params} + blocks: %{params: blocks_params} } = options ) do with {:internal_transactions, @@ -179,8 +176,7 @@ defmodule Indexer.Block.Realtime.Fetcher do {:internal_transactions, internal_transactions(block_fetcher, %{ addresses_params: addresses_params, - token_transfers_params: token_transfers_params, - transactions_params: transactions_params + blocks_params: blocks_params })}, {:balances, {:ok, %{addresses_params: balances_addresses_params, balances_params: balances_params}}} <- {:balances, @@ -363,12 +359,11 @@ defmodule Indexer.Block.Realtime.Fetcher do %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}, %{ addresses_params: addresses_params, - token_transfers_params: token_transfers_params, - transactions_params: transactions_params + blocks_params: blocks_params } ) do - case transactions_params - |> transactions_params_to_fetch_internal_transactions_params(token_transfers_params, json_rpc_named_arguments) + case blocks_params + |> blocks_params_to_fetch_internal_transactions_params() |> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) do {:ok, internal_transactions_params} -> merged_addresses_params = @@ -387,83 +382,10 @@ defmodule Indexer.Block.Realtime.Fetcher do end end - defp transactions_params_to_fetch_internal_transactions_params( - transactions_params, - token_transfers_params, - json_rpc_named_arguments - ) do - token_transfer_transaction_hash_set = MapSet.new(token_transfers_params, & &1.transaction_hash) - - Enum.flat_map( - transactions_params, - &transaction_params_to_fetch_internal_transaction_params_list( - &1, - token_transfer_transaction_hash_set, - json_rpc_named_arguments - ) - ) - end - - defp transaction_params_to_fetch_internal_transaction_params_list( - %{block_number: block_number, transaction_index: transaction_index, hash: hash} = transaction_params, - token_transfer_transaction_hash_set, - json_rpc_named_arguments - ) - when is_integer(block_number) and is_integer(transaction_index) and is_binary(hash) do - token_transfer? = hash in token_transfer_transaction_hash_set - - if fetch_internal_transactions?(transaction_params, token_transfer?, json_rpc_named_arguments) do - [%{block_number: block_number, transaction_index: transaction_index, hash_data: hash}] - else - [] - end - end - - # 0xa9059cbb - signature of the transfer(address,uint256) function from the ERC-20 token specification. - # Although transaction input data can be faked we use this heuristics to filter simple token transfer internal transactions from indexing because they slow down realtime fetcher - defp fetch_internal_transactions?( - %{ - status: :ok, - created_contract_address_hash: nil, - input: unquote(TokenTransfer.transfer_function_signature()) <> params, - value: 0 - }, - _, - _ - ) do - types = [:address, {:uint, 256}] - - try do - [_address, _value] = - params - |> Base.decode16!(case: :mixed) - |> TypeDecoder.decode_raw(types) - - false - rescue - _ -> true - end + defp blocks_params_to_fetch_internal_transactions_params(blocks_params) do + Enum.map(blocks_params, fn %{number: block_number} -> block_number end) end - defp fetch_internal_transactions?( - %{ - status: :ok, - created_contract_address_hash: nil, - input: "0x", - to_address_hash: to_address_hash, - block_number: block_number - }, - _, - json_rpc_named_arguments - ) do - Chain.contract_address?(to_address_hash, block_number, json_rpc_named_arguments) - end - - # Token transfers not transferred during contract creation don't need internal transactions as the token transfers - # derive completely from the logs. - defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil}, true, _), do: false - defp fetch_internal_transactions?(_, _, _), do: true - defp balances( %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}, %{addresses_params: addresses_params} = options diff --git a/apps/indexer/lib/indexer/internal_transaction/fetcher.ex b/apps/indexer/lib/indexer/internal_transaction/fetcher.ex index d103e054c8..512b9d2b93 100644 --- a/apps/indexer/lib/indexer/internal_transaction/fetcher.ex +++ b/apps/indexer/lib/indexer/internal_transaction/fetcher.ex @@ -12,12 +12,12 @@ defmodule Indexer.InternalTransaction.Fetcher do import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2] alias Explorer.Chain - alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.Block alias Indexer.{AddressExtraction, BufferedTask, Tracer} @behaviour BufferedTask - @max_batch_size 10 + @max_batch_size 5 @max_concurrency 4 @defaults [ flush_interval: :timer.seconds(3), @@ -43,7 +43,7 @@ defmodule Indexer.InternalTransaction.Fetcher do *Note*: The internal transactions for individual transactions cannot be paginated, so the total number of internal transactions that could be produced is unknown. """ - @spec async_fetch([%{required(:block_number) => Block.block_number(), required(:hash) => Hash.Full.t()}]) :: :ok + @spec async_fetch([%{required(:block_number) => Block.block_number()}]) :: :ok def async_fetch(transactions_fields, timeout \\ 5000) when is_list(transactions_fields) do entries = Enum.map(transactions_fields, &entry/1) @@ -71,11 +71,11 @@ defmodule Indexer.InternalTransaction.Fetcher do @impl BufferedTask def init(initial, reducer, _) do {:ok, final} = - Chain.stream_transactions_with_unfetched_internal_transactions( - [:block_number, :hash, :index], + Chain.stream_blocks_with_unfetched_internal_transactions( + [:number], initial, - fn transaction_fields, acc -> - transaction_fields + fn block_fields, acc -> + block_fields |> entry() |> reducer.(acc) end @@ -84,13 +84,12 @@ defmodule Indexer.InternalTransaction.Fetcher do final end - defp entry(%{block_number: block_number, hash: %Hash{bytes: bytes}, index: index}) when is_integer(block_number) do - {block_number, bytes, index} + defp entry(%{number: block_number}) when is_integer(block_number) do + block_number end - defp params({block_number, hash_bytes, index}) when is_integer(block_number) do - {:ok, hash} = Hash.Full.cast(hash_bytes) - %{block_number: block_number, hash_data: to_string(hash), transaction_index: index} + defp params(block_number) when is_integer(block_number) do + block_number end @impl BufferedTask @@ -101,7 +100,7 @@ defmodule Indexer.InternalTransaction.Fetcher do tracer: Tracer ) def run(entries, json_rpc_named_arguments) do - unique_entries = unique_entries(entries) + unique_entries = Enum.uniq(entries) unique_entries_count = Enum.count(unique_entries) Logger.metadata(count: unique_entries_count) @@ -164,45 +163,6 @@ defmodule Indexer.InternalTransaction.Fetcher do end end - # Protection and improved reporting for https://github.com/poanetwork/blockscout/issues/289 - defp unique_entries(entries) do - entries_by_hash_bytes = Enum.group_by(entries, &elem(&1, 1)) - - if map_size(entries_by_hash_bytes) < length(entries) do - {unique_entries, duplicate_entries} = - entries_by_hash_bytes - |> Map.values() - |> uniques_and_duplicates() - - Logger.error(fn -> - duplicate_entries - |> Stream.with_index() - |> Enum.reduce( - ["Duplicate entries being used to fetch internal transactions:\n"], - fn {entry, index}, acc -> - [acc, " ", to_string(index + 1), ". ", inspect(entry), "\n"] - end - ) - end) - - unique_entries - else - entries - end - end - - defp uniques_and_duplicates(groups) do - Enum.reduce(groups, {[], []}, fn group, {acc_uniques, acc_duplicates} -> - case group do - [unique] -> - {[unique | acc_uniques], acc_duplicates} - - [unique | _] = duplicates -> - {[unique | acc_uniques], duplicates ++ acc_duplicates} - end - end) - end - defp remove_failed_creations(internal_transactions_params) do internal_transactions_params |> Enum.map(fn internal_transaction_params -> diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 8818df10c1..e691b4420b 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -114,16 +114,18 @@ defmodule Indexer.Block.FetcherTest do |> expect(:json_rpc, fn [%{id: id, method: "trace_block", params: [^block_quantity]}], _options -> {:ok, [%{id: id, result: []}]} end) - |> expect(:json_rpc, fn [ - %{ - id: id, - jsonrpc: "2.0", - method: "eth_getBalance", - params: [^miner_hash, ^block_quantity] - } - ], - _options -> - {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + # async requests need to be grouped in one expect because the order is non-deterministic while multiple expect + # calls on the same name/arity are used in order + |> expect(:json_rpc, 2, fn json, _options -> + [request] = json + + case request do + %{id: id, method: "eth_getBalance", params: [^miner_hash, ^block_quantity]} -> + {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} + + %{id: id, method: "trace_replayBlockTransactions", params: [^block_quantity, ["trace"]]} -> + {:ok, [%{id: id, result: []}]} + end end) EthereumJSONRPC.Geth -> @@ -379,33 +381,37 @@ defmodule Indexer.Block.FetcherTest do %{id: id, method: "eth_getBalance", params: [^from_address_hash, ^block_quantity]} -> {:ok, [%{id: id, jsonrpc: "2.0", result: "0xd0d4a965ab52d8cd740000"}]} - %{id: id, method: "trace_replayTransaction", params: [^transaction_hash, ["trace"]]} -> + %{id: id, method: "trace_replayBlockTransactions", params: [^block_quantity, ["trace"]]} -> {:ok, [ %{ id: id, jsonrpc: "2.0", - result: %{ - "output" => "0x", - "stateDiff" => nil, - "trace" => [ - %{ - "action" => %{ - "callType" => "call", - "from" => from_address_hash, - "gas" => "0x475ec8", - "input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", - "to" => to_address_hash, - "value" => "0x0" - }, - "result" => %{"gasUsed" => "0x6c7a", "output" => "0x"}, - "subtraces" => 0, - "traceAddress" => [], - "type" => "call" - } - ], - "vmTrace" => nil - } + result: [ + %{ + "output" => "0x", + "stateDiff" => nil, + "trace" => [ + %{ + "action" => %{ + "callType" => "call", + "from" => from_address_hash, + "gas" => "0x475ec8", + "input" => + "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + "to" => to_address_hash, + "value" => "0x0" + }, + "result" => %{"gasUsed" => "0x6c7a", "output" => "0x"}, + "subtraces" => 0, + "traceAddress" => [], + "type" => "call" + } + ], + "transactionHash" => transaction_hash, + "vmTrace" => nil + } + ] } ]} end diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs index d005c363d9..82b72d7304 100644 --- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs @@ -24,7 +24,7 @@ defmodule Indexer.Block.Realtime.FetcherTest do |> put_in( [:transport_options, :method_to_url], eth_getBalance: "http://54.144.107.14:8545", - trace_replayTransaction: "http://54.144.107.14:8545", + trace_replayBlockTransactions: "http://54.144.107.14:8545", trace_block: "http://54.144.107.14:8545" ) @@ -204,170 +204,184 @@ defmodule Indexer.Block.Realtime.FetcherTest do responses = Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end) {:ok, responses} end) - |> expect(:json_rpc, fn [ - %{ - id: 0, - jsonrpc: "2.0", - method: "trace_replayTransaction", - params: [ - "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8", - ["trace"] - ] - } - ], - _ -> - {:ok, - [ - %{ - id: 0, - jsonrpc: "2.0", - result: %{ - "output" => "0x", - "stateDiff" => nil, - "trace" => [ - %{ - "action" => %{ - "callType" => "call", - "from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", - "gas" => "0x383ad", - "input" => - "0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", - "to" => "0x698bf6943bab687b2756394624aa183f434f65da", - "value" => "0x1158e4f216242a000" - }, - "result" => %{"gasUsed" => "0x23256", "output" => "0x"}, - "subtraces" => 5, - "traceAddress" => [], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x36771", - "input" => "0x6352211e000000000000000000000000000000000000000000000000000000000000006c", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0x495", - "output" => "0x00000000000000000000000040b18103537c0f15d5e137dd8ddd019b84949d16" - }, - "subtraces" => 0, - "traceAddress" => [0], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x35acb", - "input" => "0x33f30a43000000000000000000000000000000000000000000000000000000000000006c", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0x52d2", - "output" => - "0x00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004f000000000000000000000000000000000000000000000000000000000000004d000000000000000000000000000000000000000000000000000000000000004b000000000000000000000000000000000000000000000000000000000000004f00000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000005b61df09000000000000000000000000000000000000000000000000000000005b61df5e000000000000000000000000000000000000000000000000000000005b61df8b000000000000000000000000000000000000000000000000000000005b61df2c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000fd000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054c65696c61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002566303430313037303331343330303332333036303933333235303131323036303730373131000000000000000000000000000000000000000000000000000000" - }, - "subtraces" => 0, - "traceAddress" => [1], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x2fc79", - "input" => "0x1b8ef0bb000000000000000000000000000000000000000000000000000000000000006c", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0x10f2", - "output" => "0x0000000000000000000000000000000000000000000000000000000000000013" - }, - "subtraces" => 0, - "traceAddress" => [2], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x2e21f", - "input" => - "0xcf5f87d0000000000000000000000000000000000000000000000000000000000000006c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a", - "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", - "value" => "0x0" - }, - "result" => %{"gasUsed" => "0x1ca1", "output" => "0x"}, - "subtraces" => 0, - "traceAddress" => [3], - "type" => "call" - }, + |> expect(:json_rpc, 2, fn + [ + %{ + id: 0, + jsonrpc: "2.0", + method: "trace_replayBlockTransactions", + params: [ + "0x3C3660", + ["trace"] + ] + }, + %{ + id: 1, + jsonrpc: "2.0", + method: "trace_replayBlockTransactions", + params: [ + "0x3C365F", + ["trace"] + ] + } + ], + _ -> + {:ok, + [ + %{id: 0, jsonrpc: "2.0", result: []}, + %{ + id: 1, + jsonrpc: "2.0", + result: [ %{ - "action" => %{ - "callType" => "call", - "from" => "0x698bf6943bab687b2756394624aa183f434f65da", - "gas" => "0x8fc", - "input" => "0x", - "to" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", - "value" => "0x9184e72a000" - }, - "result" => %{"gasUsed" => "0x0", "output" => "0x"}, - "subtraces" => 0, - "traceAddress" => [4], - "type" => "call" + "output" => "0x", + "stateDiff" => nil, + "trace" => [ + %{ + "action" => %{ + "callType" => "call", + "from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", + "gas" => "0x383ad", + "input" => + "0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", + "to" => "0x698bf6943bab687b2756394624aa183f434f65da", + "value" => "0x1158e4f216242a000" + }, + "result" => %{"gasUsed" => "0x23256", "output" => "0x"}, + "subtraces" => 5, + "traceAddress" => [], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x36771", + "input" => "0x6352211e000000000000000000000000000000000000000000000000000000000000006c", + "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", + "value" => "0x0" + }, + "result" => %{ + "gasUsed" => "0x495", + "output" => "0x00000000000000000000000040b18103537c0f15d5e137dd8ddd019b84949d16" + }, + "subtraces" => 0, + "traceAddress" => [0], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x35acb", + "input" => "0x33f30a43000000000000000000000000000000000000000000000000000000000000006c", + "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", + "value" => "0x0" + }, + "result" => %{ + "gasUsed" => "0x52d2", + "output" => + "0x00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004f000000000000000000000000000000000000000000000000000000000000004d000000000000000000000000000000000000000000000000000000000000004b000000000000000000000000000000000000000000000000000000000000004f00000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000005b61df09000000000000000000000000000000000000000000000000000000005b61df5e000000000000000000000000000000000000000000000000000000005b61df8b000000000000000000000000000000000000000000000000000000005b61df2c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000fd000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054c65696c61000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002566303430313037303331343330303332333036303933333235303131323036303730373131000000000000000000000000000000000000000000000000000000" + }, + "subtraces" => 0, + "traceAddress" => [1], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x2fc79", + "input" => "0x1b8ef0bb000000000000000000000000000000000000000000000000000000000000006c", + "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", + "value" => "0x0" + }, + "result" => %{ + "gasUsed" => "0x10f2", + "output" => "0x0000000000000000000000000000000000000000000000000000000000000013" + }, + "subtraces" => 0, + "traceAddress" => [2], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x2e21f", + "input" => + "0xcf5f87d0000000000000000000000000000000000000000000000000000000000000006c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a", + "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", + "value" => "0x0" + }, + "result" => %{"gasUsed" => "0x1ca1", "output" => "0x"}, + "subtraces" => 0, + "traceAddress" => [3], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x8fc", + "input" => "0x", + "to" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", + "value" => "0x9184e72a000" + }, + "result" => %{"gasUsed" => "0x0", "output" => "0x"}, + "subtraces" => 0, + "traceAddress" => [4], + "type" => "call" + } + ], + "transactionHash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8", + "vmTrace" => nil } - ], - "vmTrace" => nil + ] } - } - ]} - end) - |> expect(:json_rpc, fn [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", "0x3C365F"] - }, - %{ - id: 1, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"] - }, - %{ - id: 2, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] - }, - %{ - id: 3, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"] - }, - %{ - id: 4, - jsonrpc: "2.0", - method: "eth_getBalance", - params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"] - } - ], - _ -> - {:ok, - [ - %{id: 0, jsonrpc: "2.0", result: "0x49e3de5187cf037d127"}, - %{id: 1, jsonrpc: "2.0", result: "0x148adc763b603291685"}, - %{id: 2, jsonrpc: "2.0", result: "0x53474fa377a46000"}, - %{id: 3, jsonrpc: "2.0", result: "0x53507afe51f28000"}, - %{id: 4, jsonrpc: "2.0", result: "0x3e1a95d7517dc197108"} - ]} + ]} + + [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", "0x3C365F"] + }, + %{ + id: 1, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x40b18103537c0f15d5e137dd8ddd019b84949d16", "0x3C365F"] + }, + %{ + id: 2, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "0x3C365F"] + }, + %{ + id: 3, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "0x3C3660"] + }, + %{ + id: 4, + jsonrpc: "2.0", + method: "eth_getBalance", + params: ["0x698bf6943bab687b2756394624aa183f434f65da", "0x3C365F"] + } + ], + _ -> + {:ok, + [ + %{id: 0, jsonrpc: "2.0", result: "0x49e3de5187cf037d127"}, + %{id: 1, jsonrpc: "2.0", result: "0x148adc763b603291685"}, + %{id: 2, jsonrpc: "2.0", result: "0x53474fa377a46000"}, + %{id: 3, jsonrpc: "2.0", result: "0x53507afe51f28000"}, + %{id: 4, jsonrpc: "2.0", result: "0x3e1a95d7517dc197108"} + ]} end) end diff --git a/apps/indexer/test/indexer/internal_transaction/fetcher_test.exs b/apps/indexer/test/indexer/internal_transaction/fetcher_test.exs index 38213458e7..9c7b3f5516 100644 --- a/apps/indexer/test/indexer/internal_transaction/fetcher_test.exs +++ b/apps/indexer/test/indexer/internal_transaction/fetcher_test.exs @@ -2,7 +2,6 @@ defmodule Indexer.InternalTransaction.FetcherTest do use EthereumJSONRPC.Case, async: false use Explorer.DataCase - import ExUnit.CaptureLog import Mox alias Explorer.Chain.{Address, Hash, Transaction} @@ -87,70 +86,32 @@ defmodule Indexer.InternalTransaction.FetcherTest do ) == [] end - test "buffers collated transactions with unfetched internal transactions", %{ + test "buffers blocks with unfetched internal transactions", %{ json_rpc_named_arguments: json_rpc_named_arguments } do block = insert(:block) - collated_unfetched_transaction = - :transaction - |> insert() - |> with_block(block) - assert InternalTransaction.Fetcher.init( [], - fn hash_string, acc -> [hash_string | acc] end, + fn block_number, acc -> [block_number | acc] end, json_rpc_named_arguments - ) == [{block.number, collated_unfetched_transaction.hash.bytes, collated_unfetched_transaction.index}] + ) == [block.number] end - test "does not buffer collated transactions with fetched internal transactions", %{ + test "does not buffer blocks with fetched internal transactions", %{ json_rpc_named_arguments: json_rpc_named_arguments } do - :transaction - |> insert() - |> with_block(internal_transactions_indexed_at: DateTime.utc_now()) + insert(:block, internal_transactions_indexed_at: DateTime.utc_now()) assert InternalTransaction.Fetcher.init( [], - fn hash_string, acc -> [hash_string | acc] end, + fn block_number, acc -> [block_number | acc] end, json_rpc_named_arguments ) == [] end end describe "run/2" do - test "duplicate transaction hashes are logged", %{json_rpc_named_arguments: json_rpc_named_arguments} do - if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do - expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> - {:ok, [%{id: 0, result: %{"trace" => []}}]} - end) - end - - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - - %Transaction{hash: %Hash{bytes: bytes}} = - insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa") - - log = - capture_log(fn -> - InternalTransaction.Fetcher.run( - [ - {1, bytes, 0}, - {1, bytes, 0} - ], - json_rpc_named_arguments - ) - end) - - assert log =~ - """ - Duplicate entries being used to fetch internal transactions: - 1. {1, <<3, 205, 88, 153, 166, 59, 111, 98, 34, 175, 218, 135, 5, 208, 89, 253, 90, 125, 18, 107, 202, 190, 150, 47, 182, 84, 217, 115, 110, 107, 202, 250>>, 0} - 2. {1, <<3, 205, 88, 153, 166, 59, 111, 98, 34, 175, 218, 135, 5, 208, 89, 253, 90, 125, 18, 107, 202, 190, 150, 47, 182, 84, 217, 115, 110, 107, 202, 250>>, 0} - """ - end - @tag :no_parity test "internal transactions with failed parent does not create a new address", %{ json_rpc_named_arguments: json_rpc_named_arguments @@ -199,6 +160,7 @@ defmodule Indexer.InternalTransaction.FetcherTest do "type" => "create" } ], + "transactionHash" => "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa", "vmTrace" => nil } } @@ -226,27 +188,5 @@ defmodule Indexer.InternalTransaction.FetcherTest do assert is_nil(fetched_address) end end - - test "duplicate transaction hashes only retry uniques", %{json_rpc_named_arguments: json_rpc_named_arguments} do - if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do - expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> - {:ok, [%{id: 0, error: %{code: -32602, message: "Invalid params"}}]} - end) - end - - CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - - # not a real transaction hash, so that fetch fails - %Transaction{hash: %Hash{bytes: bytes}} = - insert(:transaction, hash: "0x0000000000000000000000000000000000000000000000000000000000000001") - - assert InternalTransaction.Fetcher.run( - [ - {1, bytes, 0}, - {1, bytes, 0} - ], - json_rpc_named_arguments - ) == {:retry, [{1, bytes, 0}]} - end end end