Use trace_replayBlockTransactions API for faster tracing

pull/1543/head
Paul Tsupikoff 6 years ago committed by goodsoft
parent 5d5b8fa84d
commit e13f6f8a8e
No known key found for this signature in database
GPG Key ID: DF5159A3A5F09D21
  1. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ganache.ex
  2. 125
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
  3. 62
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
  4. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/variant.ex
  5. 67
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs
  6. 89
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs
  7. 195
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs
  8. 65
      apps/explorer/lib/explorer/chain.ex
  9. 9
      apps/explorer/lib/explorer/chain/block.ex
  10. 4
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  11. 38
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  12. 10
      apps/explorer/priv/repo/migrations/20190228220746_add_internal_transactions_indexed_at_to_blocks.exs
  13. 23
      apps/explorer/test/explorer/chain/import_test.exs
  14. 1
      apps/explorer/test/explorer/chain_test.exs
  15. 4
      apps/indexer/config/dev/parity.exs
  16. 19
      apps/indexer/lib/indexer/block/catchup/fetcher.ex
  17. 92
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  18. 64
      apps/indexer/lib/indexer/internal_transaction/fetcher.ex
  19. 70
      apps/indexer/test/indexer/block/fetcher_test.exs
  20. 338
      apps/indexer/test/indexer/block/realtime/fetcher_test.exs
  21. 74
      apps/indexer/test/indexer/internal_transaction/fetcher_test.exs

@ -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.

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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
)

@ -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)

@ -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

@ -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

@ -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

@ -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
}
]

@ -996,6 +996,7 @@ defmodule Explorer.ChainTest do
internal_transactions: %{
params: [
%{
block_number: 37,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
index: 0,
trace_address: [],

@ -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
],

@ -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

@ -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

@ -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 ->

@ -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

@ -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

@ -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

Loading…
Cancel
Save