Explorer.JSONRPC -> EthereumJSONRPC

Extract Explorer.JSONRPC to its own OTP application,
apps/ethereum_jsonrpc.  It is not ready to be a completely separate
library because it knowns the internal params format used for
Explorer.Chain.
pull/162/head
Luke Imhoff 7 years ago
parent 75388c8ece
commit 11bcecad96
  1. 33
      apps/ethereum_jsonrpc/README.md
  2. 30
      apps/ethereum_jsonrpc/config/config.exs
  3. 301
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  4. 16
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/application.ex
  5. 60
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
  6. 12
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex
  7. 20
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
  8. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/logs.ex
  9. 8
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
  10. 22
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex
  11. 12
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex
  12. 10
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex
  13. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex
  14. 46
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
  15. 12
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex
  16. 63
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  17. 10
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
  18. 44
      apps/ethereum_jsonrpc/mix.exs
  19. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs
  20. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/blocks_test.exs
  21. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/log_test.exs
  22. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs
  23. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs
  24. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs
  25. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs
  26. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs
  27. 6
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
  28. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs
  29. 5
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transactions_test.exs
  30. 8
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
  31. 6
      apps/ethereum_jsonrpc/test/test_helper.exs
  32. 5
      apps/explorer/config/config.exs
  33. 1
      apps/explorer/lib/explorer/application.ex
  34. 11
      apps/explorer/lib/explorer/indexer/address_fetcher.ex
  35. 11
      apps/explorer/lib/explorer/indexer/block_fetcher.ex
  36. 309
      apps/explorer/lib/explorer/jsonrpc.ex
  37. 2
      apps/explorer/mix.exs
  38. 2
      apps/explorer/test/explorer/indexer/address_fetcher_test.exs
  39. 3
      apps/explorer/test/explorer/indexer/block_fetcher_test.exs
  40. 5
      apps/explorer/test/explorer/jsonrpc/block_test.exs
  41. 5
      apps/explorer/test/explorer/jsonrpc/blocks_test.exs
  42. 5
      apps/explorer/test/explorer/jsonrpc/log_test.exs
  43. 5
      apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs
  44. 5
      apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs
  45. 5
      apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs
  46. 5
      apps/explorer/test/explorer/jsonrpc/parity_test.exs
  47. 5
      apps/explorer/test/explorer/jsonrpc/receipt_test.exs
  48. 5
      apps/explorer/test/explorer/jsonrpc/transaction_test.exs
  49. 5
      apps/explorer/test/explorer/jsonrpc/transactions_test.exs
  50. 2
      coveralls.json

@ -1,21 +1,36 @@
# EthereumJsonrpc # EthereumJSONRPC
**TODO: Add description** Ethereum JSONRPC client.
## Configuration
Configuration for parity URLs can be provided with the following mix
config:
```elixir
config :ethereum_jsonrpc,
url: "https://sokol.poa.network",
trace_url: "https://sokol-trace.poa.network",
http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
```
Note: the tracing node URL is provided separately from `:url`,
via `:trace_url`. The trace URL and is used for
`fetch_internal_transactions`, which is only a supported method on
tracing nodes. The `:http` option is passed directly to the HTTP
library (`HTTPoison`), which forwards the options down to `:hackney`.
## Installation ## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed The OTP application `:ethereum_jsonrpc` can be used in other umbrella
by adding `ethereum_jsonrpc` to your list of dependencies in `mix.exs`: OTP applications by adding `ethereum_jsonrpc` to your list of
dependencies in `mix.exs`:
```elixir ```elixir
def deps do def deps do
[ [
{:ethereum_jsonrpc, "~> 0.1.0"} {:ethereum_jsonrpc, in_umbrella: true}
] ]
end end
``` ```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/ethereum_jsonrpc](https://hexdocs.pm/ethereum_jsonrpc).

@ -2,29 +2,7 @@
# and its dependencies with the aid of the Mix.Config module. # and its dependencies with the aid of the Mix.Config module.
use Mix.Config use Mix.Config
# This configuration is loaded before any dependency and is restricted config :ethereum_jsonrpc,
# to this project. If another project depends on this project, this http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]],
# file won't be loaded nor affect the parent project. For this reason, trace_url: "https://sokol-trace.poa.network",
# if you want to provide default values for your application for url: "https://sokol.poa.network"
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure your application as:
#
# config :ethereum_jsonrpc, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:ethereum_jsonrpc, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"

@ -1,18 +1,303 @@
defmodule EthereumJsonrpc do defmodule EthereumJSONRPC do
@moduledoc """ @moduledoc """
Documentation for EthereumJsonrpc. Ethereum JSONRPC client.
## Configuration
Configuration for parity URLs can be provided with the following mix config:
config :ethereum_jsonrpc,
url: "https://sokol.poa.network",
trace_url: "https://sokol-trace.poa.network",
http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
Note: the tracing node URL is provided separately from `:url`, via `:trace_url`. The trace URL and is used for
`fetch_internal_transactions`, which is only a supported method on tracing nodes. The `:http` option is passed
directly to the HTTP library (`HTTPoison`), which forwards the options down to `:hackney`.
"""
require Logger
alias EthereumJSONRPC.{Blocks, Parity, Receipts, Transactions}
@typedoc """
Truncated 20-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a
`String.t`.
"""
@type address :: String.t()
@typedoc """
Binary data encoded as a single hexadecimal number in a `String.t`
"""
@type data :: String.t()
@typedoc """
A full 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a `String.t`
## Example
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
"""
@type hash :: String.t()
@typedoc """
8 byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash of the proof-of-work.
"""
@type nonce :: String.t()
@typedoc """
A number encoded as a hexadecimal number in a `String.t`
## Example
"0x1b4"
"""
@type quantity :: String.t()
@typedoc """
Unix timestamp encoded as a hexadecimal number in a `String.t`
"""
@type timestamp :: String.t()
@doc """
Lists changes for a given filter subscription.
"""
def check_for_updates(filter_id) do
request = %{
"id" => filter_id,
"jsonrpc" => "2.0",
"method" => "eth_getFilterChanges",
"params" => [filter_id]
}
json_rpc(request, config(:url))
end
@doc """
Fetches configuration for this module under `key`
Configuration can be set a compile time using `config`
config :ethereume_jsonrpc, key, value
Configuration can be set a runtime using `Application.put_env/3`
Application.put_env(:ethereume_jsonrpc, key, value)
"""
def config(key) do
Application.fetch_env!(:ethereum_jsonrpc, key)
end
@doc """
Fetches address balances by address hashes.
"""
def fetch_balances_by_hash(address_hashes) do
batched_requests =
for hash <- address_hashes do
%{
"id" => hash,
"jsonrpc" => "2.0",
"method" => "eth_getBalance",
"params" => [hash, "latest"]
}
end
batched_requests
|> json_rpc(config(:url))
|> handle_balances()
end
defp handle_balances({:ok, results}) do
native_results =
for response <- results, into: %{} do
{response["id"], hexadecimal_to_integer(response["result"])}
end
{:ok, native_results}
end
defp handle_balances({:error, _reason} = err), do: err
@doc """
Fetches blocks by block hashes.
Transaction data is included for each block.
"""
def fetch_blocks_by_hash(block_hashes) do
batched_requests =
for block_hash <- block_hashes do
%{
"id" => block_hash,
"jsonrpc" => "2.0",
"method" => "eth_getBlockByHash",
"params" => [block_hash, true]
}
end
batched_requests
|> json_rpc(config(:url))
|> handle_get_block_by_number()
|> case do
{:ok, _next, results} -> {:ok, results}
{:error, reason} -> {:error, reason}
end
end
@doc """
Fetches blocks by block number range.
"""
def fetch_blocks_by_range(block_start, block_end) do
block_start
|> build_batch_get_block_by_number(block_end)
|> json_rpc(config(:url))
|> handle_get_block_by_number()
end
@doc """
Fetches internal transactions from client-specific API.
"""
def fetch_internal_transactions(hashes) when is_list(hashes) do
Parity.fetch_internal_transactions(hashes)
end
def fetch_transaction_receipts(hashes) when is_list(hashes) do
Receipts.fetch(hashes)
end
@doc """
1. POSTs JSON `payload` to `url`
2. Decodes the response
3. Handles the response
## Returns
* Handled response
* `{:error, reason}` if POST failes
""" """
def json_rpc(payload, url) do
json = encode_json(payload)
headers = [{"Content-Type", "application/json"}]
case HTTPoison.post(url, json, headers, config(:http)) do
{:ok, %HTTPoison.Response{body: body, status_code: code}} ->
body |> decode_json(payload, url) |> handle_response(code)
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end
@doc """ @doc """
Hello world. Creates a filter subscription that can be polled for retreiving new blocks.
"""
def listen_for_new_blocks do
id = DateTime.utc_now() |> DateTime.to_unix()
## Examples request = %{
"id" => id,
"jsonrpc" => "2.0",
"method" => "eth_newBlockFilter",
"params" => []
}
iex> EthereumJsonrpc.hello json_rpc(request, config(:url))
:world end
@doc """
Converts `t:nonce/0` to `t:non_neg_integer/0`
"""
def nonce_to_integer(nonce) do
hexadecimal_to_integer(nonce)
end
@doc """
Converts `t:quantity/0` to `t:non_neg_integer/0`.
"""
def quantity_to_integer(quantity) do
hexadecimal_to_integer(quantity)
end
@doc """
Converts `t:timestamp/0` to `t:DateTime.t/0`
""" """
def hello do def timestamp_to_datetime(timestamp) do
:world timestamp
|> hexadecimal_to_integer()
|> Timex.from_unix()
end
defp build_batch_get_block_by_number(block_start, block_end) do
for current <- block_start..block_end do
%{
"id" => current,
"jsonrpc" => "2.0",
"method" => "eth_getBlockByNumber",
"params" => [int_to_hash_string(current), true]
}
end
end
defp encode_json(data), do: Jason.encode_to_iodata!(data)
defp decode_json(body, posted_payload, url) do
Jason.decode!(body)
rescue
Jason.DecodeError ->
Logger.error("""
failed to decode json payload:
url: #{inspect(url)}
body: #{inspect(body)}
posted payload: #{inspect(posted_payload)}
""")
raise("bad jason")
end end
defp handle_get_block_by_number({:ok, results}) do
{blocks, next} =
Enum.reduce(results, {[], :more}, fn
%{"result" => nil}, {blocks, _} -> {blocks, :end_of_chain}
%{"result" => %{} = block}, {blocks, next} -> {[block | blocks], next}
end)
elixir_blocks = Blocks.to_elixir(blocks)
elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks)
blocks_params = Blocks.elixir_to_params(elixir_blocks)
transactions_params = Transactions.elixir_to_params(elixir_transactions)
{:ok, next,
%{
blocks: blocks_params,
transactions: transactions_params
}}
end
defp handle_get_block_by_number({:error, reason}) do
{:error, reason}
end
defp handle_response(resp, 200) do
case resp do
[%{} | _] = batch_resp -> {:ok, batch_resp}
%{"error" => error} -> {:error, error}
%{"result" => result} -> {:ok, result}
end
end
defp handle_response(resp, _status) do
{:error, resp}
end
defp hexadecimal_to_integer("0x" <> hexadecimal_digits) do
String.to_integer(hexadecimal_digits, 16)
end
defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16)
end end

@ -0,0 +1,16 @@
defmodule EthereumJSONRPC.Application do
@moduledoc """
Starts `:hackney_pool` `:ethereum_jsonrpc`.
"""
use Application
@impl Application
def start(_type, _args) do
children = [
:hackney_pool.child_spec(:ethereum_jsonrpc, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000)
]
Supervisor.start_link(children, strategy: :one_for_one, name: EthereumJSONRPC.Supervisor)
end
end

@ -1,55 +1,55 @@
defmodule Explorer.JSONRPC.Block do defmodule EthereumJSONRPC.Block do
@moduledoc """ @moduledoc """
Block format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) Block format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash)
and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber). and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber).
""" """
import Explorer.JSONRPC, only: [nonce_to_integer: 1, quantity_to_integer: 1, timestamp_to_datetime: 1] import EthereumJSONRPC, only: [nonce_to_integer: 1, quantity_to_integer: 1, timestamp_to_datetime: 1]
alias Explorer.JSONRPC alias EthereumJSONRPC
alias Explorer.JSONRPC.Transactions alias EthereumJSONRPC.Transactions
@type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil}
@typedoc """ @typedoc """
* `"author"` - `t:Explorer.JSONRPC.address/0` that created the block. Aliased by `"miner"`. * `"author"` - `t:EthereumJSONRPC.address/0` that created the block. Aliased by `"miner"`.
* `"difficulty"` - `t:Explorer.JSONRPC.quantity/0`` of the difficulty for this block. * `"difficulty"` - `t:EthereumJSONRPC.quantity/0`` of the difficulty for this block.
* `"extraData"` - the extra `t:Explorer.JSONRPC.data/0`` field of this block. * `"extraData"` - the extra `t:EthereumJSONRPC.data/0`` field of this block.
* `"gasLimit" - maximum gas `t:Explorer.JSONRPC.quantity/0`` in this block. * `"gasLimit" - maximum gas `t:EthereumJSONRPC.quantity/0`` in this block.
* `"gasUsed" - the total `t:Explorer.JSONRPC.quantity/0`` of gas used by all transactions in this block. * `"gasUsed" - the total `t:EthereumJSONRPC.quantity/0`` of gas used by all transactions in this block.
* `"hash"` - the `t:Explorer.JSONRPC.hash/0` of the block. * `"hash"` - the `t:EthereumJSONRPC.hash/0` of the block.
* `"logsBloom"` - `t:Explorer.JSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) * `"logsBloom"` - `t:EthereumJSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter)
for the logs of the block. `nil` when block is pending. for the logs of the block. `nil` when block is pending.
* `"miner"` - `t:Explorer.JSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by * `"miner"` - `t:EthereumJSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by
`"author"`. `"author"`.
* `"nonce"` - `t:Explorer.JSONRPC.nonce/0`. `nil` when its pending block. * `"nonce"` - `t:EthereumJSONRPC.nonce/0`. `nil` when its pending block.
* `"number"` - the block number `t:Explorer.JSONRPC.quantity/0`. `nil` when block is pending. * `"number"` - the block number `t:EthereumJSONRPC.quantity/0`. `nil` when block is pending.
* `"parentHash" - the `t:Explorer.JSONRPC.hash/0` of the parent block. * `"parentHash" - the `t:EthereumJSONRPC.hash/0` of the parent block.
* `"receiptsRoot"` - `t:Explorer.JSONRPC.hash/0` of the root of the receipts. * `"receiptsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the receipts.
[trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block.
* `"sealFields"` - UNKNOWN * `"sealFields"` - UNKNOWN
* `"sha3Uncles"` - `t:Explorer.JSONRPC.hash/0` of the * `"sha3Uncles"` - `t:EthereumJSONRPC.hash/0` of the
[uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block. [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block.
* `"signature"` - UNKNOWN * `"signature"` - UNKNOWN
* `"size"` - `t:Explorer.JSONRPC.quantity/0`` of bytes in this block * `"size"` - `t:EthereumJSONRPC.quantity/0`` of bytes in this block
* `"stateRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the final state * `"stateRoot" - `t:EthereumJSONRPC.hash/0` of the root of the final state
[trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block.
* `"step"` - UNKNOWN * `"step"` - UNKNOWN
* `"timestamp"`: the unix timestamp as a `t:Explorer.JSONRPC.quantity/0`` for when the block was collated. * `"timestamp"`: the unix timestamp as a `t:EthereumJSONRPC.quantity/0`` for when the block was collated.
* `"totalDifficulty" - `t:Explorer.JSONRPC.quantity/0`` of the total difficulty of the chain until this block. * `"totalDifficulty" - `t:EthereumJSONRPC.quantity/0`` of the total difficulty of the chain until this block.
* `"transactions"` - `t:list/0` of `t:Explorer.JSONRPC.Transaction.t/0`. * `"transactions"` - `t:list/0` of `t:EthereumJSONRPC.Transaction.t/0`.
* `"transactionsRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the transaction * `"transactionsRoot" - `t:EthereumJSONRPC.hash/0` of the root of the transaction
[trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block.
* `uncles`: `t:list/0` of * `uncles`: `t:list/0` of
[uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block)
`t:Explorer.JSONRPC.hash/0`. `t:EthereumJSONRPC.hash/0`.
""" """
@type t :: %{String.t() => JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | nil} @type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil}
@doc """ @doc """
Converts `t:elixir/0` format to params used in `Explorer.Chain`. Converts `t:elixir/0` format to params used in `Explorer.Chain`.
iex> Explorer.JSONRPC.Block.elixir_to_params( iex> EthereumJSONRPC.Block.elixir_to_params(
...> %{ ...> %{
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> "difficulty" => 340282366920938463463374607431465537093, ...> "difficulty" => 340282366920938463463374607431465537093,
@ -125,9 +125,9 @@ defmodule Explorer.JSONRPC.Block do
end end
@doc """ @doc """
Get `t:Explorer.JSONRPC.Transactions.elixir/0` from `t:elixir/0` Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0`
iex> Explorer.JSONRPC.Block.elixir_to_transactions( iex> EthereumJSONRPC.Block.elixir_to_transactions(
...> %{ ...> %{
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> "difficulty" => 340282366920938463463374607431768211454, ...> "difficulty" => 340282366920938463463374607431768211454,
@ -211,7 +211,7 @@ defmodule Explorer.JSONRPC.Block do
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0`
iex> Explorer.JSONRPC.Block.to_elixir( iex> EthereumJSONRPC.Block.to_elixir(
...> %{ ...> %{
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> "difficulty" => "0xfffffffffffffffffffffffffffffffe", ...> "difficulty" => "0xfffffffffffffffffffffffffffffffe",
@ -278,7 +278,7 @@ defmodule Explorer.JSONRPC.Block do
end end
# double check that no new keys are being missed by requiring explicit match for passthrough # double check that no new keys are being missed by requiring explicit match for passthrough
# `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format # hash format
defp entry_to_elixir({key, _} = entry) defp entry_to_elixir({key, _} = entry)
when key in ~w(author extraData hash logsBloom miner parentHash receiptsRoot sealFields sha3Uncles signature when key in ~w(author extraData hash logsBloom miner parentHash receiptsRoot sealFields sha3Uncles signature

@ -1,10 +1,10 @@
defmodule Explorer.JSONRPC.Blocks do defmodule EthereumJSONRPC.Blocks do
@moduledoc """ @moduledoc """
Blocks format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) Blocks format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash)
and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber) from batch requests. and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber) from batch requests.
""" """
alias Explorer.JSONRPC.{Block, Transactions} alias EthereumJSONRPC.{Block, Transactions}
@type elixir :: [Block.elixir()] @type elixir :: [Block.elixir()]
@type t :: [Block.t()] @type t :: [Block.t()]
@ -12,7 +12,7 @@ defmodule Explorer.JSONRPC.Blocks do
@doc """ @doc """
Converts `t:elixir/0` elements to params used by `Explorer.Chain.Block.changeset/2`. Converts `t:elixir/0` elements to params used by `Explorer.Chain.Block.changeset/2`.
iex> Explorer.JSONRPC.Blocks.elixir_to_params( iex> EthereumJSONRPC.Blocks.elixir_to_params(
...> [ ...> [
...> %{ ...> %{
...> "author" => "0x0000000000000000000000000000000000000000", ...> "author" => "0x0000000000000000000000000000000000000000",
@ -64,9 +64,9 @@ defmodule Explorer.JSONRPC.Blocks do
end end
@doc """ @doc """
Extracts the `t:Explorer.JSONRPC.Transactions.elixir/0` from the `t:elixir/0`. Extracts the `t:EthereumJSONRPC.Transactions.elixir/0` from the `t:elixir/0`.
iex> Explorer.JSONRPC.Blocks.elixir_to_transactions([ iex> EthereumJSONRPC.Blocks.elixir_to_transactions([
...> %{ ...> %{
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> "difficulty" => 340282366920938463463374607431768211454, ...> "difficulty" => 340282366920938463463374607431768211454,
@ -150,7 +150,7 @@ defmodule Explorer.JSONRPC.Blocks do
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0`
iex> Explorer.JSONRPC.Blocks.to_elixir( iex> EthereumJSONRPC.Blocks.to_elixir(
...> [ ...> [
...> %{ ...> %{
...> "author" => "0x0000000000000000000000000000000000000000", ...> "author" => "0x0000000000000000000000000000000000000000",

@ -1,29 +1,29 @@
defmodule Explorer.JSONRPC.Log do defmodule EthereumJSONRPC.Log do
@moduledoc """ @moduledoc """
Log included in return from Log included in return from
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt).
""" """
import Explorer.JSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
@type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()} @type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()}
@typedoc """ @typedoc """
* `"address"` - `t:Explorer.JSONRPC.address/0` from which event originated. * `"address"` - `t:EthereumJSONRPC.address/0` from which event originated.
* `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. * `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block this transaction is in.
* `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. * `"blockNumber"` - `t:EthereumJSONRPC.quantity/0` for the block number this transaction is in.
* `"data"` - Data containing non-indexed log parameter * `"data"` - Data containing non-indexed log parameter
* `"logIndex"` - `t:Explorer.JSONRPC.quantity/0` of the event index positon in the block. * `"logIndex"` - `t:EthereumJSONRPC.quantity/0` of the event index positon in the block.
* `"topics"` - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. * `"topics"` - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log.
* `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction * `"transactionHash"` - `t:EthereumJSONRPC.hash/0` of the transaction
* `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. * `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the index of the transaction in the block.
""" """
@type t :: %{String.t() => String.t() | [String.t()]} @type t :: %{String.t() => String.t() | [String.t()]}
@doc """ @doc """
Converts `t:elixir/0` format to params used in `Explorer.Chain`. Converts `t:elixir/0` format to params used in `Explorer.Chain`.
iex> Explorer.JSONRPC.Log.elixir_to_params( iex> EthereumJSONRPC.Log.elixir_to_params(
...> %{ ...> %{
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
@ -71,7 +71,7 @@ defmodule Explorer.JSONRPC.Log do
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. Decodes the stringly typed numerical fields to `t:non_neg_integer/0`.
iex> Explorer.JSONRPC.Log.to_elixir( iex> EthereumJSONRPC.Log.to_elixir(
...> %{ ...> %{
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",

@ -1,10 +1,10 @@
defmodule Explorer.JSONRPC.Logs do defmodule EthereumJSONRPC.Logs do
@moduledoc """ @moduledoc """
Collection of logs included in return from Collection of logs included in return from
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt).
""" """
alias Explorer.JSONRPC.Log alias EthereumJSONRPC.Log
@type elixir :: [Log.elixir()] @type elixir :: [Log.elixir()]
@type t :: [Log.t()] @type t :: [Log.t()]

@ -1,16 +1,16 @@
defmodule Explorer.JSONRPC.Parity do defmodule EthereumJSONRPC.Parity do
@moduledoc """ @moduledoc """
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/).
""" """
import Explorer.JSONRPC, only: [config: 1, json_rpc: 2] import EthereumJSONRPC, only: [config: 1, json_rpc: 2]
alias Explorer.JSONRPC.Parity.Traces alias EthereumJSONRPC.Parity.Traces
@doc """ @doc """
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL.
iex> Explorer.JSONRPC.Parity.fetch_internal_transactions([ iex> EthereumJSONRPC.Parity.fetch_internal_transactions([
...> "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1" ...> "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1"
...> ]) ...> ])
{:ok, {:ok,

@ -1,16 +1,16 @@
defmodule Explorer.JSONRPC.Parity.Trace do defmodule EthereumJSONRPC.Parity.Trace do
@moduledoc """ @moduledoc """
Trace returned by Trace returned by
[`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an
extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/).
""" """
alias Explorer.JSONRPC.Parity.Trace.{Action, Result} alias EthereumJSONRPC.Parity.Trace.{Action, Result}
@doc """ @doc """
Create type traces are generated when a contract is created. Create type traces are generated when a contract is created.
iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( iex> EthereumJSONRPC.Parity.Trace.elixir_to_params(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
@ -46,7 +46,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
A create can fail due to a Bad Instruction in the `init` that is meant to form the `code` of the contract A create can fail due to a Bad Instruction in the `init` that is meant to form the `code` of the contract
iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( iex> EthereumJSONRPC.Parity.Trace.elixir_to_params(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "from" => "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", ...> "from" => "0x78a42d3705fb3c26a4b54737a784bf064f0815fb",
@ -76,7 +76,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
Call type traces are generated when a method is called. Calls are further divided by call type. Call type traces are generated when a method is called. Calls are further divided by call type.
iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( iex> EthereumJSONRPC.Parity.Trace.elixir_to_params(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "callType" => "call", ...> "callType" => "call",
@ -113,7 +113,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
Calls can error and be reverted Calls can error and be reverted
iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( iex> EthereumJSONRPC.Parity.Trace.elixir_to_params(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "callType" => "call", ...> "callType" => "call",
@ -153,7 +153,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
| `"balance"` | `:value` | | `"balance"` | `:value` |
| `"refundAddress"` | `:to_address_hash` | | `"refundAddress"` | `:to_address_hash` |
iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( iex> EthereumJSONRPC.Parity.Trace.elixir_to_params(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b",
@ -251,7 +251,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. Decodes the stringly typed numerical fields to `t:non_neg_integer/0`.
iex> Explorer.JSONRPC.Parity.Trace.to_elixir( iex> EthereumJSONRPC.Parity.Trace.to_elixir(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
@ -293,7 +293,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
The caller must put `"index"` and `"transactionHash"` into the incoming map, as Parity itself does not include that The caller must put `"index"` and `"transactionHash"` into the incoming map, as Parity itself does not include that
information, but it is needed to locate the trace in history fully. information, but it is needed to locate the trace in history fully.
iex> Explorer.JSONRPC.Parity.Trace.to_elixir( iex> EthereumJSONRPC.Parity.Trace.to_elixir(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
@ -316,7 +316,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
`"suicide"` `"type"` traces are different in that they have a `nil` `"result"`. This is because the `"result"` key `"suicide"` `"type"` traces are different in that they have a `nil` `"result"`. This is because the `"result"` key
is used to indicate success from Parity. is used to indicate success from Parity.
iex> Explorer.JSONRPC.Parity.Trace.to_elixir( iex> EthereumJSONRPC.Parity.Trace.to_elixir(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b",
@ -347,7 +347,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do
A call type trace can error and be reverted. A call type trace can error and be reverted.
iex> Explorer.JSONRPC.Parity.Trace.to_elixir( iex> EthereumJSONRPC.Parity.Trace.to_elixir(
...> %{ ...> %{
...> "action" => %{ ...> "action" => %{
...> "callType" => "call", ...> "callType" => "call",

@ -1,14 +1,14 @@
defmodule Explorer.JSONRPC.Parity.Trace.Action do defmodule EthereumJSONRPC.Parity.Trace.Action do
@moduledoc """ @moduledoc """
The action that was peformed in a `t:Explorer.JSONRPC.Parity.Trace.t/0` The action that was peformed in a `t:EthereumJSONRPC.Parity.Trace.t/0`
""" """
import Explorer.JSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. Decodes the stringly typed numerical fields to `t:non_neg_integer/0`.
iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir(
...> %{ ...> %{
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> "gas" => "0x462534", ...> "gas" => "0x462534",
@ -24,9 +24,9 @@ defmodule Explorer.JSONRPC.Parity.Trace.Action do
} }
For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address"` and `"refundAddress"` For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address"` and `"refundAddress"`
`t:Explorer.JSONRPC.hash/0` pass through. `t:EthereumJSONRPC.hash/0` pass through.
iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir(
...> %{ ...> %{
...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b",
...> "balance" => "0x0", ...> "balance" => "0x0",

@ -1,14 +1,14 @@
defmodule Explorer.JSONRPC.Parity.Trace.Result do defmodule EthereumJSONRPC.Parity.Trace.Result do
@moduledoc """ @moduledoc """
The result of performing the `t:Explorer.JSONRPC.Parity.Action.t/0` in a `t:Explorer.JSONRPC.Parity.Trace.t/0`. The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`.
""" """
import Explorer.JSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. Decodes the stringly typed numerical fields to `t:non_neg_integer/0`.
iex> Explorer.JSONRPC.Parity.Trace.Result.to_elixir( iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir(
...> %{ ...> %{
...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4",
...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
@ -23,7 +23,7 @@ defmodule Explorer.JSONRPC.Parity.Trace.Result do
`nil` resultscan occur for suicide type traces. `nil` resultscan occur for suicide type traces.
iex> Explorer.JSONRPC.Parity.Trace.Result.to_elixir(nil) iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir(nil)
nil nil
""" """

@ -1,11 +1,11 @@
defmodule Explorer.JSONRPC.Parity.Traces do defmodule EthereumJSONRPC.Parity.Traces do
@moduledoc """ @moduledoc """
Trace returned by Trace returned by
[`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an
extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/).
""" """
alias Explorer.JSONRPC.Parity.Trace alias EthereumJSONRPC.Parity.Trace
def elixir_to_params(elixir) when is_list(elixir) do def elixir_to_params(elixir) when is_list(elixir) do
Enum.map(elixir, &Trace.elixir_to_params/1) Enum.map(elixir, &Trace.elixir_to_params/1)

@ -1,37 +1,45 @@
defmodule Explorer.JSONRPC.Receipt do defmodule EthereumJSONRPC.Receipt do
@moduledoc """ @moduledoc """
Receipts format as returned by Receipts format as returned by
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt).
""" """
import Explorer.JSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
alias Explorer.Chain.Receipt.Status alias Explorer.Chain.Receipt.Status
alias Explorer.JSONRPC alias EthereumJSONRPC
alias Explorer.JSONRPC.Logs alias EthereumJSONRPC.Logs
@type elixir :: %{String.t() => String.t() | non_neg_integer} @type elixir :: %{String.t() => String.t() | non_neg_integer}
@typedoc """ @typedoc """
* `"contractAddress"` - The contract `t:Explorer.JSONRPC.address/0` created, if the transaction was a contract * `"contractAddress"` - The contract `t:EthereumJSONRPC.address/0` created, if the transaction was a contract
creation, otherwise `nil`. creation, otherwise `nil`.
* `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block where `"transactionHash"` was in. * `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block where `"transactionHash"` was in.
* `"blockNumber"` - The block number `t:Explorer.JSONRPC.quanity/0`. * `"blockNumber"` - The block number `t:EthereumJSONRPC.quanity/0`.
* `"cumulativeGasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used when this transaction was executed in the * `"cumulativeGasUsed"` - `t:EthereumJSONRPC.quantity/0` of gas used when this transaction was executed in the
block. block.
* `"gasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used by this specific transaction alone. * `"gasUsed"` - `t:EthereumJSONRPC.quantity/0` of gas used by this specific transaction alone.
* `"logs"` - `t:list/0` of log objects, which this transaction generated. * `"logs"` - `t:list/0` of log objects, which this transaction generated.
* `"logsBloom"` - `t:Explorer.JSONRPC.data/0` of 256 Bytes for * `"logsBloom"` - `t:EthereumJSONRPC.data/0` of 256 Bytes for
[Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs. [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs.
* `"root"` - `t:Explorer.JSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) * `"root"` - `t:EthereumJSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium)
* `"status"` - `t:Explorer.JSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) * `"status"` - `t:EthereumJSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium)
* `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` the transaction. * `"transactionHash"` - `t:EthereumJSONRPC.hash/0` the transaction.
* `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the transaction index in the block. * `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the transaction index in the block.
""" """
@type t :: %{String.t() => JSONRPC.address() | JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | list | nil} @type t :: %{
String.t() =>
EthereumJSONRPC.address()
| EthereumJSONRPC.data()
| EthereumJSONRPC.hash()
| EthereumJSONRPC.quantity()
| list
| nil
}
@doc """ @doc """
Get `t:Explorer.JSONRPC.Logs.elixir/0` from `t:elixir/0` Get `t:EthereumJSONRPC.Logs.elixir/0` from `t:elixir/0`
""" """
@spec elixir_to_logs(elixir) :: Logs.elixir() @spec elixir_to_logs(elixir) :: Logs.elixir()
def elixir_to_logs(%{"logs" => logs}), do: logs def elixir_to_logs(%{"logs" => logs}), do: logs
@ -39,7 +47,7 @@ defmodule Explorer.JSONRPC.Receipt do
@doc """ @doc """
Converts `t:elixir/0` format to params used in `Explorer.Chain`. Converts `t:elixir/0` format to params used in `Explorer.Chain`.
iex> Explorer.JSONRPC.Receipt.elixir_to_params( iex> EthereumJSONRPC.Receipt.elixir_to_params(
...> %{ ...> %{
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
...> "blockNumber" => 34, ...> "blockNumber" => 34,
@ -89,7 +97,7 @@ defmodule Explorer.JSONRPC.Receipt do
@doc """ @doc """
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. Decodes the stringly typed numerical fields to `t:non_neg_integer/0`.
iex> Explorer.JSONRPC.Receipt.to_elixir( iex> EthereumJSONRPC.Receipt.to_elixir(
...> %{ ...> %{
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
...> "blockNumber" => "0x22", ...> "blockNumber" => "0x22",
@ -125,7 +133,7 @@ defmodule Explorer.JSONRPC.Receipt do
end end
# double check that no new keys are being missed by requiring explicit match for passthrough # double check that no new keys are being missed by requiring explicit match for passthrough
# `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format # hash format
defp entry_to_elixir({key, _} = entry) when key in ~w(blockHash contractAddress logsBloom root transactionHash), defp entry_to_elixir({key, _} = entry) when key in ~w(blockHash contractAddress logsBloom root transactionHash),
do: entry do: entry

@ -1,13 +1,13 @@
defmodule Explorer.JSONRPC.Receipts do defmodule EthereumJSONRPC.Receipts do
@moduledoc """ @moduledoc """
Receipts format as returned by Receipts format as returned by
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt) from batch [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt) from batch
requests. requests.
""" """
import Explorer.JSONRPC, only: [config: 1, json_rpc: 2] import EthereumJSONRPC, only: [config: 1, json_rpc: 2]
alias Explorer.JSONRPC.{Logs, Receipt} alias EthereumJSONRPC.{Logs, Receipt}
@type elixir :: [Receipt.elixir()] @type elixir :: [Receipt.elixir()]
@type t :: [Receipt.t()] @type t :: [Receipt.t()]
@ -15,7 +15,7 @@ defmodule Explorer.JSONRPC.Receipts do
@doc """ @doc """
Extracts logs from `t:elixir/0` Extracts logs from `t:elixir/0`
iex> Explorer.JSONRPC.Receipts.elixir_to_logs([ iex> EthereumJSONRPC.Receipts.elixir_to_logs([
...> %{ ...> %{
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> "blockNumber" => 37, ...> "blockNumber" => 37,
@ -67,7 +67,7 @@ defmodule Explorer.JSONRPC.Receipts do
@doc """ @doc """
Converts each element of `t:elixir/0` to params used by `Explorer.Chain.Receipt.changeset/2`. Converts each element of `t:elixir/0` to params used by `Explorer.Chain.Receipt.changeset/2`.
iex> Explorer.JSONRPC.Receipts.elixir_to_params([ iex> EthereumJSONRPC.Receipts.elixir_to_params([
...> %{ ...> %{
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> "blockNumber" => 37, ...> "blockNumber" => 37,
@ -136,7 +136,7 @@ defmodule Explorer.JSONRPC.Receipts do
@doc """ @doc """
Converts stringly typed fields to native Elixir types. Converts stringly typed fields to native Elixir types.
iex> Explorer.JSONRPC.Receipts.to_elixir([ iex> EthereumJSONRPC.Receipts.to_elixir([
...> %{ ...> %{
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> "blockNumber" => "0x25", ...> "blockNumber" => "0x25",

@ -1,4 +1,4 @@
defmodule Explorer.JSONRPC.Transaction do defmodule EthereumJSONRPC.Transaction do
@moduledoc """ @moduledoc """
Transaction format included in the return of Transaction format included in the return of
[`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash)
@ -8,45 +8,50 @@ defmodule Explorer.JSONRPC.Transaction do
and [`eth_getTransactionByBlockNumberAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex) and [`eth_getTransactionByBlockNumberAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex)
""" """
import Explorer.JSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
alias Explorer.JSONRPC alias EthereumJSONRPC
@type elixir :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | String.t() | non_neg_integer() | nil} @type elixir :: %{
String.t() => EthereumJSONRPC.address() | EthereumJSONRPC.hash() | String.t() | non_neg_integer() | nil
}
@typedoc """ @typedoc """
* `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is * `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is
pending. pending.
* `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. `nil` when * `"blockNumber"` - `t:EthereumJSONRPC.quantity/0` for the block number this transaction is in. `nil` when
transaction is pending. transaction is pending.
* `"chainId"` - the chain on which the transaction exists. * `"chainId"` - the chain on which the transaction exists.
* `"condition"` - UNKNOWN * `"condition"` - UNKNOWN
* `"creates"` - `t:Explorer.JSONRPC.address/0` of the created contract, if the transaction creates a contract. * `"creates"` - `t:EthereumJSONRPC.address/0` of the created contract, if the transaction creates a contract.
* `"from"` - `t:Explorer.JSONRPC.address/0` of the sender. * `"from"` - `t:EthereumJSONRPC.address/0` of the sender.
* `"gas"` - `t:Explorer.JSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. * `"gas"` - `t:EthereumJSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used.
`gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed. `gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed.
* `"gasPrice"` - `t:Explorer.JSONRPC.quantity/0` of wei to pay per unit of gas used. * `"gasPrice"` - `t:EthereumJSONRPC.quantity/0` of wei to pay per unit of gas used.
* `"hash"` - `t:Explorer.JSONRPC.hash/0` of the transaction * `"hash"` - `t:EthereumJSONRPC.hash/0` of the transaction
* `"input"` - `t:Explorer.JSONRPC.data/0` sent along with the transaction, such as input to the contract. * `"input"` - `t:EthereumJSONRPC.data/0` sent along with the transaction, such as input to the contract.
* `"nonce"` - `t:Explorer.JSONRPC.quantity/0` of transactions made by the sender prior to this one. * `"nonce"` - `t:EthereumJSONRPC.quantity/0` of transactions made by the sender prior to this one.
* `"publicKey"` - `t:Explorer.JSONRPC.hash/0` of the public key of the signer. * `"publicKey"` - `t:EthereumJSONRPC.hash/0` of the public key of the signer.
* `"r"` - `t:Explorer.JSONRPC.quantity/0` for the R field of the signature. * `"r"` - `t:EthereumJSONRPC.quantity/0` for the R field of the signature.
* `"raw"` - Raw transaction `t:Explorer.JSONRPC.data/0` * `"raw"` - Raw transaction `t:EthereumJSONRPC.data/0`
* `"standardV"` - `t:Explorer.JSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. * `"standardV"` - `t:EthereumJSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature.
* `"to"` - `t:Explorer.JSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. * `"to"` - `t:EthereumJSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction.
* `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. `nil` when * `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the index of the transaction in the block. `nil` when
transaction is pending. transaction is pending.
* `"v"` - `t:Explorer.JSONRPC.quantity/0` for the V field of the signature. * `"v"` - `t:EthereumJSONRPC.quantity/0` for the V field of the signature.
* `"value"` - `t:Explorer.JSONRPC.quantity/0` of wei transfered * `"value"` - `t:EthereumJSONRPC.quantity/0` of wei transfered
""" """
@type t :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | JSONRPC.quantity() | String.t() | nil} @type t :: %{
String.t() =>
EthereumJSONRPC.address() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | String.t() | nil
}
@type params :: %{ @type params :: %{
block_hash: JSONRPC.hash(), block_hash: EthereumJSONRPC.hash(),
from_address_hash: JSONRPC.address(), from_address_hash: EthereumJSONRPC.address(),
gas: non_neg_integer(), gas: non_neg_integer(),
gas_price: non_neg_integer(), gas_price: non_neg_integer(),
hash: JSONRPC.hash(), hash: EthereumJSONRPC.hash(),
index: non_neg_integer(), index: non_neg_integer(),
input: String.t(), input: String.t(),
nonce: non_neg_integer(), nonce: non_neg_integer(),
@ -54,7 +59,7 @@ defmodule Explorer.JSONRPC.Transaction do
r: non_neg_integer(), r: non_neg_integer(),
s: non_neg_integer(), s: non_neg_integer(),
standard_v: 0 | 1, standard_v: 0 | 1,
to_address_hash: JSONRPC.address(), to_address_hash: EthereumJSONRPC.address(),
v: non_neg_integer(), v: non_neg_integer(),
value: non_neg_integer() value: non_neg_integer()
} }
@ -97,9 +102,9 @@ defmodule Explorer.JSONRPC.Transaction do
end end
@doc """ @doc """
Extracts `t:Explorer.JSONRPC.hash/0` from transaction `params` Extracts `t:EthereumJSONRPC.hash/0` from transaction `params`
iex> Explorer.JSONRPC.Transaction.params_to_hash( iex> EthereumJSONRPC.Transaction.params_to_hash(
...> %{ ...> %{
...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
...> gas: 4700000, ...> gas: 4700000,
@ -129,7 +134,7 @@ defmodule Explorer.JSONRPC.Transaction do
end end
# double check that no new keys are being missed by requiring explicit match for passthrough # double check that no new keys are being missed by requiring explicit match for passthrough
# `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format # hash format
# r s standardV and v pass through because they exceed postgres integer limits # r s standardV and v pass through because they exceed postgres integer limits
defp entry_to_elixir({key, value}) defp entry_to_elixir({key, value})

@ -1,11 +1,11 @@
defmodule Explorer.JSONRPC.Transactions do defmodule EthereumJSONRPC.Transactions do
@moduledoc """ @moduledoc """
List of transactions format as included in return from List of transactions format as included in return from
[`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) and [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) and
[`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber). [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber).
""" """
alias Explorer.JSONRPC.Transaction alias EthereumJSONRPC.Transaction
@type elixir :: [Transaction.elixir()] @type elixir :: [Transaction.elixir()]
@type t :: [Transaction.t()] @type t :: [Transaction.t()]
@ -13,7 +13,7 @@ defmodule Explorer.JSONRPC.Transactions do
@doc """ @doc """
Converts each entry in `elixir` to params used in `Explorer.Chain.Transaction.changeset/2`. Converts each entry in `elixir` to params used in `Explorer.Chain.Transaction.changeset/2`.
iex> Explorer.JSONRPC.Transactions.elixir_to_params( iex> EthereumJSONRPC.Transactions.elixir_to_params(
...> [ ...> [
...> %{ ...> %{
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
@ -67,7 +67,7 @@ defmodule Explorer.JSONRPC.Transactions do
@doc """ @doc """
Extract just the `t:Explorer.Chain.Transaction.t/0` `hash` from `params` list elements. Extract just the `t:Explorer.Chain.Transaction.t/0` `hash` from `params` list elements.
iex> Explorer.JSONRPC.Transactions.params_to_hashes( iex> EthereumJSONRPC.Transactions.params_to_hashes(
...> [ ...> [
...> %{ ...> %{
...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
@ -98,7 +98,7 @@ defmodule Explorer.JSONRPC.Transactions do
@doc """ @doc """
Decodes stringly typed fields in entries in `transactions` Decodes stringly typed fields in entries in `transactions`
iex> Explorer.JSONRPC.Transactions.to_elixir([ iex> EthereumJSONRPC.Transactions.to_elixir([
...> %{ ...> %{
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
...> "blockNumber" => "0x22", ...> "blockNumber" => "0x22",

@ -3,31 +3,63 @@ defmodule EthereumJsonrpc.MixProject do
def project do def project do
[ [
aliases: aliases(Mix.env()),
app: :ethereum_jsonrpc, app: :ethereum_jsonrpc,
version: "0.1.0",
build_path: "../../_build", build_path: "../../_build",
config_path: "../../config/config.exs", config_path: "../../config/config.exs",
deps: deps(),
deps_path: "../../deps", deps_path: "../../deps",
lockfile: "../../mix.lock", dialyzer: [
plt_add_deps: :transitive,
plt_add_apps: [:mix],
ignore_warnings: "../../.dialyzer-ignore"
],
elixir: "~> 1.6", elixir: "~> 1.6",
lockfile: "../../mix.lock",
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test,
dialyzer: :test
],
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
deps: deps() test_coverage: [tool: ExCoveralls],
version: "0.1.0"
] ]
end end
# Run "mix help compile.app" to learn about applications. # Run "mix help compile.app" to learn about applications.
def application do def application do
[ [
mod: {EthereumJSONRPC.Application, []},
extra_applications: [:logger] extra_applications: [:logger]
] ]
end end
defp aliases(env) do
env_aliases(env)
end
defp env_aliases(:dev), do: []
defp env_aliases(_env), do: [compile: "compile --warnings-as-errors"]
# Run "mix help deps" to learn about dependencies. # Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ [
# {:dep_from_hexpm, "~> 0.3.0"}, # Style Checking
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}, {:credo, "0.9.2", only: [:dev, :test], runtime: false},
# {:sibling_app_in_umbrella, in_umbrella: true}, # Static Type Checking
{:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false},
# Code coverage
{:excoveralls, "~> 0.8.1", only: [:test]},
# JSONRPC HTTP Post calls
{:httpoison, "~> 1.0", override: true},
# Decode/Encode JSON for JSONRPC
{:jason, "~> 1.0"},
# Convert unix timestamps in JSONRPC to DateTimes
{:timex, "~> 3.1.24"}
] ]
end end
end end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.BlockTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Block
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.BlocksTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Blocks
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.LogTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Log
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.Parity.Trace.ActionTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Parity.Trace.Action
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.Parity.Trace.ResultTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Parity.Trace.Result
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.Parity.TraceTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Parity.Trace
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.ParityTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Parity
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.ReceiptTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Receipt
end

@ -1,13 +1,13 @@
defmodule Explorer.JSONRPC.ReceiptsTest do defmodule EthereumJSONRPC.ReceiptsTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
alias Explorer.JSONRPC.Receipts alias EthereumJSONRPC.Receipts
doctest Receipts doctest Receipts
# These are integration tests that depend on the sokol chain being used. sokol can be used with the following config # These are integration tests that depend on the sokol chain being used. sokol can be used with the following config
# #
# config :explorer, Explorer.JSONRPC, # config :explorer, EthereumJSONRPC,
# trace_url: "https://sokol-trace.poa.network", # trace_url: "https://sokol-trace.poa.network",
# url: "https://sokol.poa.network" # url: "https://sokol.poa.network"
# #

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.TransactionTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Transaction
end

@ -0,0 +1,5 @@
defmodule EthereumJSONRPC.TransactionsTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Transactions
end

@ -1,8 +0,0 @@
defmodule EthereumJsonrpcTest do
use ExUnit.Case
doctest EthereumJsonrpc
test "greets the world" do
assert EthereumJsonrpc.hello() == :world
end
end

@ -1 +1,7 @@
# https://github.com/CircleCI-Public/circleci-demo-elixir-phoenix/blob/a89de33a01df67b6773ac90adc74c34367a4a2d6/test/test_helper.exs#L1-L3
junit_folder = Mix.Project.build_path() <> "/junit/#{Mix.Project.config()[:app]}"
File.mkdir_p!(junit_folder)
:ok = Application.put_env(:junit_formatter, :report_dir, junit_folder)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start() ExUnit.start()

@ -14,11 +14,6 @@ config :explorer,
ecto_repos: [Explorer.Repo], ecto_repos: [Explorer.Repo],
coin: "POA" coin: "POA"
config :explorer, Explorer.JSONRPC,
http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]],
trace_url: "https://sokol-trace.poa.network",
url: "https://sokol.poa.network"
config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000
config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime] config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime]

@ -25,7 +25,6 @@ defmodule Explorer.Application do
# Children to start when not testing # Children to start when not testing
defp secondary_children(_) do defp secondary_children(_) do
[ [
Explorer.JSONRPC,
Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor),
Explorer.Indexer.Supervisor, Explorer.Indexer.Supervisor,
Explorer.Chain.Statistics.Server, Explorer.Chain.Statistics.Server,

@ -5,12 +5,9 @@ defmodule Explorer.Indexer.AddressFetcher do
use GenServer use GenServer
require Logger require Logger
alias Explorer.{Chain, JSONRPC} alias EthereumJSONRPC
alias Explorer.Chain
alias Explorer.Chain.{ alias Explorer.Chain.{Address, Hash}
Address,
Hash
}
@fetch_interval :timer.seconds(3) @fetch_interval :timer.seconds(3)
@max_batch_size 100 @max_batch_size 100
@ -112,7 +109,7 @@ defmodule Explorer.Indexer.AddressFetcher do
end end
defp do_fetch_addresses(address_hashes) do defp do_fetch_addresses(address_hashes) do
JSONRPC.fetch_balances_by_hash(address_hashes) EthereumJSONRPC.fetch_balances_by_hash(address_hashes)
end end
defp take_batch(queue) do defp take_batch(queue) do

@ -7,9 +7,10 @@ defmodule Explorer.Indexer.BlockFetcher do
require Logger require Logger
alias Explorer.{Chain, Indexer, JSONRPC} alias EthereumJSONRPC
alias EthereumJSONRPC.Transactions
alias Explorer.{Chain, Indexer}
alias Explorer.Indexer.{AddressFetcher, Sequence} alias Explorer.Indexer.{AddressFetcher, Sequence}
alias Explorer.JSONRPC.Transactions
# dialyzer thinks that Logger.debug functions always have no_local_return # dialyzer thinks that Logger.debug functions always have no_local_return
@dialyzer {:nowarn_function, import_range: 3} @dialyzer {:nowarn_function, import_range: 3}
@ -161,7 +162,7 @@ defmodule Explorer.Indexer.BlockFetcher do
hashes hashes
|> Enum.chunk_every(state.internal_transactions_batch_size) |> Enum.chunk_every(state.internal_transactions_batch_size)
|> Task.async_stream(&JSONRPC.fetch_internal_transactions(&1), stream_opts) |> Task.async_stream(&EthereumJSONRPC.fetch_internal_transactions(&1), stream_opts)
|> Enum.reduce_while({:ok, []}, fn |> Enum.reduce_while({:ok, []}, fn
{:ok, {:ok, internal_transactions}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions}} {:ok, {:ok, internal_transactions}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions}}
{:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}}
@ -177,7 +178,7 @@ defmodule Explorer.Indexer.BlockFetcher do
hashes hashes
|> Enum.chunk_every(state.receipts_batch_size) |> Enum.chunk_every(state.receipts_batch_size)
|> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) |> Task.async_stream(&EthereumJSONRPC.fetch_transaction_receipts(&1), stream_opts)
|> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn |> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn
{:ok, {:ok, %{logs: logs, receipts: receipts}}}, {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> {:ok, {:ok, %{logs: logs, receipts: receipts}}}, {:ok, %{logs: acc_logs, receipts: acc_receipts}} ->
{:cont, {:ok, %{logs: acc_logs ++ logs, receipts: acc_receipts ++ receipts}}} {:cont, {:ok, %{logs: acc_logs ++ logs, receipts: acc_receipts ++ receipts}}}
@ -256,7 +257,7 @@ defmodule Explorer.Indexer.BlockFetcher do
# Only public for testing # Only public for testing
@doc false @doc false
def import_range({block_start, block_end} = range, %{} = state, seq) do def import_range({block_start, block_end} = range, %{} = state, seq) do
with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, with {:blocks, {:ok, next, result}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(block_start, block_end)},
%{blocks: blocks, transactions: transactions} = result, %{blocks: blocks, transactions: transactions} = result,
cap_seq(seq, next, range, state), cap_seq(seq, next, range, state),
transaction_hashes = Transactions.params_to_hashes(transactions), transaction_hashes = Transactions.params_to_hashes(transactions),

@ -1,309 +0,0 @@
defmodule Explorer.JSONRPC do
@moduledoc """
Ethereum JSONRPC client.
## Configuration
Configuration for parity URLs can be provided with the following mix config:
config :explorer, Explorer.JSONRPC,
url: "https://sokol.poa.network",
trace_url: "https://sokol-trace.poa.network",
http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]]
Note: the tracing node URL is provided separately from `:url`, via `:trace_url`. The trace URL and is used for
`fetch_internal_transactions`, which is only a supported method on tracing nodes. The `:http` option is passed
directly to the HTTP library (`HTTPoison`), which forwards the options down to `:hackney`.
"""
require Logger
alias Explorer.JSONRPC.{Blocks, Parity, Receipts, Transactions}
@typedoc """
Truncated 20-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a
`String.t`.
"""
@type address :: String.t()
@typedoc """
Binary data encoded as a single hexadecimal number in a `String.t`
"""
@type data :: String.t()
@typedoc """
A full 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a `String.t`
## Example
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
"""
@type hash :: String.t()
@typedoc """
8 byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash of the proof-of-work.
"""
@type nonce :: String.t()
@typedoc """
A number encoded as a hexadecimal number in a `String.t`
## Example
"0x1b4"
"""
@type quantity :: String.t()
@typedoc """
Unix timestamp encoded as a hexadecimal number in a `String.t`
"""
@type timestamp :: String.t()
def child_spec(_opts) do
:hackney_pool.child_spec(:eth, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000)
end
@doc """
Lists changes for a given filter subscription.
"""
def check_for_updates(filter_id) do
request = %{
"id" => filter_id,
"jsonrpc" => "2.0",
"method" => "eth_getFilterChanges",
"params" => [filter_id]
}
json_rpc(request, config(:url))
end
@doc """
Fetches configuration for this module under `key`
Configuration can be set a compile time using `config`
config :explorer, Explorer.JSONRRPC, key: value
Configuration can be set a runtime using `Application.put_env/3`
Application.put_env(:explorer, Explorer.JSONRPC, key: value)
"""
def config(key) do
:explorer
|> Application.fetch_env!(__MODULE__)
|> Keyword.fetch!(key)
end
@doc """
Fetches address balances by address hashes.
"""
def fetch_balances_by_hash(address_hashes) do
batched_requests =
for hash <- address_hashes do
%{
"id" => hash,
"jsonrpc" => "2.0",
"method" => "eth_getBalance",
"params" => [hash, "latest"]
}
end
batched_requests
|> json_rpc(config(:url))
|> handle_balances()
end
defp handle_balances({:ok, results}) do
native_results =
for response <- results, into: %{} do
{response["id"], hexadecimal_to_integer(response["result"])}
end
{:ok, native_results}
end
defp handle_balances({:error, _reason} = err), do: err
@doc """
Fetches blocks by block hashes.
Transaction data is included for each block.
"""
def fetch_blocks_by_hash(block_hashes) do
batched_requests =
for block_hash <- block_hashes do
%{
"id" => block_hash,
"jsonrpc" => "2.0",
"method" => "eth_getBlockByHash",
"params" => [block_hash, true]
}
end
batched_requests
|> json_rpc(config(:url))
|> handle_get_block_by_number()
|> case do
{:ok, _next, results} -> {:ok, results}
{:error, reason} -> {:error, reason}
end
end
@doc """
Fetches blocks by block number range.
"""
def fetch_blocks_by_range(block_start, block_end) do
block_start
|> build_batch_get_block_by_number(block_end)
|> json_rpc(config(:url))
|> handle_get_block_by_number()
end
@doc """
Fetches internal transactions from client-specific API.
"""
def fetch_internal_transactions(hashes) when is_list(hashes) do
Parity.fetch_internal_transactions(hashes)
end
def fetch_transaction_receipts(hashes) when is_list(hashes) do
Receipts.fetch(hashes)
end
@doc """
1. POSTs JSON `payload` to `url`
2. Decodes the response
3. Handles the response
## Returns
* Handled response
* `{:error, reason}` if POST failes
"""
def json_rpc(payload, url) do
json = encode_json(payload)
headers = [{"Content-Type", "application/json"}]
case HTTPoison.post(url, json, headers, config(:http)) do
{:ok, %HTTPoison.Response{body: body, status_code: code}} ->
body |> decode_json(payload, url) |> handle_response(code)
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end
@doc """
Creates a filter subscription that can be polled for retreiving new blocks.
"""
def listen_for_new_blocks do
id = DateTime.utc_now() |> DateTime.to_unix()
request = %{
"id" => id,
"jsonrpc" => "2.0",
"method" => "eth_newBlockFilter",
"params" => []
}
json_rpc(request, config(:url))
end
@doc """
Converts `t:nonce/0` to `t:non_neg_integer/0`
"""
def nonce_to_integer(nonce) do
hexadecimal_to_integer(nonce)
end
@doc """
Converts `t:quantity/0` to `t:non_neg_integer/0`.
"""
def quantity_to_integer(quantity) do
hexadecimal_to_integer(quantity)
end
@doc """
Converts `t:timestamp/0` to `t:DateTime.t/0`
"""
def timestamp_to_datetime(timestamp) do
timestamp
|> hexadecimal_to_integer()
|> Timex.from_unix()
end
defp build_batch_get_block_by_number(block_start, block_end) do
for current <- block_start..block_end do
%{
"id" => current,
"jsonrpc" => "2.0",
"method" => "eth_getBlockByNumber",
"params" => [int_to_hash_string(current), true]
}
end
end
defp encode_json(data), do: Jason.encode_to_iodata!(data)
defp decode_json(body, posted_payload, url) do
Jason.decode!(body)
rescue
Jason.DecodeError ->
Logger.error("""
failed to decode json payload:
url: #{inspect(url)}
body: #{inspect(body)}
posted payload: #{inspect(posted_payload)}
""")
raise("bad jason")
end
defp handle_get_block_by_number({:ok, results}) do
{blocks, next} =
Enum.reduce(results, {[], :more}, fn
%{"result" => nil}, {blocks, _} -> {blocks, :end_of_chain}
%{"result" => %{} = block}, {blocks, next} -> {[block | blocks], next}
end)
elixir_blocks = Blocks.to_elixir(blocks)
elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks)
blocks_params = Blocks.elixir_to_params(elixir_blocks)
transactions_params = Transactions.elixir_to_params(elixir_transactions)
{:ok, next,
%{
blocks: blocks_params,
transactions: transactions_params
}}
end
defp handle_get_block_by_number({:error, reason}) do
{:error, reason}
end
defp handle_response(resp, 200) do
case resp do
[%{} | _] = batch_resp -> {:ok, batch_resp}
%{"error" => error} -> {:error, error}
%{"result" => result} -> {:ok, result}
end
end
defp handle_response(resp, _status) do
{:error, resp}
end
defp hexadecimal_to_integer("0x" <> hexadecimal_digits) do
String.to_integer(hexadecimal_digits, 16)
end
defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16)
end

@ -70,6 +70,8 @@ defmodule Explorer.Mixfile do
# Code coverage # Code coverage
{:excoveralls, "~> 0.8.1", only: [:test]}, {:excoveralls, "~> 0.8.1", only: [:test]},
{:exvcr, "~> 0.10", only: :test}, {:exvcr, "~> 0.10", only: :test},
# JSONRPC access to Parity for `Explorer.Indexer`
{:ethereum_jsonrpc, in_umbrella: true},
{:httpoison, "~> 1.0", override: true}, {:httpoison, "~> 1.0", override: true},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false},

@ -4,7 +4,6 @@ defmodule Explorer.Indexer.AddressFetcherTest do
use Explorer.DataCase, async: false use Explorer.DataCase, async: false
alias Explorer.Chain.Address alias Explorer.Chain.Address
alias Explorer.JSONRPC
alias Explorer.Indexer.AddressFetcher alias Explorer.Indexer.AddressFetcher
@hash %Explorer.Chain.Hash{ @hash %Explorer.Chain.Hash{
@ -13,7 +12,6 @@ defmodule Explorer.Indexer.AddressFetcherTest do
} }
setup do setup do
start_supervised!({JSONRPC, []})
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor})
:ok :ok

@ -5,7 +5,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
import ExUnit.CaptureLog import ExUnit.CaptureLog
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction}
alias Explorer.JSONRPC
alias Explorer.Indexer.{BlockFetcher, Sequence} alias Explorer.Indexer.{BlockFetcher, Sequence}
@tag capture_log: true @tag capture_log: true
@ -32,7 +31,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
test "starts fetching blocks from Genesis" do test "starts fetching blocks from Genesis" do
assert Repo.aggregate(Block, :count, :hash) == 0 assert Repo.aggregate(Block, :count, :hash) == 0
start_supervised!({JSONRPC, []})
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor})
start_supervised!(BlockFetcher) start_supervised!(BlockFetcher)
@ -84,7 +82,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
setup :state setup :state
setup do setup do
start_supervised!({JSONRPC, []})
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor})
{:ok, state} = BlockFetcher.init(debug_logs: false) {:ok, state} = BlockFetcher.init(debug_logs: false)

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.BlockTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Block
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.BlocksTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Blocks
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.LogTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Log
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.Parity.Trace.ActionTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Parity.Trace.Action
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.Parity.Trace.ResultTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Parity.Trace.Result
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.Parity.TraceTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Parity.Trace
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.ParityTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Parity
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.ReceiptTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Receipt
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.TransactionTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Transaction
end

@ -1,5 +0,0 @@
defmodule Explorer.JSONRPC.TransactionsTest do
use ExUnit.Case, async: true
doctest Explorer.JSONRPC.Transactions
end

@ -1,7 +1,7 @@
{ {
"coverage_options": { "coverage_options": {
"treat_no_relevant_lines_as_covered": true, "treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 94.0 "minimum_coverage": 91.2
}, },
"terminal_options": { "terminal_options": {
"file_column_width": 120 "file_column_width": 120

Loading…
Cancel
Save