diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 5511d9eb80..6dd5a547d7 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -65,6 +65,8 @@ defmodule Explorer.Chain.TokenTransfer do @constant "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @transfer_function_signature "0xa9059cbb" + @primary_key false schema "token_transfers" do field(:amount, :decimal) @@ -115,6 +117,11 @@ defmodule Explorer.Chain.TokenTransfer do """ def constant, do: @constant + @doc """ + ERC 20's transfer(address,uint256) function signature + """ + def transfer_function_signature, do: @transfer_function_signature + @spec fetch_token_transfers_from_token_hash(Hash.t(), [paging_options]) :: [] def fetch_token_transfers_from_token_hash(token_address_hash, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 36c9425ee5..a108a8caa3 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -15,6 +15,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Ecto.Changeset alias EthereumJSONRPC.{FetchedBalances, Subscription} alias Explorer.Chain + alias Explorer.Chain.TokenTransfer alias Indexer.{AddressExtraction, Block, TokenBalances, Tracer} alias Indexer.Block.Realtime.{ConsensusEnsurer, TaskSupervisor} @@ -332,7 +333,8 @@ defmodule Indexer.Block.Realtime.Fetcher do ) do case transactions_params |> transactions_params_to_fetch_internal_transactions_params(token_transfers_params) - |> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) do + |> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments) + |> reject_simple_token_transfer_internal_transactions() do {:ok, internal_transactions_params} -> merged_addresses_params = %{internal_transactions: internal_transactions_params} @@ -373,6 +375,21 @@ defmodule Indexer.Block.Realtime.Fetcher do end end + defp reject_simple_token_transfer_internal_transactions({:ok, internal_transaction_params}) do + # 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 + + filtered_internal_transaction = + Enum.reject(internal_transaction_params, fn internal_transaction -> + internal_transaction.value == 0 && + String.starts_with?(internal_transaction.input, TokenTransfer.transfer_function_signature()) + end) + + {:ok, filtered_internal_transaction} + end + + defp reject_simple_token_transfer_internal_transactions(result), do: result + # Input-less transactions are value-transfers only, so their internal transactions do not need to be indexed defp fetch_internal_transactions?(%{status: :ok, created_contract_address_hash: nil, input: "0x"}, _), do: false # Token transfers not transferred during contract creation don't need internal transactions as the token transfers diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs index 5e76fcaabe..4b729a8cc7 100644 --- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs @@ -415,5 +415,327 @@ defmodule Indexer.Block.Realtime.FetcherTest do errors: [] }} = Indexer.Block.Fetcher.fetch_and_import_range(block_fetcher, 3_946_079..3_946_080) end + + @tag :no_geth + test "doesn't fetch internal transaction for ERC 20's transfer method", %{ + block_fetcher: %Indexer.Block.Fetcher{} = block_fetcher, + json_rpc_named_arguments: json_rpc_named_arguments + } do + {:ok, sequence} = Sequence.start_link(ranges: [], step: 2) + Sequence.cap(sequence) + + Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + + Uncle.Supervisor.Case.start_supervised!( + block_fetcher: %Indexer.Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} + ) + + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getBlockByNumber", + params: ["0x3C365F", true] + } + ], + _ -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "author" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", + "difficulty" => "0xfffffffffffffffffffffffffffffffe", + "extraData" => "0xd583010b088650617269747986312e32372e32826c69", + "gasLimit" => "0x7a1200", + "gasUsed" => "0x2886e", + "hash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc", + "logsBloom" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", + "number" => "0x3c365f", + "parentHash" => "0x57f6d66e07488defccd5216c4d2968dd6afd3bd32415e284de3b02af6535e8dc", + "receiptsRoot" => "0x111be72e682cea9c93e02f1ef503fb64aa821b2ef510fd9177c49b37d0af98b5", + "sealFields" => [ + "0x841246c63f", + "0xb841ba3d11db672fd7893d1b7906275fa7c4c7f4fbcc8fa29eab0331480332361516545ef10a36d800ad2be2b449dde8d5703125156a9cf8a035f5a8623463e051b700" + ], + "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "signature" => + "ba3d11db672fd7893d1b7906275fa7c4c7f4fbcc8fa29eab0331480332361516545ef10a36d800ad2be2b449dde8d5703125156a9cf8a035f5a8623463e051b700", + "size" => "0x33e", + "stateRoot" => "0x7f73f5fb9f891213b671356126c31e9795d038844392c7aa8800ed4f52307209", + "step" => "306628159", + "timestamp" => "0x5b61df3b", + "totalDifficulty" => "0x3c365effffffffffffffffffffffffed7f0362", + "transactions" => [ + %{ + "blockHash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc", + "blockNumber" => "0x3c365f", + "chainId" => "0x63", + "condition" => nil, + "creates" => nil, + "from" => "0x40b18103537c0f15d5e137dd8ddd019b84949d16", + "gas" => "0x3d9c5", + "gasPrice" => "0x3b9aca00", + "hash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8", + "input" => + "0x8841ac11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", + "nonce" => "0x65b", + "publicKey" => + "0x89c2123ed4b5d141cf1f4b6f5f3d754418f03aea2e870a1c50888d94bf5531f74237e2fea72d0bc198ef213272b62c6869615720757255e6cba087f9db6e759f", + "r" => "0x55a1a93541d7f782f97f6699437bb60fa4606d63760b30c1ee317e648f93995", + "raw" => + "0xf8f582065b843b9aca008303d9c594698bf6943bab687b2756394624aa183f434f65da8901158e4f216242a000b8848841ac11000000000000000000000000000000000000000000000000000000000000006c00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000581eaa0055a1a93541d7f782f97f6699437bb60fa4606d63760b30c1ee317e648f93995a06affd4da5eca84fbca2b016c980f861e0af1f8d6535e2fe29d8f96dc0ce358f7", + "s" => "0x6affd4da5eca84fbca2b016c980f861e0af1f8d6535e2fe29d8f96dc0ce358f7", + "standardV" => "0x1", + "to" => "0x698bf6943bab687b2756394624aa183f434f65da", + "transactionIndex" => "0x0", + "v" => "0xea", + "value" => "0x1158e4f216242a000" + } + ], + "transactionsRoot" => "0xd7c39a93eafe0bdcbd1324c13dcd674bed8c9fa8adbf8f95bf6a59788985da6f", + "uncles" => ["0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cd"] + } + } + ]} + end) + |> expect(:json_rpc, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_getTransactionReceipt", + params: ["0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8"] + } + ], + _ -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "blockHash" => "0xa4ec735cabe1510b5ae081b30f17222580b4588dbec52830529753a688b046cc", + "blockNumber" => "0x3c365f", + "contractAddress" => nil, + "cumulativeGasUsed" => "0x2886e", + "gasUsed" => "0x2886e", + "logs" => [], + "logsBloom" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "root" => nil, + "status" => "0x1", + "transactionHash" => "0xd3937e70fab3fb2bfe8feefac36815408bf07de3b9e09fe81114b9a6b17f55c8", + "transactionIndex" => "0x0" + } + } + ]} + end) + |> expect(:json_rpc, fn [%{method: "trace_block"}] = requests, _options -> + 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" => + "0xa9059cbb000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", + "to" => "0x698bf6943bab687b2756394624aa183f434f65da", + "value" => "0x1158e4f216242a000" + }, + "result" => %{"gasUsed" => "0x23256", "output" => "0x"}, + "subtraces" => 5, + "traceAddress" => [], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x36771", + "input" => "0xa9059cbb000000000000000000000000000000000000000000000000000000000000006c", + "to" => "0x11c4469d974f8af5ba9ec99f3c42c07c848c861c", + "value" => "0x0" + }, + "result" => %{ + "gasUsed" => "0x495", + "output" => "0x00000000000000000000000040b18103537c0f15d5e137dd8ddd019b84949d16" + }, + "subtraces" => 0, + "traceAddress" => [0], + "type" => "call" + }, + %{ + "action" => %{ + "callType" => "call", + "from" => "0x698bf6943bab687b2756394624aa183f434f65da", + "gas" => "0x35acb", + "input" => "0xa9059cbb000000000000000000000000000000000000000000000000000000000000006c", + "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" => + "0xa9059cbb0000000000000000000000000000000000000000000000000000000000000006c0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a", + "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" + } + ], + "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: ["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"} + ]} + end) + end + + assert {:ok, + %{ + inserted: %{ + addresses: [ + %Address{hash: first_address_hash, fetched_coin_balance_block_number: 3_946_079}, + %Address{hash: second_address_hash, fetched_coin_balance_block_number: 3_946_079}, + %Address{hash: third_address_hash, fetched_coin_balance_block_number: 3_946_079}, + %Address{hash: fifth_address_hash, fetched_coin_balance_block_number: 3_946_079} + ], + address_coin_balances: [ + %{ + address_hash: first_address_hash, + block_number: 3_946_079 + }, + %{ + address_hash: second_address_hash, + block_number: 3_946_079 + }, + %{ + address_hash: third_address_hash, + block_number: 3_946_079 + }, + %{ + address_hash: fifth_address_hash, + block_number: 3_946_079 + } + ], + blocks: [%Chain.Block{number: 3_946_079}], + internal_transactions: [ + %{index: 0, transaction_hash: transaction_hash}, + %{index: 3, transaction_hash: transaction_hash}, + %{index: 5, transaction_hash: transaction_hash} + ], + transactions: [%Transaction{hash: transaction_hash}] + }, + errors: [] + }} = Indexer.Block.Fetcher.fetch_and_import_range(block_fetcher, 3_946_079..3_946_079) + end end end