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
parent
75388c8ece
commit
11bcecad96
@ -1,18 +1,303 @@ |
||||
defmodule EthereumJsonrpc do |
||||
defmodule EthereumJSONRPC do |
||||
@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 """ |
||||
Hello world. |
||||
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] |
||||
} |
||||
|
||||
## Examples |
||||
json_rpc(request, config(:url)) |
||||
end |
||||
|
||||
iex> EthereumJsonrpc.hello |
||||
:world |
||||
@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 hello do |
||||
:world |
||||
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 |
||||
|
@ -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,10 +1,10 @@ |
||||
defmodule Explorer.JSONRPC.Logs do |
||||
defmodule EthereumJSONRPC.Logs do |
||||
@moduledoc """ |
||||
Collection of logs included in return from |
||||
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). |
||||
""" |
||||
|
||||
alias Explorer.JSONRPC.Log |
||||
alias EthereumJSONRPC.Log |
||||
|
||||
@type elixir :: [Log.elixir()] |
||||
@type t :: [Log.t()] |
@ -1,16 +1,16 @@ |
||||
defmodule Explorer.JSONRPC.Parity do |
||||
defmodule EthereumJSONRPC.Parity do |
||||
@moduledoc """ |
||||
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 """ |
||||
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" |
||||
...> ]) |
||||
{:ok, |
@ -1,11 +1,11 @@ |
||||
defmodule Explorer.JSONRPC.Parity.Traces do |
||||
defmodule EthereumJSONRPC.Parity.Traces do |
||||
@moduledoc """ |
||||
Trace returned by |
||||
[`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/). |
||||
""" |
||||
|
||||
alias Explorer.JSONRPC.Parity.Trace |
||||
alias EthereumJSONRPC.Parity.Trace |
||||
|
||||
def elixir_to_params(elixir) when is_list(elixir) do |
||||
Enum.map(elixir, &Trace.elixir_to_params/1) |
@ -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 |
||||
|
||||
alias Explorer.JSONRPC.Receipts |
||||
alias EthereumJSONRPC.Receipts |
||||
|
||||
doctest Receipts |
||||
|
||||
# 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", |
||||
# 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() |
||||
|
@ -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 |
@ -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 |
Loading…
Reference in new issue