Use Mox by default for local integration tests

How to run without Mox is in the root `README.md`.

Run all combinations of Variant and Transport on CircleCI to ensure that
Mox matches HTTP behavior, so Mox can be used for fast local testing:
* Geth, HTTP
* Geth, Mox
* Parity, HTTP
* Parity, Mox

Using Mox locally also lessens load on Sokol and the Infura API key the
tests use for Geth under the assumption that we do far more local
testing than CI testing.  May help with #340, but does not fix the root
cause, which is now known as `Sequence` not chunking ranges correctly on
retry.
pull/384/head
Luke Imhoff 6 years ago
parent 834ad23f26
commit 8f827ecfa6
  1. 109
      .circleci/config.yml
  2. 11
      apps/ethereum_jsonrpc/README.md
  3. 2
      apps/ethereum_jsonrpc/mix.exs
  4. 179
      apps/ethereum_jsonrpc/test/etheream_jsonrpc_test.exs
  5. 28
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs
  6. 121
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs
  7. 180
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs
  8. 146
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
  9. 69
      apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case.ex
  10. 32
      apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/http/case.ex
  11. 4
      apps/ethereum_jsonrpc/test/test_helper.exs
  12. 5
      apps/explorer/lib/explorer/chain.ex
  13. 6
      apps/indexer/config/config.exs
  14. 7
      apps/indexer/config/dev.exs
  15. 2
      apps/indexer/config/dev/geth.exs
  16. 2
      apps/indexer/config/dev/parity.exs
  17. 7
      apps/indexer/config/prod.exs
  18. 13
      apps/indexer/config/prod/geth.exs
  19. 17
      apps/indexer/config/prod/parity.exs
  20. 1
      apps/indexer/config/test.exs
  21. 4
      apps/indexer/mix.exs
  22. 96
      apps/indexer/test/indexer/address_balance_fetcher_test.exs
  23. 495
      apps/indexer/test/indexer/block_fetcher_test.exs
  24. 95
      apps/indexer/test/indexer/internal_transaction_fetcher_test.exs
  25. 73
      apps/indexer/test/indexer/pending_transaction_fetcher_test.exs
  26. 2
      apps/indexer/test/test_helper.exs

@ -296,7 +296,7 @@ jobs:
name: Scan explorer_web for vulnerabilities
command: mix sobelow --config
working_directory: "apps/explorer_web"
test_geth:
test_geth_http:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.6.5-node-browsers
@ -306,7 +306,10 @@ jobs:
PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below
PGUSER: postgres
ETHEREUM_JSONRPC_VARIANT: geth
ETHEREUM_JSONRPC_VARIANT: "EthereumJSONRPC.Geth"
ETHEREUM_JSONRPC_TRANSPORT: "EthereumJSONRPC.HTTP"
ETHEREUM_JSONRPC_HTTP: "EthereumJSONRPC.HTTP.HTTPoison"
ETHEREUM_JSONRPC_HTTP_URL: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"
- image: circleci/postgres:10.3-alpine
environment:
# Match apps/explorer/config/test.exs config :explorerer, Explorer.Repo, database
@ -333,7 +336,7 @@ jobs:
- store_test_results:
path: _build/test/junit
test_parity:
test_geth_mox:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.6.5-node-browsers
@ -343,7 +346,8 @@ jobs:
PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below
PGUSER: postgres
ETHEREUM_JSONRPC_VARIANT: parity
ETHEREUM_JSONRPC_VARIANT: "EthereumJSONRPC.Geth"
ETHEREUM_JSONRPC_TRANSPORT: "EthereumJSONRPC.Mox"
- image: circleci/postgres:10.3-alpine
environment:
# Match apps/explorer/config/test.exs config :explorerer, Explorer.Repo, database
@ -366,7 +370,86 @@ jobs:
name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run: mix coveralls.circle --parallel --umbrella
- run: mix coveralls.circle --exclude no_geth --parallel --umbrella
- store_test_results:
path: _build/test/junit
test_parity_http:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.6.5-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below
PGUSER: postgres
ETHEREUM_JSONRPC_VARIANT: "EthereumJSONRPC.Parity"
# enable on-chain tests against Sokol instead of `mox` tests run locally
ETHEREUM_JSONRPC_TRANSPORT: "EthereumJSONRPC.HTTP"
ETHEREUM_JSONRPC_HTTP: "EthereumJSONRPC.HTTP.HTTPoison"
ETHEREUM_JSONRPC_HTTP_URL: "https://sokol-trace.poa.network"
- image: circleci/postgres:10.3-alpine
environment:
# Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
POSTGRES_DB: explorer_test
# match PGPASSWORD for elixir image above
POSTGRES_PASSWORD: postgres
# match PGUSER for elixir image above
POSTGRES_USER: postgres
working_directory: ~/app
steps:
- attach_workspace:
at: .
- run: mix local.hex --force
- run: mix local.rebar --force
- run:
name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run: mix coveralls.circle --exclude no_parity --parallel --umbrella
- store_test_results:
path: _build/test/junit
test_parity_mox:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.6.5-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below
PGUSER: postgres
ETHEREUM_JSONRPC_VARIANT: "EthereumJSONRPC.Parity"
ETHEREUM_JSONRPC_TRANSPORT: "EthereumJSONRPC.Mox"
- image: circleci/postgres:10.3-alpine
environment:
# Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
POSTGRES_DB: explorer_test
# match PGPASSWORD for elixir image above
POSTGRES_PASSWORD: postgres
# match PGUSER for elixir image above
POSTGRES_USER: postgres
working_directory: ~/app
steps:
- attach_workspace:
at: .
- run: mix local.hex --force
- run: mix local.rebar --force
- run:
name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run: mix coveralls.circle --exclude no_parity --parallel --umbrella
- store_test_results:
path: _build/test/junit
@ -394,8 +477,10 @@ workflows:
- eslint
- jest
- sobelow
- test_parity
- test_geth
- test_parity_http
- test_parity_mox
- test_geth_http
- test_geth_mox
- dialyzer:
requires:
- build
@ -411,9 +496,15 @@ workflows:
- sobelow:
requires:
- build
- test_parity:
- test_parity_http:
requires:
- build
- test_parity_mox:
requires:
- build
- test_geth_http:
requires:
- build
- test_geth:
- test_geth_mox:
requires:
- build

@ -20,6 +20,17 @@ via `:trace_url`. The trace URL and is used for
tracing nodes. The `:http` option is passed directly to the HTTP
library (`HTTPoison`), which forwards the options down to `:hackney`.
## Testing
By default, [`mox`](https://github.com/plataformatec/mox) will be used to mock the `EthereumJSONRPC.Transport` and `EthereumJSONRPC.HTTP` behaviours. They mocked behaviours returns differ based on the `EthereumJSONRPC.Variant`.
| `EthereumJSONRPC.Variant` | `EthereumJSONRPC.Transport` | `EthereumJSONRPC.HTTP` | `url` | Command | Usage(s) |
|:--------------------------|:----------------------------|:---------------------------------|:--------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------|
| `EthereumJSONRPC.Parity` | `EthereumJSONRPC.Mox` | `EthereumJSONRPC.HTTP.Mox` | N/A | `mix test` | Local, `circleci/config.yml` `test_parity_mox` job |
| `EthereumJSONRPC.Parity` | `EthereumJSONRPC.HTTP` | `EthereumJSONRPC.HTTP.HTTPoison` | `https://trace-sokol.poa.network` | `ETHEREUM_JSONRPC_VARIANT=EthereumJSONRPC.Parity ETHEREUM_JSONRPC_TRANSPORT=EthereumJSONRPC.HTTP ETHEREUM_JSONRPC_HTTP=EthereumJSONRPC.HTTP.HTTPoison ETHEREUM_JSONRPC_HTTP_URL=https://sokol-trace.poa.network mix test --exclude no_parity` | `.circleci/config.yml` `test_parity_http` job |
| `EthereumJSONRPC.Geth` | `EthereumJSONRPC.Mox` | `EthereumJSONRPC.HTTP.Mox` | N/A | `ETHEREUM_JSONRPC_VARIANT=EthereumJSONRPC.Geth mix test --exclude no_geth` | `.circleci/config.yml` `test_geth_http` job |
| `EthereumJSONRPC.Geth` | `EthereumJSONRPC.HTTP` | `EthereumJSONRPC.HTTP.HTTPoison` | `https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY` | `ETHEREUM_JSONRPC_VARIANT=EthereumJSONRPC.Geth ETHEREUM_JSONRPC_TRANSPORT=EthereumJSONRPC.HTTP ETHEREUM_JSONRPC_HTTP=EthereumJSONRPC.HTTP.HTTPoison ETHEREUM_JSONRPC_HTTP_URL=https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY mix test --exclude no_geth` | `.circleci/config.yml` `test_geth_http` job |
## Installation
The OTP application `:ethereum_jsonrpc` can be used in other umbrella

@ -69,6 +69,8 @@ defmodule EthereumJsonrpc.MixProject do
{:httpoison, "~> 1.0", override: true},
# Decode/Encode JSON for JSONRPC
{:jason, "~> 1.0"},
# Mocking `EthereumJSONRPC.Transport` and `EthereumJSONRPC.HTTP` so we avoid hitting real chains for local testing
{:mox, "~> 0.3.2", only: [:test]},
# Convert unix timestamps in JSONRPC to DateTimes
{:timex, "~> 3.1.24"}
]

@ -1,99 +1,130 @@
defmodule EthereumJSONRPCTest do
use ExUnit.Case, async: true
use EthereumJSONRPC.Case, async: true
import EthereumJSONRPC.Case
import Mox
@moduletag :capture_log
setup do
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
setup :verify_on_exit!
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: variant
]
}
end
@moduletag :capture_log
describe "fetch_balances/1" do
test "with all valid hash_data returns {:ok, addresses_params}", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
assert {:ok,
expected_fetched_balance =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Geth -> 0
EthereumJSONRPC.Parity -> 1
variant -> raise ArgumentError, "Unsupported variant (#{variant}})"
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, [%{id: 0, result: EthereumJSONRPC.integer_to_quantity(expected_fetched_balance)}]}
end)
end
hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
assert EthereumJSONRPC.fetch_balances(
[
%{block_quantity: "0x1", hash_data: hash}
],
json_rpc_named_arguments
) ==
{:ok,
[
%{
fetched_balance: fetched_balance,
fetched_balance: expected_fetched_balance,
fetched_balance_block_number: 1,
hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
hash: hash
}
]} =
EthereumJSONRPC.fetch_balances(
[
%{block_quantity: "0x1", hash_data: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"}
],
json_rpc_named_arguments
)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Geth ->
assert fetched_balance == 0
EthereumJSONRPC.Parity ->
assert fetched_balance == 1
variant ->
raise ArgumentError, "Unsupported variant (#{variant}})"
end
]}
end
test "with all invalid hash_data returns {:error, reasons}", %{json_rpc_named_arguments: json_rpc_named_arguments} do
assert {:error, reasons} =
EthereumJSONRPC.fetch_balances([%{block_quantity: "0x1", hash_data: "0x0"}], json_rpc_named_arguments)
assert is_list(reasons)
assert length(reasons) == 1
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
[reason] = reasons
assert %{
code: -32602,
data: %{"blockNumber" => "0x1", "hash" => "0x0"},
message: message
} = reason
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
expected_message =
case variant do
EthereumJSONRPC.Geth ->
assert message ==
"invalid argument 0: json: cannot unmarshal hex string of odd length into Go value of type common.Address"
EthereumJSONRPC.Parity ->
assert message ==
"Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
variant ->
_ ->
raise ArgumentError, "Unsupported variant (#{variant}})"
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
error: %{
code: -32602,
message: expected_message
}
}
]}
end)
end
assert {:error,
[
%{
code: -32602,
data: %{"blockNumber" => "0x1", "hash" => "0x0"},
message: ^expected_message
}
]} =
EthereumJSONRPC.fetch_balances([%{block_quantity: "0x1", hash_data: "0x0"}], json_rpc_named_arguments)
end
test "with a mix of valid and invalid hash_data 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: "0x0"
},
%{
id: 1,
result: "0x1"
},
%{
id: 2,
error: %{
code: -32602,
message:
"Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
}
},
%{
id: 3,
result: "0x3"
},
%{
id: 4,
error: %{
code: -32602,
message:
"Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
}
}
]
}
end)
end
assert {:error, reasons} =
EthereumJSONRPC.fetch_balances(
[
@ -134,6 +165,12 @@ defmodule EthereumJSONRPCTest do
describe "fetch_block_number_by_tag" do
@tag capture_log: false
test "with earliest", %{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, %{"number" => "0x0"}}
end)
end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("earliest", json_rpc_named_arguments) end,
fn result ->
@ -144,6 +181,12 @@ defmodule EthereumJSONRPCTest do
@tag capture_log: false
test "with latest", %{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, %{"number" => "0x1"}}
end)
end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) end,
fn result ->
@ -155,6 +198,12 @@ defmodule EthereumJSONRPCTest do
@tag capture_log: false
test "with pending", %{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, %{"number" => "0x2"}}
end)
end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) end,
fn result ->

@ -1,36 +1,10 @@
defmodule EthereumJSONRPC.GethTest do
use ExUnit.Case, async: false
use EthereumJSONRPC.Case, async: false
alias EthereumJSONRPC.Geth
@moduletag :no_parity
setup do
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
]
],
variant: variant
}
end
describe "fetch_internal_transactions/2" do
test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
Geth.fetch_internal_transactions(

@ -0,0 +1,121 @@
defmodule EthereumJSONRPC.HTTP.MoxTest do
@moduledoc """
Tests differences in behavior of `EthereumJSONRPC` when `EthereumJSONRPC.HTTP` is used as the transport that are too
detrimental to run against Sokol, so uses `EthereumJSONRPC.HTTP.Mox` instead.
"""
use ExUnit.Case, async: true
import EthereumJSONRPC, only: [request: 1]
import EthereumJSONRPC.HTTP.Case
import Mox
setup do
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.Mox,
url: url(),
http_options: http_options()
]
]
}
end
setup :verify_on_exit!
describe "json_rpc/2" do
# regression test for https://github.com/poanetwork/poa-explorer/issues/254
#
# this test triggered a DoS with CloudFlare reporting 502 Bad Gateway
# (see https://github.com/poanetwork/poa-explorer/issues/340), so it can't be run against the real Sokol chain and
# must use `mox` to fake it.
test "transparently splits batch payloads that would trigger a 413 Request Entity Too Large", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do
EthereumJSONRPC.HTTP.Mox
|> expect(:json_rpc, 2, fn _url, json, _options ->
assert IO.iodata_to_binary(json) =~ ":13000"
{:ok, %{body: "413 Request Entity Too Large", status_code: 413}}
end)
|> expect(:json_rpc, fn _url, json, _options ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ ":13000"
assert json_binary =~ ":6499"
body =
0..6499
|> Enum.map(fn id ->
%{jsonrpc: "2.0", id: id, result: %{number: EthereumJSONRPC.integer_to_quantity(id)}}
end)
|> Jason.encode!()
{:ok, %{body: body, status_code: 200}}
end)
|> expect(:json_rpc, fn _url, json, _optons ->
json_binary = IO.iodata_to_binary(json)
refute json_binary =~ ":6499"
assert json_binary =~ ":6500"
assert json_binary =~ ":13000"
body =
6500..13000
|> Enum.map(fn id ->
%{jsonrpc: "2.0", id: id, result: %{number: EthereumJSONRPC.integer_to_quantity(id)}}
end)
|> Jason.encode!()
{:ok, %{body: body, status_code: 200}}
end)
end
block_numbers = 0..13000
payload =
block_numbers
|> Stream.with_index()
|> Enum.map(&get_block_by_number_request/1)
assert_payload_too_large(payload, json_rpc_named_arguments)
assert {:ok, responses} = EthereumJSONRPC.json_rpc(payload, json_rpc_named_arguments)
assert Enum.count(responses) == Enum.count(block_numbers)
block_number_set = MapSet.new(block_numbers)
response_block_number_set =
Enum.into(responses, MapSet.new(), fn %{result: %{"number" => quantity}} ->
EthereumJSONRPC.quantity_to_integer(quantity)
end)
assert MapSet.equal?(response_block_number_set, block_number_set)
end
end
defp assert_payload_too_large(payload, json_rpc_named_arguments) do
assert Keyword.fetch!(json_rpc_named_arguments, :transport) == EthereumJSONRPC.HTTP
transport_options = Keyword.fetch!(json_rpc_named_arguments, :transport_options)
http = Keyword.fetch!(transport_options, :http)
url = Keyword.fetch!(transport_options, :url)
json = Jason.encode_to_iodata!(payload)
http_options = Keyword.fetch!(transport_options, :http_options)
assert {:ok, %{body: body, status_code: 413}} = http.json_rpc(url, json, http_options)
assert body =~ "413 Request Entity Too Large"
end
defp get_block_by_number_request({block_number, id}) do
request(%{
id: id,
method: "eth_getBlockByNumber",
params: [EthereumJSONRPC.integer_to_quantity(block_number), true]
})
end
end

@ -1,32 +1,77 @@
defmodule EthereumJSONRPC.ParityTest do
use ExUnit.Case, async: true
use EthereumJSONRPC.Case
@moduletag :no_geth
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Mox
setup do
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://sokol-trace.poa.network",
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
]
]
}
end
setup :verify_on_exit!
doctest EthereumJSONRPC.Parity
@moduletag :no_geth
describe "fetch_internal_transactions/1" do
test "with all valid transaction_params returns {:ok, transactions_params}", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
gas = 4_533_872
init =
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef"
value = 0
block_number = 1
index = 0
created_contract_address_hash = "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461"
created_contract_code =
"0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029"
gas_used = 382_953
trace_address = []
transaction_hash = "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1"
type = "create"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
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,
"type" => type
}
]
}
}
]}
end)
end
assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[
%{
block_number: 1,
hash_data: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1"
block_number: block_number,
hash_data: transaction_hash
}
],
json_rpc_named_arguments
@ -35,19 +80,17 @@ defmodule EthereumJSONRPC.ParityTest do
[
%{
block_number: 1,
created_contract_address_hash: "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461",
created_contract_code:
"0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029",
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
gas: 4_533_872,
gas_used: 382_953,
index: 0,
init:
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
trace_address: [],
transaction_hash: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1",
type: "create",
value: 0
created_contract_address_hash: created_contract_address_hash,
created_contract_code: created_contract_code,
from_address_hash: from_address_hash,
gas: gas,
gas_used: gas_used,
index: index,
init: init,
trace_address: trace_address,
transaction_hash: transaction_hash,
type: type,
value: value
}
]
}
@ -56,6 +99,21 @@ defmodule EthereumJSONRPC.ParityTest do
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(
[
%{
@ -68,12 +126,12 @@ defmodule EthereumJSONRPC.ParityTest do
{:error,
[
%{
:code => -32603,
:data => %{
code: -32603,
data: %{
"blockNumber" => 1,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001"
},
:message =>
message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
]}
@ -82,6 +140,40 @@ defmodule EthereumJSONRPC.ParityTest do
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
@ -99,11 +191,6 @@ defmodule EthereumJSONRPC.ParityTest do
block_number: 1,
hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001"
},
# :error, :ok clause
%{
block_number: 35,
hash_data: "0x6b80a90c958fb5791a070929379ed6eb7a33ecdf9f9cafcada2f6803b3f25ec3"
},
# :error, :error clause
%{
block_number: 2,
@ -115,30 +202,21 @@ defmodule EthereumJSONRPC.ParityTest do
{:error,
[
%{
:code => -32603,
:data => %{
code: -32603,
data: %{
"blockNumber" => 1,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001"
},
:message =>
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
},
%{
:code => -32603,
:data => %{
"blockNumber" => 35,
"transactionHash" => "0x6b80a90c958fb5791a070929379ed6eb7a33ecdf9f9cafcada2f6803b3f25ec3"
},
:message =>
message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
},
%{
:code => -32603,
:data => %{
code: -32603,
data: %{
"blockNumber" => 2,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000002"
},
:message =>
message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}
]}

@ -1,102 +1,134 @@
defmodule EthereumJSONRPC.ReceiptsTest do
use ExUnit.Case, async: true
use EthereumJSONRPC.Case
alias EthereumJSONRPC.Receipts
setup do
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Mox
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
alias EthereumJSONRPC.Receipts
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: variant
]
}
end
setup :verify_on_exit!
doctest Receipts
describe "fetch/2" do
test "with receipts and logs", %{json_rpc_named_arguments: json_rpc_named_arguments} do
%{
cumulative_gas_used: cumulative_gas_used,
gas_used: gas_used,
address_hash: address_hash,
block_number: block_number,
data: data,
index: index,
first_topic: first_topic,
status: status,
type: type,
transaction_hash: transaction_hash,
transaction_index: transaction_index
} =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Geth ->
assert {:ok,
%{
logs: [],
receipts: [
cumulative_gas_used: 884_322,
address_hash: "0x1e2fbe6be9eb39fc894d38be976111f332172d83",
block_number: 3_560_000,
data:
"0x00000000000000000000000033066f6a8adf2d4f5db193524b6fbae062ec0d110000000000000000000000000000000000000000000000000000000000001030",
index: 12,
first_topic: "0xf6db2bace4ac8277384553ad9603d045220a91fb2448ab6130d7a6f044f9a8cf",
gas_used: 106_025,
status: :error,
type: nil,
transaction_hash: "0xd3efddbbeb6ad8d8bb3f6b8c8fb6165567e9dd868013146bdbeb60953c82822a",
transaction_index: 17
}
EthereumJSONRPC.Parity ->
%{
cumulative_gas_used: 1_238_877,
gas_used: 21000,
cumulative_gas_used: 50450,
gas_used: 50450,
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: 37,
data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
index: 0,
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
status: :ok,
transaction_hash: "0x360fb62cc817093e5624468735803ea39cad719e5c68ca322bae6ba4f520756f",
transaction_index: 57
type: "mined",
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
transaction_index: 0
}
]
}} =
Receipts.fetch(
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
native_status =
case status do
:ok -> "0x1"
:error -> "0x0"
end
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
gas: 90000,
hash: "0x360fb62cc817093e5624468735803ea39cad719e5c68ca322bae6ba4f520756f"
id: 0,
result: %{
"cumulativeGasUsed" => integer_to_quantity(cumulative_gas_used),
"gasUsed" => integer_to_quantity(gas_used),
"logs" => [
%{
"address" => address_hash,
"blockNumber" => integer_to_quantity(block_number),
"data" => data,
"logIndex" => integer_to_quantity(index),
"topics" => [first_topic],
"transactionHash" => transaction_hash,
"type" => type
}
],
json_rpc_named_arguments
)
"status" => native_status,
"transactionHash" => transaction_hash,
"transactionIndex" => integer_to_quantity(transaction_index)
}
}
]}
end)
end
EthereumJSONRPC.Parity ->
assert {:ok,
%{
logs: [
%{
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
address_hash: ^address_hash,
block_number: ^block_number,
data: ^data,
first_topic: ^first_topic,
fourth_topic: nil,
index: 0,
index: ^index,
second_topic: nil,
third_topic: nil,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
type: "mined"
transaction_hash: ^transaction_hash
}
| _
],
receipts: [
%{
cumulative_gas_used: 50450,
gas_used: 50450,
status: :ok,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
transaction_index: 0
cumulative_gas_used: ^cumulative_gas_used,
gas_used: ^gas_used,
status: ^status,
transaction_hash: ^transaction_hash,
transaction_index: ^transaction_index
}
]
}} =
Receipts.fetch(
[
%{
gas: 50451,
hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
gas: 9000,
hash: transaction_hash
}
],
json_rpc_named_arguments
)
variant ->
raise ArgumentError, "Unsupported variant (#{variant})"
end
end
end
end

@ -1,10 +1,79 @@
defmodule EthereumJSONRPC.Case do
@moduledoc """
Adds `json_rpc_named_arguments` to context.
Reads `ETHEREUM_JSONRPC_TRANSPORT` environment variable to determine which module to use `:json_rpc_named_arguments`
`:transport`:
* `EthereumJSONRPC.HTTP` - Allow testing of HTTP-only behavior like status codes
* `EthereumJSONRPC.Mox` - mock, transport neutral responses. The default for local testing.
When `ETHEREUM_JSONRPC_TRANSPORT` is `EthereumJSONRPC.HTTP`, then reads `ETHEREUM_JSONRPC_HTTP_URL` environment
variable to determine `:json_rpc_named_arguments` `:transport_options` `:url`. Failure to set
`ETHEREUM_JSONRPC_HTTP_URL` in this case will raise an `ArgumentError`.
* `EthereumJSONRPC.HTTP.HTTPoison` - HTTP responses from calls to real chain URLs
* `EthereumJSONRPC.HTTP.Mox` - mock HTTP responses, so can be used for HTTP-only behavior like status codes.
"""
use ExUnit.CaseTemplate
require Logger
setup do
transport = transport()
transport_options =
case transport do
EthereumJSONRPC.HTTP ->
[
http: EthereumJSONRPC.HTTP.Case.http(),
url: EthereumJSONRPC.HTTP.Case.url(),
http_options: EthereumJSONRPC.HTTP.Case.http_options()
]
_ ->
[]
end
%{
json_rpc_named_arguments: [
transport: transport,
transport_options: transport_options,
variant: variant()
]
}
end
def log_bad_gateway(under_test, assertions) do
case under_test.() do
{:error, {:bad_gateway, url}} -> Logger.error(fn -> ["Bad Gateway to ", url, ". Check CloudFlare."] end)
other -> assertions.(other)
end
end
def module(environment_variable, default) do
alias =
environment_variable
|> System.get_env()
|> Kernel.||(default)
module = Module.concat([alias])
with {:error, reason} <- Code.ensure_loaded(module) do
raise ArgumentError,
"Could not load `#{environment_variable}` environment variable module (#{module}) due to #{reason}"
end
module
end
def transport do
module("ETHEREUM_JSONRPC_TRANSPORT", "EthereumJSONRPC.Mox")
end
def variant do
module("ETHEREUM_JSONRPC_VARIANT", "EthereumJSONRPC.Parity")
end
end

@ -0,0 +1,32 @@
defmodule EthereumJSONRPC.HTTP.Case do
use ExUnit.CaseTemplate
import EthereumJSONRPC.Case, only: [module: 2]
setup do
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: http(),
url: url(),
http_options: http_options()
]
]
}
end
def http do
module("ETHEREUM_JSONRPC_HTTP", "EthereumJSONRPC.HTTP.Mox")
end
def http_options do
[recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
end
def url do
"ETHEREUM_JSONRPC_HTTP_URL"
|> System.get_env()
|> Kernel.||("https://example.com")
end
end

@ -6,5 +6,9 @@ File.mkdir_p!(junit_folder)
# Counter `test --no-start`. `--no-start` is needed for `:indexer` compatibility
{:ok, _} = Application.ensure_all_started(:ethereum_jsonrpc)
Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)
# for when we need to simulate HTTP-specific stuff like 413 Request Entity Too Large
Mox.defmock(EthereumJSONRPC.HTTP.Mox, for: EthereumJSONRPC.HTTP)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start()

@ -2190,8 +2190,7 @@ defmodule Explorer.Chain do
|> Repo.insert()
end
@spec changes_list(params :: map, [{:for, module} | {:with, :atom}]) ::
{:ok, changes :: map} | {:error, [Changeset.t()]}
@spec changes_list(params :: [map], [{:for, module} | {:with, atom}]) :: {:ok, [map]} | {:error, [Changeset.t()]}
defp changes_list(params, options) when is_list(options) do
ecto_schema_module = Keyword.fetch!(options, :for)
changeset_function_name = Keyword.get(options, :with, :changeset)
@ -2395,7 +2394,7 @@ defmodule Explorer.Chain do
Repo.transaction(multi, timeout: Keyword.get(options, :timeout, @transaction_timeout))
end
@spec insert_internal_transactions([map()], [timestamps_option]) ::
@spec insert_internal_transactions([map], [timeout_option | timestamps_option]) ::
{:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]}
| {:error, [Changeset.t()]}
defp insert_internal_transactions(changes_list, named_arguments)

@ -6,8 +6,6 @@ config :indexer,
debug_logs: !!System.get_env("DEBUG_INDEXER"),
ecto_repos: [Explorer.Repo]
variant = System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity"
# Import variant specific config. This must remain at the bottom
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{variant}.exs"
import_config "#{Mix.env()}.exs"

@ -0,0 +1,7 @@
use Mix.Config
variant = System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity"
# Import variant specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "dev/#{variant}.exs"

@ -8,6 +8,6 @@ config :indexer,
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY",
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
]
],
variant: EthereumJSONRPC.Geth
]

@ -12,6 +12,6 @@ config :indexer,
trace_replayTransaction: "https://sokol-trace.poa.network"
],
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
]
],
variant: EthereumJSONRPC.Parity
]

@ -0,0 +1,7 @@
use Mix.Config
variant = System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity"
# Import variant specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "dev/#{variant}.exs"

@ -0,0 +1,13 @@
use Mix.Config
config :indexer,
block_rate: 5_000,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY",
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Geth
]

@ -0,0 +1,17 @@
use Mix.Config
config :indexer,
block_rate: 5_000,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://sokol.poa.network",
method_to_url: [
eth_getBalance: "https://sokol-trace.poa.network",
trace_replayTransaction: "https://sokol-trace.poa.network"
],
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Parity
]

@ -39,7 +39,9 @@ defmodule Indexer.MixProject do
# JSONRPC access to Parity for `Explorer.Indexer`
{:ethereum_jsonrpc, in_umbrella: true},
# Importing to database
{:explorer, in_umbrella: true}
{:explorer, in_umbrella: true},
# Mocking `EthereumJSONRPC.Transport`, so we avoid hitting real chains for local testing
{:mox, "~> 0.3.2", only: [:test]}
]
end

@ -1,45 +1,37 @@
defmodule Indexer.AddressBalanceFetcherTest do
# MUST be `async: false` so that {:shared, pid} is set for connection to allow AddressBalanceFetcher's self-send to have
# connection allowed immediately.
use Explorer.DataCase, async: false
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Mox
alias Explorer.Chain.{Address, Hash, Wei}
alias Indexer.{AddressBalanceFetcher, AddressBalanceFetcherCase}
setup do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
@moduletag :capture_log
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
# MUST use global mode because we aren't guaranteed to get `start_supervised`'s pid back fast enough to `allow` it to
# use expectations and stubs from test's pid.
setup :set_mox_global
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
setup :verify_on_exit!
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
setup do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: variant
]
}
:ok
end
describe "init/1" do
test "fetches unfetched Block miner balance", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
%{block_number: block_number, fetched_balance: fetched_balance, miner_hash_data: miner_hash_data} =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
case variant do
EthereumJSONRPC.Geth ->
%{
block_number: 201_480,
@ -58,6 +50,16 @@ defmodule Indexer.AddressBalanceFetcherTest do
raise ArgumentError, "Unsupported variant (#{variant})"
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
block_quantity = integer_to_quantity(block_number)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [^miner_hash_data, ^block_quantity]}],
_options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
end
{:ok, miner_hash} = Hash.Address.cast(miner_hash_data)
miner = insert(:address, hash: miner_hash)
block = insert(:block, miner: miner, number: block_number)
@ -81,8 +83,10 @@ defmodule Indexer.AddressBalanceFetcherTest do
test "fetches unfetched addresses when less than max batch size", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
%{block_number: block_number, fetched_balance: fetched_balance, miner_hash_data: miner_hash_data} =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
case variant do
EthereumJSONRPC.Geth ->
%{
block_number: 201_480,
@ -101,6 +105,16 @@ defmodule Indexer.AddressBalanceFetcherTest do
raise ArgumentError, "Unsupported variant (#{variant})"
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
block_quantity = integer_to_quantity(block_number)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [^miner_hash_data, ^block_quantity]}],
_options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
end
{:ok, miner_hash} = Hash.Address.cast(miner_hash_data)
miner = insert(:address, hash: miner_hash)
block = insert(:block, miner: miner, number: block_number)
@ -121,10 +135,10 @@ defmodule Indexer.AddressBalanceFetcherTest do
describe "async_fetch_balances/1" do
test "fetches balances for address_hashes", %{json_rpc_named_arguments: json_rpc_named_arguments} do
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
%{block_number: block_number, fetched_balance: fetched_balance, hash: hash} =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
case variant do
EthereumJSONRPC.Geth ->
%{
block_number: 201_480,
@ -150,6 +164,19 @@ defmodule Indexer.AddressBalanceFetcherTest do
raise ArgumentError, "Unsupported variant (#{variant})"
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
block_quantity = integer_to_quantity(block_number)
hash_data = to_string(hash)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [^hash_data, ^block_quantity]}],
_options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
end
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
assert :ok = AddressBalanceFetcher.async_fetch_balances([%{block_number: block_number, hash: hash}])
address =
@ -163,7 +190,6 @@ defmodule Indexer.AddressBalanceFetcherTest do
end
describe "run/2" do
@tag :capture_log
test "duplicate address hashes the max block_quantity", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
@ -185,6 +211,13 @@ defmodule Indexer.AddressBalanceFetcherTest do
raise ArgumentError, "Unsupported variant (#{variant})"
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [^hash_data, "0x2"]}], _options ->
{:ok, [%{id: id, result: integer_to_quantity(fetched_balance)}]}
end)
end
case AddressBalanceFetcher.run(
[%{block_quantity: "0x1", hash_data: hash_data}, %{block_quantity: "0x2", hash_data: hash_data}],
0,
@ -209,6 +242,13 @@ defmodule Indexer.AddressBalanceFetcherTest do
test "duplicate address hashes only retry max block_quantity", %{json_rpc_named_arguments: json_rpc_named_arguments} do
hash_data = "0x000000000000000000000000000000000"
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance", params: [^hash_data, "0x2"]}], _options ->
{:ok, [%{id: id, error: %{code: 404, message: "Not Found"}}]}
end)
end
assert AddressBalanceFetcher.run(
[%{block_quantity: "0x1", hash_data: hash_data}, %{block_quantity: "0x2", hash_data: hash_data}],
0,

@ -1,8 +1,11 @@
defmodule Indexer.BlockFetcherTest do
# `async: false` due to use of named GenServer
use Explorer.DataCase, async: false
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
import ExUnit.CaptureLog
import Mox
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import EthereumJSONRPC.Case
alias Explorer.Chain.{Address, Block, Log, Transaction, Wei}
@ -17,7 +20,13 @@ defmodule Indexer.BlockFetcherTest do
Sequence
}
@tag capture_log: true
@moduletag capture_log: true
# MUST use global mode because we aren't guaranteed to get `start_supervised`'s pid back fast enough to `allow` it to
# use expectations and stubs from test's pid.
setup :set_mox_global
setup :verify_on_exit!
# First block with all schemas to import
# 37 is determined using the following query:
@ -37,34 +46,172 @@ defmodule Indexer.BlockFetcherTest do
# ON blocks.hash = transactions.block_hash) as blocks
@first_full_block_number 37
setup do
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
describe "start_link/1" do
test "starts fetching blocks from latest and goes down", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
block_number = 3_416_888
block_quantity = integer_to_quantity(block_number)
EthereumJSONRPC.Mox
|> stub(:json_rpc, fn
# latest block number to seed starting block number for genesis and realtime tasks
%{method: "eth_getBlockByNumber", params: ["latest", false]}, _options ->
{:ok,
%{
"author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
"difficulty" => "0xfffffffffffffffffffffffffffffffe",
"extraData" => "0xd583010a068650617269747986312e32362e32826c69",
"gasLimit" => "0x7a1200",
"gasUsed" => "0x0",
"hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
"number" => block_quantity,
"parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x841240b30d",
"0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01",
"size" => "0x243",
"stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7",
"step" => "306230029",
"timestamp" => "0x5b437f41",
"totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}}
[%{method: "eth_getBlockByNumber", params: [_, true]} | _] = requests, _options ->
{:ok,
Enum.map(requests, fn %{id: id, params: [block_quantity, true]} ->
%{
id: id,
jsonrpc: "2.0",
result: %{
"author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
"difficulty" => "0xfffffffffffffffffffffffffffffffe",
"extraData" => "0xd583010a068650617269747986312e32362e32826c69",
"gasLimit" => "0x7a1200",
"gasUsed" => "0x0",
"hash" =>
Explorer.Factory.block_hash()
|> to_string(),
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
"number" => block_quantity,
"parentHash" =>
Explorer.Factory.block_hash()
|> to_string(),
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x841240b30d",
"0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01",
"size" => "0x243",
"stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7",
"step" => "306230029",
"timestamp" => "0x5b437f41",
"totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
end)}
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
[%{method: "eth_getBalance"} | _] = requests, _options ->
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, jsonrpc: "2.0", result: "0x0"} end)}
end)
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
EthereumJSONRPC.Geth ->
block_number = 5_950_901
block_quantity = integer_to_quantity(block_number)
EthereumJSONRPC.Mox
|> stub(:json_rpc, fn
%{method: "eth_getBlockByNumber", params: ["latest", false]}, _options ->
{:ok,
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: variant
]
"difficulty" => "0xc2550dc5bfc5d",
"extraData" => "0x65746865726d696e652d657538",
"gasLimit" => "0x7a121d",
"gasUsed" => "0x6cc04b",
"hash" => "0x71f484056fec687fd469989426c94c469ff08a28eae9a1865359d64557bb99f6",
"logsBloom" =>
"0x900840000041000850020000002800020800840900200210041006005028810880231200c1a0800001003a00011813005102000020800207080210000020014c00888640001040300c180008000084001000010018010040001118181400a06000280428024010081100015008080814141000644404040a8021101010040001001022000000000880420004008000180004000a01002080890010000a0601001a0000410244421002c0000100920100020004000020c10402004080008000203001000200c4001a000002000c0000000100200410090bc52e080900108230000110010082120200000004e01002000500001009e14001002051000040830080",
"miner" => "0xea674fdde714fd979de3edf0f56aa9716b898ec8",
"mixHash" => "0x555275cd0ab4c3b2fe3936843ee25bb67da05ef7dcf17216bc0e382d21d139a0",
"nonce" => "0xa49e42a024600113",
"number" => block_quantity,
"parentHash" => "0xb4357733c59cc6f785542d072a205f4e195f7198f544ea5e01c1b90ef0f914a5",
"receiptsRoot" => "0x17baf8de366fecc1be494bff245be6357ac60a5fe786099dba89983778c8421e",
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size" => "0x6c7b",
"stateRoot" => "0x79345c692a0bf363e95c37750336c534309b3f3fe8b59712ac1527118070f488",
"timestamp" => "0x5b475377",
"totalDifficulty" => "0x120258e22c69502fc88",
"transactions" => ["0xa4b58d1d1473f4891d9ff91f624dba73611bf1f6e9a60d3ca2dcfc75d2ab185c"],
"transactionsRoot" => "0x5972b7988f667d7e86679322641117e503ea2c1bc5a27822a8a8120fe53f2c8b",
"uncles" => []
}}
[%{method: "eth_getBlockByNumber", params: [_, true]} | _] = requests, _options ->
{:ok,
Enum.map(requests, fn %{id: id, params: [block_quantity, true]} ->
%{
id: id,
jsonrpc: "2.0",
result: %{
"difficulty" => "0xc22479024e55f",
"extraData" => "0x73656f3130",
"gasLimit" => "0x7a121d",
"gasUsed" => "0x7a0527",
"hash" =>
Explorer.Factory.block_hash()
|> to_string(),
"logsBloom" =>
"0x006a044c050a6759208088200009808898246808402123144ac15801c09a2672990130000042500000cc6090b063f195352095a88018194112101a02640000a0109c03c40568440b853a800a60044408604bb49d1d604c802008000884520208496608a520992e0f4b41a94188088920c1995107db4696c03839a911500084001009884100605084c4542953b08101103080254c34c802a00042a62f811340400d22080d000c0e39927ca481800c8024048425462000150850500205a224810041904023a80c00dc01040203000086020111210403081096822008c12500a2060a54834800400851210122c481a04a24b5284e9900a08110c180011001c03100",
"miner" => "0xb2930b35844a230f00e51431acae96fe543a0347",
"mixHash" => "0x5e07a58028d2cee7ddbefe245e6d7b5232d997b66cc906b18ad9ad51535ced24",
"nonce" => "0x3d88ebe8031aadf6",
"number" => block_quantity,
"parentHash" =>
Explorer.Factory.block_hash()
|> to_string(),
"receiptsRoot" => "0x5294a8b56be40c0c198aa443664e801bb926d49878f96151849f3ddd0cb5e76d",
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size" => "0x4796",
"stateRoot" => "0x3755d4b5c9ae3cd58d7a856a46fbe8fb69f0ba93d81e831cd68feb8b61bc3009",
"timestamp" => "0x5b475393",
"totalDifficulty" => "0x120259a450e2527e1e7",
"transactions" => [],
"transactionsRoot" => "0xa71969ed649cd1f21846ab7b4029e79662941cc34cd473aa4590e666920ad2f4",
"uncles" => []
}
}
end)}
[%{method: "eth_getBalance"} | _] = requests, _options ->
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, jsonrpc: "2.0", result: "0x0"} end)}
end)
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
end
describe "start_link/1" do
test "starts fetching blocks from latest and goes down", %{json_rpc_named_arguments: json_rpc_named_arguments} do
{:ok, latest_block_number} = EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments)
default_blocks_batch_size = BlockFetcher.default_blocks_batch_size()
@ -76,7 +223,7 @@ defmodule Indexer.BlockFetcherTest do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
start_supervised!(BlockFetcher)
start_supervised!({BlockFetcher, json_rpc_named_arguments: json_rpc_named_arguments})
wait_for_results(fn ->
Repo.one!(from(block in Block, where: block.number == ^latest_block_number))
@ -159,7 +306,7 @@ defmodule Indexer.BlockFetcherTest do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
{:ok, state} = BlockFetcher.init([])
{:ok, state} = BlockFetcher.init(json_rpc_named_arguments: json_rpc_named_arguments)
%{state: state}
end
@ -168,6 +315,118 @@ defmodule Indexer.BlockFetcherTest do
json_rpc_named_arguments: json_rpc_named_arguments,
state: state
} do
block_number = 0
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
block_quantity = integer_to_quantity(block_number)
miner_hash = "0x0000000000000000000000000000000000000000"
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBlockByNumber", params: [^block_quantity, true]}],
_options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: %{
"author" => "0x0000000000000000000000000000000000000000",
"difficulty" => "0x20000",
"extraData" => "0x",
"gasLimit" => "0x663be0",
"gasUsed" => "0x0",
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => miner_hash,
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => [
"0x80",
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"size" => "0x215",
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3",
"step" => "0",
"timestamp" => "0x0",
"totalDifficulty" => "0x20000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
]}
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"}]}
end)
EthereumJSONRPC.Geth ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBlockByNumber", params: [^block_quantity, true]}],
_options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: %{
"difficulty" => "0x400000000",
"extraData" => "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
"gasLimit" => "0x1388",
"gasUsed" => "0x0",
"hash" => "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => miner_hash,
"mixHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce" => "0x0000000000000042",
"number" => block_quantity,
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size" => "0x21c",
"stateRoot" => "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544",
"timestamp" => "0x0",
"totalDifficulty" => "0x400000000",
"transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
]}
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"}]}
end)
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
end
{:ok, sequence} = Sequence.start_link(first: 0, step: 1)
%{address_hash: address_hash, block_hash: block_hash} =
@ -201,11 +460,11 @@ defmodule Indexer.BlockFetcherTest do
}
variant ->
raise ArgumenrError, "Unsupported variant (#{variant})"
raise ArgumentError, "Unsupported variant (#{variant})"
end
log_bad_gateway(
fn -> BlockFetcher.import_range(0..0, state, sequence) end,
fn -> BlockFetcher.import_range(block_number..block_number, state, sequence) end,
fn result ->
assert {:ok,
%{
@ -229,10 +488,182 @@ defmodule Indexer.BlockFetcherTest do
)
end
# We can't currently index the whole Ethereum Mainnet, so we don't know what is the first full block.
# Implement when a full block is found for Ethereum Mainnet and remove :no_geth tag
@tag :no_geth
test "can import range with all synchronous imported schemas", %{
json_rpc_named_arguments: json_rpc_named_arguments,
state: state
} do
block_number = @first_full_block_number
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
block_quantity = integer_to_quantity(block_number)
from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
to_address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
transaction_hash = "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn json, _options ->
assert [%{id: id, method: "eth_getBlockByNumber", params: [^block_quantity, true]}] = json
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: %{
"author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
"difficulty" => "0xfffffffffffffffffffffffffffffffe",
"extraData" => "0xd5830108048650617269747986312e32322e31826c69",
"gasLimit" => "0x69fe20",
"gasUsed" => "0xc512",
"hash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
"number" => "0x25",
"parentHash" => "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e",
"receiptsRoot" => "0xd300311aab7dcc98c05ac3f1893629b2c9082c189a0a0c76f4f63e292ac419d5",
"sealFields" => [
"0x84120a71de",
"0xb841fcdb570511ec61edda93849bb7c6b3232af60feb2ea74e4035f0143ab66dfdd00f67eb3eda1adddbb6b572db1e0abd39ce00f9b3ccacb9f47973279ff306fe5401"
],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" =>
"fcdb570511ec61edda93849bb7c6b3232af60feb2ea74e4035f0143ab66dfdd00f67eb3eda1adddbb6b572db1e0abd39ce00f9b3ccacb9f47973279ff306fe5401",
"size" => "0x2cf",
"stateRoot" => "0x2cd84079b0d0c267ed387e3895fd1c1dc21ff82717beb1132adac64276886e19",
"step" => "302674398",
"timestamp" => "0x5a343956",
"totalDifficulty" => "0x24ffffffffffffffffffffffffedf78dfd",
"transactions" => [
%{
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"blockNumber" => "0x25",
"chainId" => "0x4d",
"condition" => nil,
"creates" => nil,
"from" => from_address_hash,
"gas" => "0x47b760",
"gasPrice" => "0x174876e800",
"hash" => transaction_hash,
"input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
"nonce" => "0x4",
"publicKey" =>
"0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9",
"r" => "0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01",
"raw" =>
"0xf88a0485174876e8008347b760948bf38d4764929064f2d4d3a56520a76ab3df415b80a410855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef81bea0a7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01a01f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f",
"s" => "0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f",
"standardV" => "0x1",
"to" => to_address_hash,
"transactionIndex" => "0x0",
"v" => "0xbe",
"value" => "0x0"
}
],
"transactionsRoot" => "0x68e314a05495f390f9cd0c36267159522e5450d2adf254a74567b452e767bf34",
"uncles" => []
}
}
]}
end)
|> expect(:json_rpc, fn json, _options ->
assert [
%{
id: id,
method: "eth_getTransactionReceipt",
params: ["0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"]
}
] = json
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: %{
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"blockNumber" => "0x25",
"contractAddress" => nil,
"cumulativeGasUsed" => "0xc512",
"gasUsed" => "0xc512",
"logs" => [
%{
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"blockNumber" => "0x25",
"data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
"logIndex" => "0x0",
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"],
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => "0x0",
"transactionLogIndex" => "0x0",
"type" => "mined"
}
],
"logsBloom" =>
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"root" => nil,
"status" => "0x1",
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
"transactionIndex" => "0x0"
}
}
]}
end)
# 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, 5, fn json, _options ->
[request] = json
case request do
%{id: id, method: "eth_getBalance", params: [^to_address_hash, ^block_quantity]} ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x1"}]}
%{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"]]} ->
{: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
}
}
]}
end
end)
variant ->
raise ArgumentError, "Unsupported variant (#{variant})"
end
end
{:ok, sequence} = Sequence.start_link(first: 0, step: 1)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
@ -372,7 +803,7 @@ defmodule Indexer.BlockFetcherTest do
57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
}
]
}} = BlockFetcher.import_range(@first_full_block_number..@first_full_block_number, state, sequence)
}} = BlockFetcher.import_range(block_number..block_number, state, sequence)
wait_for_tasks(InternalTransactionFetcher)
wait_for_tasks(AddressBalanceFetcher)
@ -385,12 +816,12 @@ defmodule Indexer.BlockFetcherTest do
first_address = Repo.get!(Address, first_address_hash)
assert first_address.fetched_balance == %Wei{value: Decimal.new(1)}
assert first_address.fetched_balance_block_number == @first_full_block_number
assert first_address.fetched_balance_block_number == block_number
second_address = Repo.get!(Address, second_address_hash)
assert second_address.fetched_balance == %Wei{value: Decimal.new(252_460_837_000_000_000_000_000_000)}
assert second_address.fetched_balance_block_number == @first_full_block_number
assert second_address.fetched_balance_block_number == block_number
variant ->
raise ArgumentError, "Unsupport variant (#{variant})"
@ -423,8 +854,8 @@ defmodule Indexer.BlockFetcherTest do
return
end
defp state(_) do
{:ok, state} = BlockFetcher.init([])
defp state(%{json_rpc_named_arguments: json_rpc_named_arguments}) do
{:ok, state} = BlockFetcher.init(json_rpc_named_arguments: json_rpc_named_arguments)
%{state: state}
end

@ -1,54 +1,74 @@
defmodule Indexer.InternalTransactionFetcherTest do
use Explorer.DataCase, async: false
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
import ExUnit.CaptureLog
import Mox
alias Indexer.{AddressBalanceFetcherCase, InternalTransactionFetcher}
alias Indexer.{AddressBalanceFetcherCase, InternalTransactionFetcher, PendingTransactionFetcher}
@moduletag :capture_log
# MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow`
# it to use expectations and stubs from test's pid.
setup :set_mox_global
setup do
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
setup :verify_on_exit!
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
@moduletag [capture_log: true, no_geth: true]
test "does not try to fetch pending transactions from Indexer.PendingTransactionFetcher", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn _json, _options ->
{:ok,
[
%{
"blockHash" => nil,
"blockNumber" => nil,
"chainId" => "0x4d",
"condition" => nil,
"creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4",
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
"gas" => "0x47b760",
"gasPrice" => "0x174876e800",
"hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
"input" =>
"0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
"nonce" => "0x0",
"publicKey" =>
"0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9",
"r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75",
"raw" =>
"0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3",
"s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3",
"standardV" => "0x0",
"to" => nil,
"transactionIndex" => nil,
"v" => "0xbd",
"value" => "0x0"
}
]}
end)
|> stub(:json_rpc, fn _json, _options ->
{:ok, []}
end)
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: variant
]
}
end
@tag :no_geth
test "does not try to fetch pending transactions from Indexer.PendingTransactionFetcher", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
start_supervised!({Indexer.PendingTransactionFetcher, json_rpc_named_arguments: json_rpc_named_arguments})
start_supervised!({PendingTransactionFetcher, json_rpc_named_arguments: json_rpc_named_arguments})
wait_for_results(fn ->
Repo.one!(from(transaction in Explorer.Chain.Transaction, where: is_nil(transaction.block_hash), limit: 1))
end)
:transaction
|> insert(hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6")
|> with_block()
hash_strings =
InternalTransactionFetcher.init([], fn hash_string, acc -> [hash_string | acc] end, json_rpc_named_arguments)
@ -102,6 +122,12 @@ defmodule Indexer.InternalTransactionFetcherTest do
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
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
@ -127,8 +153,13 @@ defmodule Indexer.InternalTransactionFetcherTest do
"""
end
@tag :no_geth
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
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)

@ -1,39 +1,60 @@
defmodule Indexer.PendingTransactionFetcherTest do
# `async: false` due to use of named GenServer
use Explorer.DataCase, async: false
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
setup do
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
import Mox
"parity" ->
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
alias Explorer.Chain.Transaction
alias Indexer.PendingTransactionFetcher
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
# MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow`
# it to use expectations and stubs from test's pid.
setup :set_mox_global
%{
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: url,
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: variant
]
}
end
setup :verify_on_exit!
@moduletag [capture_log: true, no_geth: true]
describe "start_link/1" do
@tag :no_geth
# this test may fail if Sokol so low volume that the pending transactions are empty for too long
test "starts fetching pending transactions", %{json_rpc_named_arguments: json_rpc_named_arguments} do
alias Explorer.Chain.Transaction
alias Indexer.PendingTransactionFetcher
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn _json, _options ->
{:ok,
[
%{
"blockHash" => nil,
"blockNumber" => nil,
"chainId" => "0x4d",
"condition" => nil,
"creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4",
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
"gas" => "0x47b760",
"gasPrice" => "0x174876e800",
"hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
"input" =>
"0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
"nonce" => "0x0",
"publicKey" =>
"0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9",
"r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75",
"raw" =>
"0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3",
"s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3",
"standardV" => "0x0",
"to" => nil,
"transactionIndex" => nil,
"v" => "0xbd",
"value" => "0x0"
}
]}
end)
|> stub(:json_rpc, fn _json, _options ->
{:ok, []}
end)
end
assert Repo.aggregate(Transaction, :count, :hash) == 0

@ -14,6 +14,8 @@ end
# no declared in :applications since it is test-only
{:ok, _} = Application.ensure_all_started(:ex_machina)
Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start()

Loading…
Cancel
Save