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. 191
      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. 204
      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. 6
      apps/indexer/config/dev/geth.exs
  16. 6
      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. 503
      apps/indexer/test/indexer/block_fetcher_test.exs
  24. 97
      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 name: Scan explorer_web for vulnerabilities
command: mix sobelow --config command: mix sobelow --config
working_directory: "apps/explorer_web" working_directory: "apps/explorer_web"
test_geth: test_geth_http:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.6.5-node-browsers - image: circleci/elixir:1.6.5-node-browsers
@ -306,7 +306,10 @@ jobs:
PGPASSWORD: postgres PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below # match POSTGRES_USER for postgres image below
PGUSER: postgres 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 - image: circleci/postgres:10.3-alpine
environment: environment:
# Match apps/explorer/config/test.exs config :explorerer, Explorer.Repo, database # Match apps/explorer/config/test.exs config :explorerer, Explorer.Repo, database
@ -333,7 +336,7 @@ jobs:
- store_test_results: - store_test_results:
path: _build/test/junit path: _build/test/junit
test_parity: test_geth_mox:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.6.5-node-browsers - image: circleci/elixir:1.6.5-node-browsers
@ -343,7 +346,8 @@ jobs:
PGPASSWORD: postgres PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below # match POSTGRES_USER for postgres image below
PGUSER: postgres PGUSER: postgres
ETHEREUM_JSONRPC_VARIANT: parity ETHEREUM_JSONRPC_VARIANT: "EthereumJSONRPC.Geth"
ETHEREUM_JSONRPC_TRANSPORT: "EthereumJSONRPC.Mox"
- image: circleci/postgres:10.3-alpine - image: circleci/postgres:10.3-alpine
environment: environment:
# Match apps/explorer/config/test.exs config :explorerer, Explorer.Repo, database # Match apps/explorer/config/test.exs config :explorerer, Explorer.Repo, database
@ -366,7 +370,86 @@ jobs:
name: Wait for DB name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m 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: - store_test_results:
path: _build/test/junit path: _build/test/junit
@ -394,8 +477,10 @@ workflows:
- eslint - eslint
- jest - jest
- sobelow - sobelow
- test_parity - test_parity_http
- test_geth - test_parity_mox
- test_geth_http
- test_geth_mox
- dialyzer: - dialyzer:
requires: requires:
- build - build
@ -411,9 +496,15 @@ workflows:
- sobelow: - sobelow:
requires: requires:
- build - build
- test_parity: - test_parity_http:
requires:
- build
- test_parity_mox:
requires:
- build
- test_geth_http:
requires: requires:
- build - build
- test_geth: - test_geth_mox:
requires: requires:
- build - 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 tracing nodes. The `:http` option is passed directly to the HTTP
library (`HTTPoison`), which forwards the options down to `:hackney`. 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 ## Installation
The OTP application `:ethereum_jsonrpc` can be used in other umbrella The OTP application `:ethereum_jsonrpc` can be used in other umbrella

@ -69,6 +69,8 @@ defmodule EthereumJsonrpc.MixProject do
{:httpoison, "~> 1.0", override: true}, {:httpoison, "~> 1.0", override: true},
# Decode/Encode JSON for JSONRPC # Decode/Encode JSON for JSONRPC
{:jason, "~> 1.0"}, {: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 # Convert unix timestamps in JSONRPC to DateTimes
{:timex, "~> 3.1.24"} {:timex, "~> 3.1.24"}
] ]

@ -1,99 +1,130 @@
defmodule EthereumJSONRPCTest do defmodule EthereumJSONRPCTest do
use ExUnit.Case, async: true use EthereumJSONRPC.Case, async: true
import EthereumJSONRPC.Case import EthereumJSONRPC.Case
import Mox
@moduletag :capture_log setup :verify_on_exit!
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 -> @moduletag :capture_log
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_balances/1" do describe "fetch_balances/1" do
test "with all valid hash_data returns {:ok, addresses_params}", %{ test "with all valid hash_data returns {:ok, addresses_params}", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } do
assert {:ok, expected_fetched_balance =
[ case Keyword.fetch!(json_rpc_named_arguments, :variant) do
%{ EthereumJSONRPC.Geth -> 0
fetched_balance: fetched_balance, EthereumJSONRPC.Parity -> 1
fetched_balance_block_number: 1, variant -> raise ArgumentError, "Unsupported variant (#{variant}})"
hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" end
}
]} =
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 -> if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
raise ArgumentError, "Unsupported variant (#{variant}})" expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, [%{id: 0, result: EthereumJSONRPC.integer_to_quantity(expected_fetched_balance)}]}
end)
end end
hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
assert EthereumJSONRPC.fetch_balances(
[
%{block_quantity: "0x1", hash_data: hash}
],
json_rpc_named_arguments
) ==
{:ok,
[
%{
fetched_balance: expected_fetched_balance,
fetched_balance_block_number: 1,
hash: hash
}
]}
end end
test "with all invalid hash_data returns {:error, reasons}", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "with all invalid hash_data returns {:error, reasons}", %{json_rpc_named_arguments: json_rpc_named_arguments} do
assert {:error, reasons} = variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
EthereumJSONRPC.fetch_balances([%{block_quantity: "0x1", hash_data: "0x0"}], json_rpc_named_arguments)
assert is_list(reasons)
assert length(reasons) == 1
[reason] = reasons expected_message =
case variant do
EthereumJSONRPC.Geth ->
"invalid argument 0: json: cannot unmarshal hex string of odd length into Go value of type common.Address"
assert %{ EthereumJSONRPC.Parity ->
code: -32602, "Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
data: %{"blockNumber" => "0x1", "hash" => "0x0"},
message: message
} = reason
case Keyword.fetch!(json_rpc_named_arguments, :variant) do _ ->
EthereumJSONRPC.Geth -> raise ArgumentError, "Unsupported variant (#{variant}})"
assert message == end
"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 -> if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
raise ArgumentError, "Unsupported variant (#{variant}})" expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
error: %{
code: -32602,
message: expected_message
}
}
]}
end)
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 end
test "with a mix of valid and invalid hash_data returns {:error, reasons}", %{ test "with a mix of valid and invalid hash_data returns {:error, reasons}", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } 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} = assert {:error, reasons} =
EthereumJSONRPC.fetch_balances( EthereumJSONRPC.fetch_balances(
[ [
@ -134,6 +165,12 @@ defmodule EthereumJSONRPCTest do
describe "fetch_block_number_by_tag" do describe "fetch_block_number_by_tag" do
@tag capture_log: false @tag capture_log: false
test "with earliest", %{json_rpc_named_arguments: json_rpc_named_arguments} do 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( log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("earliest", json_rpc_named_arguments) end, fn -> EthereumJSONRPC.fetch_block_number_by_tag("earliest", json_rpc_named_arguments) end,
fn result -> fn result ->
@ -144,6 +181,12 @@ defmodule EthereumJSONRPCTest do
@tag capture_log: false @tag capture_log: false
test "with latest", %{json_rpc_named_arguments: json_rpc_named_arguments} do 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( log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) end, fn -> EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) end,
fn result -> fn result ->
@ -155,6 +198,12 @@ defmodule EthereumJSONRPCTest do
@tag capture_log: false @tag capture_log: false
test "with pending", %{json_rpc_named_arguments: json_rpc_named_arguments} do 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( log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) end, fn -> EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) end,
fn result -> fn result ->

@ -1,36 +1,10 @@
defmodule EthereumJSONRPC.GethTest do defmodule EthereumJSONRPC.GethTest do
use ExUnit.Case, async: false use EthereumJSONRPC.Case, async: false
alias EthereumJSONRPC.Geth alias EthereumJSONRPC.Geth
@moduletag :no_parity @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 describe "fetch_internal_transactions/2" do
test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "is not supported", %{json_rpc_named_arguments: json_rpc_named_arguments} do
Geth.fetch_internal_transactions( 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 defmodule EthereumJSONRPC.ParityTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
use EthereumJSONRPC.Case
@moduletag :no_geth import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Mox
setup do setup :verify_on_exit!
%{
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
doctest EthereumJSONRPC.Parity doctest EthereumJSONRPC.Parity
@moduletag :no_geth
describe "fetch_internal_transactions/1" do describe "fetch_internal_transactions/1" do
test "with all valid transaction_params returns {:ok, transactions_params}", %{ test "with all valid transaction_params returns {:ok, transactions_params}", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } 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( assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[ [
%{ %{
block_number: 1, block_number: block_number,
hash_data: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1" hash_data: transaction_hash
} }
], ],
json_rpc_named_arguments json_rpc_named_arguments
@ -35,19 +80,17 @@ defmodule EthereumJSONRPC.ParityTest do
[ [
%{ %{
block_number: 1, block_number: 1,
created_contract_address_hash: "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461", created_contract_address_hash: created_contract_address_hash,
created_contract_code: created_contract_code: created_contract_code,
"0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029", from_address_hash: from_address_hash,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", gas: gas,
gas: 4_533_872, gas_used: gas_used,
gas_used: 382_953, index: index,
index: 0, init: init,
init: trace_address: trace_address,
"0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", transaction_hash: transaction_hash,
trace_address: [], type: type,
transaction_hash: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1", value: value
type: "create",
value: 0
} }
] ]
} }
@ -56,6 +99,21 @@ defmodule EthereumJSONRPC.ParityTest do
test "with all invalid transaction_params returns {:error, reasons}", %{ test "with all invalid transaction_params returns {:error, reasons}", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } 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( assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[ [
%{ %{
@ -68,12 +126,12 @@ defmodule EthereumJSONRPC.ParityTest do
{:error, {:error,
[ [
%{ %{
:code => -32603, code: -32603,
:data => %{ data: %{
"blockNumber" => 1, "blockNumber" => 1,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001" "transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001"
}, },
:message => message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." "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}", %{ test "with a mix of valid and invalid transaction_params returns {:error, reasons}", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } 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( assert EthereumJSONRPC.Parity.fetch_internal_transactions(
[ [
# start with :ok # start with :ok
@ -99,11 +191,6 @@ defmodule EthereumJSONRPC.ParityTest do
block_number: 1, block_number: 1,
hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001" hash_data: "0x0000000000000000000000000000000000000000000000000000000000000001"
}, },
# :error, :ok clause
%{
block_number: 35,
hash_data: "0x6b80a90c958fb5791a070929379ed6eb7a33ecdf9f9cafcada2f6803b3f25ec3"
},
# :error, :error clause # :error, :error clause
%{ %{
block_number: 2, block_number: 2,
@ -115,30 +202,21 @@ defmodule EthereumJSONRPC.ParityTest do
{:error, {:error,
[ [
%{ %{
:code => -32603, code: -32603,
:data => %{ data: %{
"blockNumber" => 1, "blockNumber" => 1,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001" "transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000001"
}, },
:message => 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 =>
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
}, },
%{ %{
:code => -32603, code: -32603,
:data => %{ data: %{
"blockNumber" => 2, "blockNumber" => 2,
"transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000002" "transactionHash" => "0x0000000000000000000000000000000000000000000000000000000000000002"
}, },
:message => message:
"Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug."
} }
]} ]}

@ -1,102 +1,134 @@
defmodule EthereumJSONRPC.ReceiptsTest do defmodule EthereumJSONRPC.ReceiptsTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
use EthereumJSONRPC.Case
alias EthereumJSONRPC.Receipts import EthereumJSONRPC, only: [integer_to_quantity: 1]
import Mox
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 -> alias EthereumJSONRPC.Receipts
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
%{ setup :verify_on_exit!
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
doctest Receipts doctest Receipts
describe "fetch/2" do describe "fetch/2" do
test "with receipts and logs", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "with receipts and logs", %{json_rpc_named_arguments: json_rpc_named_arguments} do
case Keyword.fetch!(json_rpc_named_arguments, :variant) do %{
EthereumJSONRPC.Geth -> cumulative_gas_used: cumulative_gas_used,
assert {:ok, gas_used: gas_used,
%{ address_hash: address_hash,
logs: [], block_number: block_number,
receipts: [ data: data,
%{ index: index,
cumulative_gas_used: 1_238_877, first_topic: first_topic,
gas_used: 21000, status: status,
status: :ok, type: type,
transaction_hash: "0x360fb62cc817093e5624468735803ea39cad719e5c68ca322bae6ba4f520756f", transaction_hash: transaction_hash,
transaction_index: 57 transaction_index: transaction_index
} } =
] case Keyword.fetch!(json_rpc_named_arguments, :variant) do
}} = EthereumJSONRPC.Geth ->
Receipts.fetch( %{
[ cumulative_gas_used: 884_322,
%{ address_hash: "0x1e2fbe6be9eb39fc894d38be976111f332172d83",
gas: 90000, block_number: 3_560_000,
hash: "0x360fb62cc817093e5624468735803ea39cad719e5c68ca322bae6ba4f520756f" data:
} "0x00000000000000000000000033066f6a8adf2d4f5db193524b6fbae062ec0d110000000000000000000000000000000000000000000000000000000000001030",
], index: 12,
json_rpc_named_arguments first_topic: "0xf6db2bace4ac8277384553ad9603d045220a91fb2448ab6130d7a6f044f9a8cf",
) gas_used: 106_025,
status: :error,
type: nil,
transaction_hash: "0xd3efddbbeb6ad8d8bb3f6b8c8fb6165567e9dd868013146bdbeb60953c82822a",
transaction_index: 17
}
EthereumJSONRPC.Parity -> EthereumJSONRPC.Parity ->
assert {:ok, %{
%{ cumulative_gas_used: 50450,
logs: [ gas_used: 50450,
%{ address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", block_number: 37,
data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", index: 0,
fourth_topic: nil, first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
index: 0, status: :ok,
second_topic: nil, type: "mined",
third_topic: nil, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", transaction_index: 0
type: "mined" }
} end
],
receipts: [ if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
%{ native_status =
cumulative_gas_used: 50450, case status do
gas_used: 50450, :ok -> "0x1"
status: :ok, :error -> "0x0"
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", end
transaction_index: 0
}
]
}} =
Receipts.fetch(
[
%{
gas: 50451,
hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"
}
],
json_rpc_named_arguments
)
variant -> expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
raise ArgumentError, "Unsupported variant (#{variant})" {:ok,
[
%{
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
}
],
"status" => native_status,
"transactionHash" => transaction_hash,
"transactionIndex" => integer_to_quantity(transaction_index)
}
}
]}
end)
end end
assert {:ok,
%{
logs: [
%{
address_hash: ^address_hash,
block_number: ^block_number,
data: ^data,
first_topic: ^first_topic,
fourth_topic: nil,
index: ^index,
second_topic: nil,
third_topic: nil,
transaction_hash: ^transaction_hash
}
| _
],
receipts: [
%{
cumulative_gas_used: ^cumulative_gas_used,
gas_used: ^gas_used,
status: ^status,
transaction_hash: ^transaction_hash,
transaction_index: ^transaction_index
}
]
}} =
Receipts.fetch(
[
%{
gas: 9000,
hash: transaction_hash
}
],
json_rpc_named_arguments
)
end end
end end
end end

@ -1,10 +1,79 @@
defmodule EthereumJSONRPC.Case do 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 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 def log_bad_gateway(under_test, assertions) do
case under_test.() do case under_test.() do
{:error, {:bad_gateway, url}} -> Logger.error(fn -> ["Bad Gateway to ", url, ". Check CloudFlare."] end) {:error, {:bad_gateway, url}} -> Logger.error(fn -> ["Bad Gateway to ", url, ". Check CloudFlare."] end)
other -> assertions.(other) other -> assertions.(other)
end end
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 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 # Counter `test --no-start`. `--no-start` is needed for `:indexer` compatibility
{:ok, _} = Application.ensure_all_started(:ethereum_jsonrpc) {: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.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start() ExUnit.start()

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

@ -6,8 +6,6 @@ config :indexer,
debug_logs: !!System.get_env("DEBUG_INDEXER"), debug_logs: !!System.get_env("DEBUG_INDEXER"),
ecto_repos: [Explorer.Repo] ecto_repos: [Explorer.Repo]
variant = System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" # Import environment specific config. This must remain at the bottom
# Import variant specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # 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, http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY", url: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY",
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
] ],
], variant: EthereumJSONRPC.Geth
variant: EthereumJSONRPC.Geth ]

@ -12,6 +12,6 @@ config :indexer,
trace_replayTransaction: "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]] http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
] ],
], variant: EthereumJSONRPC.Parity
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` # JSONRPC access to Parity for `Explorer.Indexer`
{:ethereum_jsonrpc, in_umbrella: true}, {:ethereum_jsonrpc, in_umbrella: true},
# Importing to database # 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 end

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

@ -1,8 +1,11 @@
defmodule Indexer.BlockFetcherTest do defmodule Indexer.BlockFetcherTest do
# `async: false` due to use of named GenServer # `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 ExUnit.CaptureLog
import Mox
import EthereumJSONRPC, only: [integer_to_quantity: 1]
import EthereumJSONRPC.Case import EthereumJSONRPC.Case
alias Explorer.Chain.{Address, Block, Log, Transaction, Wei} alias Explorer.Chain.{Address, Block, Log, Transaction, Wei}
@ -17,7 +20,13 @@ defmodule Indexer.BlockFetcherTest do
Sequence 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 # First block with all schemas to import
# 37 is determined using the following query: # 37 is determined using the following query:
@ -37,34 +46,172 @@ defmodule Indexer.BlockFetcherTest do
# ON blocks.hash = transactions.block_hash) as blocks # ON blocks.hash = transactions.block_hash) as blocks
@first_full_block_number 37 @first_full_block_number 37
setup do describe "start_link/1" do
{variant, url} = test "starts fetching blocks from latest and goes down", %{json_rpc_named_arguments: json_rpc_named_arguments} do
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
"geth" -> case Keyword.fetch!(json_rpc_named_arguments, :variant) do
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"} EthereumJSONRPC.Parity ->
block_number = 3_416_888
"parity" -> block_quantity = integer_to_quantity(block_number)
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
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)}
[%{method: "eth_getBalance"} | _] = requests, _options ->
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, jsonrpc: "2.0", result: "0x0"} end)}
end)
variant_name -> EthereumJSONRPC.Geth ->
raise ArgumentError, "Unsupported variant name (#{variant_name})" 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,
%{
"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 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 "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) {:ok, latest_block_number} = EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments)
default_blocks_batch_size = BlockFetcher.default_blocks_batch_size() default_blocks_batch_size = BlockFetcher.default_blocks_batch_size()
@ -76,7 +223,7 @@ defmodule Indexer.BlockFetcherTest do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.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 -> wait_for_results(fn ->
Repo.one!(from(block in Block, where: block.number == ^latest_block_number)) 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}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.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} %{state: state}
end end
@ -168,6 +315,118 @@ defmodule Indexer.BlockFetcherTest do
json_rpc_named_arguments: json_rpc_named_arguments, json_rpc_named_arguments: json_rpc_named_arguments,
state: state state: state
} do } 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) {:ok, sequence} = Sequence.start_link(first: 0, step: 1)
%{address_hash: address_hash, block_hash: block_hash} = %{address_hash: address_hash, block_hash: block_hash} =
@ -201,11 +460,11 @@ defmodule Indexer.BlockFetcherTest do
} }
variant -> variant ->
raise ArgumenrError, "Unsupported variant (#{variant})" raise ArgumentError, "Unsupported variant (#{variant})"
end end
log_bad_gateway( 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 -> fn result ->
assert {:ok, assert {:ok,
%{ %{
@ -229,10 +488,182 @@ defmodule Indexer.BlockFetcherTest do
) )
end 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", %{ test "can import range with all synchronous imported schemas", %{
json_rpc_named_arguments: json_rpc_named_arguments, json_rpc_named_arguments: json_rpc_named_arguments,
state: state state: state
} do } 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) {:ok, sequence} = Sequence.start_link(first: 0, step: 1)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do 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>> 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(InternalTransactionFetcher)
wait_for_tasks(AddressBalanceFetcher) wait_for_tasks(AddressBalanceFetcher)
@ -385,12 +816,12 @@ defmodule Indexer.BlockFetcherTest do
first_address = Repo.get!(Address, first_address_hash) first_address = Repo.get!(Address, first_address_hash)
assert first_address.fetched_balance == %Wei{value: Decimal.new(1)} 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) 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 == %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 -> variant ->
raise ArgumentError, "Unsupport variant (#{variant})" raise ArgumentError, "Unsupport variant (#{variant})"
@ -423,8 +854,8 @@ defmodule Indexer.BlockFetcherTest do
return return
end end
defp state(_) do defp state(%{json_rpc_named_arguments: json_rpc_named_arguments}) do
{:ok, state} = BlockFetcher.init([]) {:ok, state} = BlockFetcher.init(json_rpc_named_arguments: json_rpc_named_arguments)
%{state: state} %{state: state}
end end

@ -1,54 +1,74 @@
defmodule Indexer.InternalTransactionFetcherTest do defmodule Indexer.InternalTransactionFetcherTest do
use Explorer.DataCase, async: false use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
import ExUnit.CaptureLog 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 setup :verify_on_exit!
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
"parity" -> @moduletag [capture_log: true, no_geth: true]
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"}
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 -> variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})" raise ArgumentError, "Unsupported variant name (#{variant_name})"
end end
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}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) 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 -> wait_for_results(fn ->
Repo.one!(from(transaction in Explorer.Chain.Transaction, where: is_nil(transaction.block_hash), limit: 1)) Repo.one!(from(transaction in Explorer.Chain.Transaction, where: is_nil(transaction.block_hash), limit: 1))
end) end)
:transaction
|> insert(hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6")
|> with_block()
hash_strings = hash_strings =
InternalTransactionFetcher.init([], fn hash_string, acc -> [hash_string | acc] end, json_rpc_named_arguments) 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 describe "run/2" do
test "duplicate transaction hashes are logged", %{json_rpc_named_arguments: json_rpc_named_arguments} 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}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
@ -127,8 +153,13 @@ defmodule Indexer.InternalTransactionFetcherTest do
""" """
end end
@tag :no_geth
test "duplicate transaction hashes only retry uniques", %{json_rpc_named_arguments: json_rpc_named_arguments} do 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}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)

@ -1,39 +1,60 @@
defmodule Indexer.PendingTransactionFetcherTest do defmodule Indexer.PendingTransactionFetcherTest do
# `async: false` due to use of named GenServer # `async: false` due to use of named GenServer
use Explorer.DataCase, async: false use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
setup do import Mox
{variant, url} =
case System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity" do
"geth" ->
{EthereumJSONRPC.Geth, "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY"}
"parity" -> alias Explorer.Chain.Transaction
{EthereumJSONRPC.Parity, "https://sokol-trace.poa.network"} alias Indexer.PendingTransactionFetcher
variant_name -> # MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow`
raise ArgumentError, "Unsupported variant name (#{variant_name})" # it to use expectations and stubs from test's pid.
end setup :set_mox_global
%{ setup :verify_on_exit!
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP, @moduletag [capture_log: true, no_geth: true]
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 "start_link/1" do 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 # 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 test "starts fetching pending transactions", %{json_rpc_named_arguments: json_rpc_named_arguments} do
alias Explorer.Chain.Transaction if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
alias Indexer.PendingTransactionFetcher 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 assert Repo.aggregate(Transaction, :count, :hash) == 0

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

Loading…
Cancel
Save