Land #162: Optimized indexer
commit
d4234d3246
@ -0,0 +1,4 @@ |
|||||||
|
# Used by "mix format" |
||||||
|
[ |
||||||
|
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] |
||||||
|
] |
@ -0,0 +1,24 @@ |
|||||||
|
# The directory Mix will write compiled artifacts to. |
||||||
|
/_build/ |
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here. |
||||||
|
/cover/ |
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to. |
||||||
|
/deps/ |
||||||
|
|
||||||
|
# Where 3rd-party dependencies like ExDoc output generated docs. |
||||||
|
/doc/ |
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally. |
||||||
|
/.fetch |
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too. |
||||||
|
erl_crash.dump |
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build"). |
||||||
|
*.ez |
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build"). |
||||||
|
ethereum_jsonrpc-*.tar |
||||||
|
|
@ -0,0 +1,36 @@ |
|||||||
|
# EthereumJSONRPC |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
The OTP application `:ethereum_jsonrpc` can be used in other umbrella |
||||||
|
OTP applications by adding `ethereum_jsonrpc` to your list of |
||||||
|
dependencies in `mix.exs`: |
||||||
|
|
||||||
|
```elixir |
||||||
|
def deps do |
||||||
|
[ |
||||||
|
{:ethereum_jsonrpc, in_umbrella: true} |
||||||
|
] |
||||||
|
end |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
# This file is responsible for configuring your application |
||||||
|
# and its dependencies with the aid of the Mix.Config module. |
||||||
|
use Mix.Config |
||||||
|
|
||||||
|
config :ethereum_jsonrpc, |
||||||
|
http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]], |
||||||
|
trace_url: "https://sokol-trace.poa.network", |
||||||
|
url: "https://sokol.poa.network" |
@ -0,0 +1,303 @@ |
|||||||
|
defmodule EthereumJSONRPC do |
||||||
|
@moduledoc """ |
||||||
|
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 """ |
||||||
|
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 |
@ -0,0 +1,299 @@ |
|||||||
|
defmodule EthereumJSONRPC.Block do |
||||||
|
@moduledoc """ |
||||||
|
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). |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [nonce_to_integer: 1, quantity_to_integer: 1, timestamp_to_datetime: 1] |
||||||
|
|
||||||
|
alias EthereumJSONRPC |
||||||
|
alias EthereumJSONRPC.Transactions |
||||||
|
|
||||||
|
@type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `"author"` - `t:EthereumJSONRPC.address/0` that created the block. Aliased by `"miner"`. |
||||||
|
* `"difficulty"` - `t:EthereumJSONRPC.quantity/0` of the difficulty for this block. |
||||||
|
* `"extraData"` - the extra `t:EthereumJSONRPC.data/0` field of this block. |
||||||
|
* `"gasLimit" - maximum gas `t:EthereumJSONRPC.quantity/0` in this block. |
||||||
|
* `"gasUsed" - the total `t:EthereumJSONRPC.quantity/0` of gas used by all transactions in this block. |
||||||
|
* `"hash"` - the `t:EthereumJSONRPC.hash/0` of the block. |
||||||
|
* `"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. |
||||||
|
* `"miner"` - `t:EthereumJSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by |
||||||
|
`"author"`. |
||||||
|
* `"nonce"` - `t:EthereumJSONRPC.nonce/0`. `nil` when its pending block. |
||||||
|
* `"number"` - the block number `t:EthereumJSONRPC.quantity/0`. `nil` when block is pending. |
||||||
|
* `"parentHash" - the `t:EthereumJSONRPC.hash/0` of the parent block. |
||||||
|
* `"receiptsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the receipts. |
||||||
|
[trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. |
||||||
|
* `"sealFields"` - UNKNOWN |
||||||
|
* `"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. |
||||||
|
* `"signature"` - UNKNOWN |
||||||
|
* `"size"` - `t:EthereumJSONRPC.quantity/0` of bytes in this block |
||||||
|
* `"stateRoot" - `t:EthereumJSONRPC.hash/0` of the root of the final state |
||||||
|
[trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. |
||||||
|
* `"step"` - UNKNOWN |
||||||
|
* `"timestamp"`: the unix timestamp as a `t:EthereumJSONRPC.quantity/0` for when the block was collated. |
||||||
|
* `"totalDifficulty" - `t:EthereumJSONRPC.quantity/0` of the total difficulty of the chain until this block. |
||||||
|
* `"transactions"` - `t:list/0` of `t:EthereumJSONRPC.Transaction.t/0`. |
||||||
|
* `"transactionsRoot" - `t:EthereumJSONRPC.hash/0` of the root of the transaction |
||||||
|
[trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. |
||||||
|
* `uncles`: `t:list/0` of |
||||||
|
[uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) |
||||||
|
`t:EthereumJSONRPC.hash/0`. |
||||||
|
""" |
||||||
|
@type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts `t:elixir/0` format to params used in `Explorer.Chain`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Block.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "difficulty" => 340282366920938463463374607431465537093, |
||||||
|
...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", |
||||||
|
...> "gasLimit" => 6706541, |
||||||
|
...> "gasUsed" => 0, |
||||||
|
...> "hash" => "0x52c867bc0a91e573dc39300143c3bead7408d09d45bdb686749f02684ece72f3", |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "number" => 1, |
||||||
|
...> "parentHash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", |
||||||
|
...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "sealFields" => [ |
||||||
|
...> "0x84120a71ba", |
||||||
|
...> "0xb8417a5887662f09ac4673af5850d28f3ad6550407b9c814ef563a13320f881b55ef03754f48f2dde027ad4a5abcabcc42780d9ebfc645f183e5252507d6a25bc2ec01" |
||||||
|
...> ], |
||||||
|
...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
...> "signature" => "7a5887662f09ac4673af5850d28f3ad6550407b9c814ef563a13320f881b55ef03754f48f2dde027ad4a5abcabcc42780d9ebfc645f183e5252507d6a25bc2ec01", |
||||||
|
...> "size" => 576, |
||||||
|
...> "stateRoot" => "0xc196ad59d867542ef20b29df5f418d07dc7234f4bc3d25260526620b7958a8fb", |
||||||
|
...> "step" => "302674362", |
||||||
|
...> "timestamp" => Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), |
||||||
|
...> "totalDifficulty" => 340282366920938463463374607431465668165, |
||||||
|
...> "transactions" => [], |
||||||
|
...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "uncles" => [] |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
difficulty: 340282366920938463463374607431465537093, |
||||||
|
gas_limit: 6706541, |
||||||
|
gas_used: 0, |
||||||
|
hash: "0x52c867bc0a91e573dc39300143c3bead7408d09d45bdb686749f02684ece72f3", |
||||||
|
miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
nonce: 0, |
||||||
|
number: 1, |
||||||
|
parent_hash: "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", |
||||||
|
size: 576, |
||||||
|
timestamp: Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), |
||||||
|
total_difficulty: 340282366920938463463374607431465668165 |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_params(elixir) :: map |
||||||
|
def elixir_to_params( |
||||||
|
%{ |
||||||
|
"author" => miner_hash, |
||||||
|
"difficulty" => difficulty, |
||||||
|
"gasLimit" => gas_limit, |
||||||
|
"gasUsed" => gas_used, |
||||||
|
"hash" => hash, |
||||||
|
"miner" => miner_hash, |
||||||
|
"number" => number, |
||||||
|
"parentHash" => parent_hash, |
||||||
|
"size" => size, |
||||||
|
"timestamp" => timestamp, |
||||||
|
"totalDifficulty" => total_difficulty |
||||||
|
} = elixir |
||||||
|
) do |
||||||
|
%{ |
||||||
|
difficulty: difficulty, |
||||||
|
gas_limit: gas_limit, |
||||||
|
gas_used: gas_used, |
||||||
|
hash: hash, |
||||||
|
miner_hash: miner_hash, |
||||||
|
number: number, |
||||||
|
parent_hash: parent_hash, |
||||||
|
size: size, |
||||||
|
timestamp: timestamp, |
||||||
|
total_difficulty: total_difficulty |
||||||
|
} |
||||||
|
|> Map.put(:nonce, Map.get(elixir, "nonce", 0)) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0` |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Block.elixir_to_transactions( |
||||||
|
...> %{ |
||||||
|
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "difficulty" => 340282366920938463463374607431768211454, |
||||||
|
...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", |
||||||
|
...> "gasLimit" => 6926030, |
||||||
|
...> "gasUsed" => 269607, |
||||||
|
...> "hash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "number" => 34, |
||||||
|
...> "parentHash" => "0x106d528393159b93218dd410e2a778f083538098e46f1a44902aa67a164aed0b", |
||||||
|
...> "receiptsRoot" => "0xf45ed4ab910504ffe231230879c86e32b531bb38a398a7c9e266b4a992e12dfb", |
||||||
|
...> "sealFields" => [ |
||||||
|
...> "0x84120a71db", |
||||||
|
...> "0xb8417ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501" |
||||||
|
...> ], |
||||||
|
...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
...> "signature" => "7ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501", |
||||||
|
...> "size" => 1493, |
||||||
|
...> "stateRoot" => "0x6eaa6281df37b9b010f4779affc25ee059088240547ce86cf7ca7b7acd952d4f", |
||||||
|
...> "step" => "302674395", |
||||||
|
...> "timestamp" => Timex.parse!("2017-12-15T21:06:15Z", "{ISO:Extended:Z}"), |
||||||
|
...> "totalDifficulty" => 11569600475311907757754736652679816646147, |
||||||
|
...> "transactions" => [ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "blockNumber" => 34, |
||||||
|
...> "chainId" => 77, |
||||||
|
...> "condition" => nil, |
||||||
|
...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => 4700000, |
||||||
|
...> "gasPrice" => 100000000000, |
||||||
|
...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "nonce" => 0, |
||||||
|
...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
...> "r" => 78347657398501398198088841525118387115323315106407672963464534626150881627253, |
||||||
|
...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "s" => 51922098313630537482394298802395571009347262093735654389129912200586195014115, |
||||||
|
...> "standardV" => 0, |
||||||
|
...> "to" => nil, |
||||||
|
...> "transactionIndex" => 0, |
||||||
|
...> "v" => 189, |
||||||
|
...> "value" => 0 |
||||||
|
...> } |
||||||
|
...> ], |
||||||
|
...> "transactionsRoot" => "0x2c2e243e9735f6d0081ffe60356c0e4ec4c6a9064c68d10bf8091ff896f33087", |
||||||
|
...> "uncles" => [] |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
"blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
"blockNumber" => 34, |
||||||
|
"chainId" => 77, |
||||||
|
"condition" => nil, |
||||||
|
"creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"gas" => 4700000, |
||||||
|
"gasPrice" => 100000000000, |
||||||
|
"hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
"input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"nonce" => 0, |
||||||
|
"publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
"r" => 78347657398501398198088841525118387115323315106407672963464534626150881627253, |
||||||
|
"raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
"s" => 51922098313630537482394298802395571009347262093735654389129912200586195014115, |
||||||
|
"standardV" => 0, |
||||||
|
"to" => nil, |
||||||
|
"transactionIndex" => 0, |
||||||
|
"v" => 189, |
||||||
|
"value" => 0 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_transactions(elixir) :: Transactions.elixir() |
||||||
|
def elixir_to_transactions(%{"transactions" => transactions}), do: transactions |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Block.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "difficulty" => "0xfffffffffffffffffffffffffffffffe", |
||||||
|
...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", |
||||||
|
...> "gasLimit" => "0x66889b", |
||||||
|
...> "gasUsed" => "0x0", |
||||||
|
...> "hash" => "0x7f035c5f3c0678250853a1fde6027def7cac1812667bd0d5ab7ccb94eb8b6f3a", |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "number" => "0x3", |
||||||
|
...> "parentHash" => "0x5fc539c74f65418c64df413c8cc89828c4657a9fecabaa550ceb44ec67786da7", |
||||||
|
...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "sealFields" => [ |
||||||
|
...> "0x84120a71bc", |
||||||
|
...> "0xb84116ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101" |
||||||
|
...> ], |
||||||
|
...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
...> "signature" => "16ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101", |
||||||
|
...> "size" => "0x240", |
||||||
|
...> "stateRoot" => "0xf0a110ed0f3173dfb2403c59f4f7971ad3be5ec4eedee0764bd654d607213aba", |
||||||
|
...> "step" => "302674364", |
||||||
|
...> "timestamp" => "0x5a3438ac", |
||||||
|
...> "totalDifficulty" => "0x2ffffffffffffffffffffffffedf78e41", |
||||||
|
...> "transactions" => [], |
||||||
|
...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "uncles" => [] |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"difficulty" => 340282366920938463463374607431768211454, |
||||||
|
"extraData" => "0xd5830108048650617269747986312e32322e31826c69", |
||||||
|
"gasLimit" => 6719643, |
||||||
|
"gasUsed" => 0, |
||||||
|
"hash" => "0x7f035c5f3c0678250853a1fde6027def7cac1812667bd0d5ab7ccb94eb8b6f3a", |
||||||
|
"logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"number" => 3, |
||||||
|
"parentHash" => "0x5fc539c74f65418c64df413c8cc89828c4657a9fecabaa550ceb44ec67786da7", |
||||||
|
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
"sealFields" => [ |
||||||
|
"0x84120a71bc", |
||||||
|
"0xb84116ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101" |
||||||
|
], |
||||||
|
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
"signature" => "16ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101", |
||||||
|
"size" => 576, |
||||||
|
"stateRoot" => "0xf0a110ed0f3173dfb2403c59f4f7971ad3be5ec4eedee0764bd654d607213aba", |
||||||
|
"step" => "302674364", |
||||||
|
"timestamp" => Timex.parse!("2017-12-15T21:03:40Z", "{ISO:Extended:Z}"), |
||||||
|
"totalDifficulty" => 1020847100762815390390123822295002091073, |
||||||
|
"transactions" => [], |
||||||
|
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
"uncles" => [] |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
def to_elixir(block) when is_map(block) do |
||||||
|
Enum.into(block, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({key, quantity}) when key in ~w(difficulty gasLimit gasUsed number size totalDifficulty) do |
||||||
|
{key, quantity_to_integer(quantity)} |
||||||
|
end |
||||||
|
|
||||||
|
# double check that no new keys are being missed by requiring explicit match for passthrough |
||||||
|
# `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct |
||||||
|
# hash format |
||||||
|
defp entry_to_elixir({key, _} = entry) |
||||||
|
when key in ~w(author extraData hash logsBloom miner parentHash receiptsRoot sealFields sha3Uncles signature |
||||||
|
stateRoot step transactionsRoot uncles), |
||||||
|
do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({"nonce" = key, nonce}) do |
||||||
|
{key, nonce_to_integer(nonce)} |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({"timestamp" = key, timestamp}) do |
||||||
|
{key, timestamp_to_datetime(timestamp)} |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({"transactions" = key, transactions}) do |
||||||
|
{key, Transactions.to_elixir(transactions)} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,214 @@ |
|||||||
|
defmodule EthereumJSONRPC.Blocks do |
||||||
|
@moduledoc """ |
||||||
|
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. |
||||||
|
""" |
||||||
|
|
||||||
|
alias EthereumJSONRPC.{Block, Transactions} |
||||||
|
|
||||||
|
@type elixir :: [Block.elixir()] |
||||||
|
@type t :: [Block.t()] |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts `t:elixir/0` elements to params used by `Explorer.Chain.Block.changeset/2`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Blocks.elixir_to_params( |
||||||
|
...> [ |
||||||
|
...> %{ |
||||||
|
...> "author" => "0x0000000000000000000000000000000000000000", |
||||||
|
...> "difficulty" => 131072, |
||||||
|
...> "extraData" => "0x", |
||||||
|
...> "gasLimit" => 6700000, |
||||||
|
...> "gasUsed" => 0, |
||||||
|
...> "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "miner" => "0x0000000000000000000000000000000000000000", |
||||||
|
...> "number" => 0, |
||||||
|
...> "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "sealFields" => ["0x80", |
||||||
|
...> "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"], |
||||||
|
...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
...> "signature" => "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "size" => 533, |
||||||
|
...> "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", |
||||||
|
...> "step" => "0", |
||||||
|
...> "timestamp" => Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), |
||||||
|
...> "totalDifficulty" => 131072, |
||||||
|
...> "transactions" => [], |
||||||
|
...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "uncles" => [] |
||||||
|
...> } |
||||||
|
...> ] |
||||||
|
...> ) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
difficulty: 131072, |
||||||
|
gas_limit: 6700000, |
||||||
|
gas_used: 0, |
||||||
|
hash: "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", |
||||||
|
miner_hash: "0x0000000000000000000000000000000000000000", |
||||||
|
nonce: 0, |
||||||
|
number: 0, |
||||||
|
parent_hash: "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
size: 533, |
||||||
|
timestamp: Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), |
||||||
|
total_difficulty: 131072 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_params(elixir) :: [map] |
||||||
|
def elixir_to_params(elixir) when is_list(elixir) do |
||||||
|
Enum.map(elixir, &Block.elixir_to_params/1) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Extracts the `t:EthereumJSONRPC.Transactions.elixir/0` from the `t:elixir/0`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Blocks.elixir_to_transactions([ |
||||||
|
...> %{ |
||||||
|
...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "difficulty" => 340282366920938463463374607431768211454, |
||||||
|
...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", |
||||||
|
...> "gasLimit" => 6926030, |
||||||
|
...> "gasUsed" => 269607, |
||||||
|
...> "hash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "number" => 34, |
||||||
|
...> "parentHash" => "0x106d528393159b93218dd410e2a778f083538098e46f1a44902aa67a164aed0b", |
||||||
|
...> "receiptsRoot" => "0xf45ed4ab910504ffe231230879c86e32b531bb38a398a7c9e266b4a992e12dfb", |
||||||
|
...> "sealFields" => ["0x84120a71db", |
||||||
|
...> "0xb8417ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501"], |
||||||
|
...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
...> "signature" => "7ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501", |
||||||
|
...> "size" => 1493, |
||||||
|
...> "stateRoot" => "0x6eaa6281df37b9b010f4779affc25ee059088240547ce86cf7ca7b7acd952d4f", |
||||||
|
...> "step" => "302674395", |
||||||
|
...> "timestamp" => Timex.parse!("2017-12-15T21:06:15Z", "{ISO:Extended:Z}"), |
||||||
|
...> "totalDifficulty" => 11569600475311907757754736652679816646147, |
||||||
|
...> "transactions" => [ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "blockNumber" => 34, |
||||||
|
...> "chainId" => 77, |
||||||
|
...> "condition" => nil, |
||||||
|
...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => 4700000, |
||||||
|
...> "gasPrice" => 100000000000, |
||||||
|
...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "nonce" => 0, |
||||||
|
...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
...> "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "standardV" => "0x0", |
||||||
|
...> "to" => nil, |
||||||
|
...> "transactionIndex" => 0, |
||||||
|
...> "v" => "0xbd", |
||||||
|
...> "value" => 0 |
||||||
|
...> } |
||||||
|
...> ], |
||||||
|
...> "transactionsRoot" => "0x2c2e243e9735f6d0081ffe60356c0e4ec4c6a9064c68d10bf8091ff896f33087", |
||||||
|
...> "uncles" => [] |
||||||
|
...> } |
||||||
|
...> ]) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
"blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
"blockNumber" => 34, |
||||||
|
"chainId" => 77, |
||||||
|
"condition" => nil, |
||||||
|
"creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"gas" => 4700000, |
||||||
|
"gasPrice" => 100000000000, |
||||||
|
"hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
"input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"nonce" => 0, |
||||||
|
"publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
"r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
"raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
"s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
"standardV" => "0x0", |
||||||
|
"to" => nil, |
||||||
|
"transactionIndex" => 0, |
||||||
|
"v" => "0xbd", |
||||||
|
"value" => 0 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_transactions(t) :: Transactions.elixir() |
||||||
|
def elixir_to_transactions(elixir) when is_list(elixir) do |
||||||
|
Enum.flat_map(elixir, &Block.elixir_to_transactions/1) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Blocks.to_elixir( |
||||||
|
...> [ |
||||||
|
...> %{ |
||||||
|
...> "author" => "0x0000000000000000000000000000000000000000", |
||||||
|
...> "difficulty" => "0x20000", |
||||||
|
...> "extraData" => "0x", |
||||||
|
...> "gasLimit" => "0x663be0", |
||||||
|
...> "gasUsed" => "0x0", |
||||||
|
...> "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "miner" => "0x0000000000000000000000000000000000000000", |
||||||
|
...> "number" => "0x0", |
||||||
|
...> "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "sealFields" => ["0x80", |
||||||
|
...> "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"], |
||||||
|
...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
...> "signature" => "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "size" => "0x215", |
||||||
|
...> "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", |
||||||
|
...> "step" => "0", |
||||||
|
...> "timestamp" => "0x0", |
||||||
|
...> "totalDifficulty" => "0x20000", |
||||||
|
...> "transactions" => [], |
||||||
|
...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
...> "uncles" => [] |
||||||
|
...> } |
||||||
|
...> ] |
||||||
|
...> ) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
"author" => "0x0000000000000000000000000000000000000000", |
||||||
|
"difficulty" => 131072, |
||||||
|
"extraData" => "0x", |
||||||
|
"gasLimit" => 6700000, |
||||||
|
"gasUsed" => 0, |
||||||
|
"hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", |
||||||
|
"logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"miner" => "0x0000000000000000000000000000000000000000", |
||||||
|
"number" => 0, |
||||||
|
"parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
"sealFields" => ["0x80", |
||||||
|
"0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"], |
||||||
|
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
"signature" => "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"size" => 533, |
||||||
|
"stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", |
||||||
|
"step" => "0", |
||||||
|
"timestamp" => Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), |
||||||
|
"totalDifficulty" => 131072, |
||||||
|
"transactions" => [], |
||||||
|
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||||
|
"uncles" => [] |
||||||
|
} |
||||||
|
] |
||||||
|
""" |
||||||
|
@spec to_elixir(t) :: elixir |
||||||
|
def to_elixir(blocks) when is_list(blocks) do |
||||||
|
Enum.map(blocks, &Block.to_elixir/1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,119 @@ |
|||||||
|
defmodule EthereumJSONRPC.Log do |
||||||
|
@moduledoc """ |
||||||
|
Log included in return from |
||||||
|
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [quantity_to_integer: 1] |
||||||
|
|
||||||
|
@type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()} |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `"address"` - `t:EthereumJSONRPC.address/0` from which event originated. |
||||||
|
* `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block this transaction is in. |
||||||
|
* `"blockNumber"` - `t:EthereumJSONRPC.quantity/0` for the block number this transaction is in. |
||||||
|
* `"data"` - Data containing non-indexed log parameter |
||||||
|
* `"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. |
||||||
|
* `"transactionHash"` - `t:EthereumJSONRPC.hash/0` of the transaction |
||||||
|
* `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the index of the transaction in the block. |
||||||
|
""" |
||||||
|
@type t :: %{String.t() => String.t() | [String.t()]} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts `t:elixir/0` format to params used in `Explorer.Chain`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Log.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => 37, |
||||||
|
...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
...> "logIndex" => 0, |
||||||
|
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => 0, |
||||||
|
...> "transactionLogIndex" => 0, |
||||||
|
...> "type" => "mined" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", |
||||||
|
fourth_topic: nil, |
||||||
|
index: 0, |
||||||
|
second_topic: nil, |
||||||
|
third_topic: nil, |
||||||
|
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
type: "mined" |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
def elixir_to_params(%{ |
||||||
|
"address" => address_hash, |
||||||
|
"data" => data, |
||||||
|
"logIndex" => index, |
||||||
|
"topics" => topics, |
||||||
|
"transactionHash" => transaction_hash, |
||||||
|
"type" => type |
||||||
|
}) do |
||||||
|
%{ |
||||||
|
address_hash: address_hash, |
||||||
|
data: data, |
||||||
|
index: index, |
||||||
|
transaction_hash: transaction_hash, |
||||||
|
type: type |
||||||
|
} |
||||||
|
|> put_topics(topics) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Log.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => "0x25", |
||||||
|
...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
...> "logIndex" => "0x0", |
||||||
|
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => "0x0", |
||||||
|
...> "transactionLogIndex" => "0x0", |
||||||
|
...> "type" => "mined" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
"blockNumber" => 37, |
||||||
|
"data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
"logIndex" => 0, |
||||||
|
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
"transactionIndex" => 0, |
||||||
|
"transactionLogIndex" => 0, |
||||||
|
"type" => "mined" |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
def to_elixir(log) when is_map(log) do |
||||||
|
Enum.into(log, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({key, _} = entry) when key in ~w(address blockHash data topics transactionHash type), do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do |
||||||
|
{key, quantity_to_integer(quantity)} |
||||||
|
end |
||||||
|
|
||||||
|
defp put_topics(params, topics) when is_map(params) and is_list(topics) do |
||||||
|
params |
||||||
|
|> Map.put(:first_topic, Enum.at(topics, 0)) |
||||||
|
|> Map.put(:second_topic, Enum.at(topics, 1)) |
||||||
|
|> Map.put(:third_topic, Enum.at(topics, 2)) |
||||||
|
|> Map.put(:fourth_topic, Enum.at(topics, 3)) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,21 @@ |
|||||||
|
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 EthereumJSONRPC.Log |
||||||
|
|
||||||
|
@type elixir :: [Log.elixir()] |
||||||
|
@type t :: [Log.t()] |
||||||
|
|
||||||
|
@spec elixir_to_params(elixir) :: [map] |
||||||
|
def elixir_to_params(elixir) when is_list(elixir) do |
||||||
|
Enum.map(elixir, &Log.elixir_to_params/1) |
||||||
|
end |
||||||
|
|
||||||
|
@spec to_elixir(t) :: elixir |
||||||
|
def to_elixir(logs) when is_list(logs) do |
||||||
|
Enum.map(logs, &Log.to_elixir/1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,69 @@ |
|||||||
|
defmodule EthereumJSONRPC.Parity do |
||||||
|
@moduledoc """ |
||||||
|
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [config: 1, json_rpc: 2] |
||||||
|
|
||||||
|
alias EthereumJSONRPC.Parity.Traces |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.fetch_internal_transactions([ |
||||||
|
...> "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1" |
||||||
|
...> ]) |
||||||
|
{:ok, |
||||||
|
[ |
||||||
|
%{ |
||||||
|
created_contract_address_hash: "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461", |
||||||
|
created_contract_code: "0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029", |
||||||
|
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
gas: 4533872, |
||||||
|
gas_used: 382953, |
||||||
|
index: 0, |
||||||
|
init: "0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
trace_address: [], |
||||||
|
transaction_hash: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1", |
||||||
|
type: "create", |
||||||
|
value: 0 |
||||||
|
} |
||||||
|
]} |
||||||
|
|
||||||
|
""" |
||||||
|
def fetch_internal_transactions(transaction_hashes) when is_list(transaction_hashes) do |
||||||
|
with {:ok, responses} <- |
||||||
|
transaction_hashes |
||||||
|
|> Enum.map(&transaction_hash_to_internal_transaction_json/1) |
||||||
|
|> json_rpc(config(:trace_url)) do |
||||||
|
internal_transactions_params = |
||||||
|
responses |
||||||
|
|> responses_to_traces() |
||||||
|
|> Traces.to_elixir() |
||||||
|
|> Traces.elixir_to_params() |
||||||
|
|
||||||
|
{:ok, internal_transactions_params} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp response_to_trace(%{"id" => transaction_hash, "result" => %{"trace" => traces}}) when is_list(traces) do |
||||||
|
traces |
||||||
|
|> Stream.with_index() |
||||||
|
|> Enum.map(fn {trace, index} -> |
||||||
|
Map.merge(trace, %{"index" => index, "transactionHash" => transaction_hash}) |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp responses_to_traces(responses) when is_list(responses) do |
||||||
|
Enum.flat_map(responses, &response_to_trace/1) |
||||||
|
end |
||||||
|
|
||||||
|
defp transaction_hash_to_internal_transaction_json(transaction_hash) do |
||||||
|
%{ |
||||||
|
"id" => transaction_hash, |
||||||
|
"jsonrpc" => "2.0", |
||||||
|
"method" => "trace_replayTransaction", |
||||||
|
"params" => [transaction_hash, ["trace"]] |
||||||
|
} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,432 @@ |
|||||||
|
defmodule EthereumJSONRPC.Parity.Trace 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 EthereumJSONRPC.Parity.Trace.{Action, Result} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Create type traces are generated when a contract is created. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => 4597044, |
||||||
|
...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "value" => 0 |
||||||
|
...> }, |
||||||
|
...> "index" => 0, |
||||||
|
...> "result" => %{ |
||||||
|
...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "gasUsed" => 166651 |
||||||
|
...> }, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "type" => "create" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
gas: 4597044, |
||||||
|
gas_used: 166651, |
||||||
|
index: 0, |
||||||
|
init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
trace_address: [], |
||||||
|
transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
type: "create", |
||||||
|
value: 0 |
||||||
|
} |
||||||
|
|
||||||
|
A create can fail due to a Bad Instruction in the `init` that is meant to form the `code` of the contract |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "from" => "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", |
||||||
|
...> "gas" => 3946728, |
||||||
|
...> "init" => "0x4bb278f3", |
||||||
|
...> "value" => 0 |
||||||
|
...> }, |
||||||
|
...> "error" => "Bad instruction", |
||||||
|
...> "index" => 0, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "transactionHash" => "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", |
||||||
|
...> "type" => "create" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
error: "Bad instruction", |
||||||
|
from_address_hash: "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", |
||||||
|
gas: 3946728, |
||||||
|
index: 0, |
||||||
|
init: "0x4bb278f3", |
||||||
|
trace_address: [], |
||||||
|
transaction_hash: "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", |
||||||
|
type: "create", |
||||||
|
value: 0 |
||||||
|
} |
||||||
|
|
||||||
|
Call type traces are generated when a method is called. Calls are further divided by call type. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "callType" => "call", |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => 4677320, |
||||||
|
...> "input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
...> "to" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
...> "value" => 0 |
||||||
|
...> }, |
||||||
|
...> "index" => 0, |
||||||
|
...> "result" => %{ |
||||||
|
...> "gasUsed" => 27770, |
||||||
|
...> "output" => "0x" |
||||||
|
...> }, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "type" => "call" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
call_type: "call", |
||||||
|
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
gas: 4677320, |
||||||
|
gas_used: 27770, |
||||||
|
index: 0, |
||||||
|
output: "0x", |
||||||
|
to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
trace_address: [], |
||||||
|
transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
type: "call", |
||||||
|
value: 0 |
||||||
|
} |
||||||
|
|
||||||
|
Calls can error and be reverted |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "callType" => "call", |
||||||
|
...> "from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", |
||||||
|
...> "gas" => 7578728, |
||||||
|
...> "input" => "0xa6f2ae3a", |
||||||
|
...> "to" => "0xfdca0da4158740a93693441b35809b5bb463e527", |
||||||
|
...> "value" => 10000000000000000 |
||||||
|
...> }, |
||||||
|
...> "error" => "Reverted", |
||||||
|
...> "index" => 0, |
||||||
|
...> "subtraces" => 7, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", |
||||||
|
...> "type" => "call" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
call_type: "call", |
||||||
|
error: "Reverted", |
||||||
|
from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", |
||||||
|
gas: 7578728, |
||||||
|
index: 0, |
||||||
|
to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", |
||||||
|
trace_address: [], |
||||||
|
transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", |
||||||
|
type: "call", |
||||||
|
value: 10000000000000000 |
||||||
|
} |
||||||
|
|
||||||
|
Suicides transfer a `"balance"` from `"address"` to `"refundAddress"`. These suicide-unique fields can be mapped to |
||||||
|
pre-existing `t:Explorer.Chain.InternalTransaction.t/0` fields. |
||||||
|
|
||||||
|
| Elixir | Params | |
||||||
|
|-------------------|----------------------| |
||||||
|
| `"address"` | `:from_address_hash` | |
||||||
|
| `"balance"` | `:value` | |
||||||
|
| `"refundAddress"` | `:to_address_hash` | |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", |
||||||
|
...> "balance" => 0, |
||||||
|
...> "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" |
||||||
|
...> }, |
||||||
|
...> "index" => 1, |
||||||
|
...> "result" => nil, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [0], |
||||||
|
...> "transactionHash" => "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", |
||||||
|
...> "type" => "suicide" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
from_address_hash: "0xa7542d78b9a0be6147536887e0065f16182d294b", |
||||||
|
index: 1, |
||||||
|
to_address_hash: "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5", |
||||||
|
trace_address: [0], |
||||||
|
transaction_hash: "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", |
||||||
|
type: "suicide", |
||||||
|
value: 0 |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
def elixir_to_params(%{"type" => "call" = type} = elixir) do |
||||||
|
%{ |
||||||
|
"action" => %{ |
||||||
|
"callType" => call_type, |
||||||
|
"from" => from_address_hash, |
||||||
|
"gas" => gas, |
||||||
|
"to" => to_address_hash, |
||||||
|
"value" => value |
||||||
|
}, |
||||||
|
"index" => index, |
||||||
|
"traceAddress" => trace_address, |
||||||
|
"transactionHash" => transaction_hash |
||||||
|
} = elixir |
||||||
|
|
||||||
|
%{ |
||||||
|
call_type: call_type, |
||||||
|
from_address_hash: from_address_hash, |
||||||
|
gas: gas, |
||||||
|
index: index, |
||||||
|
to_address_hash: to_address_hash, |
||||||
|
trace_address: trace_address, |
||||||
|
transaction_hash: transaction_hash, |
||||||
|
type: type, |
||||||
|
value: value |
||||||
|
} |
||||||
|
|> put_call_error_or_result(elixir) |
||||||
|
end |
||||||
|
|
||||||
|
def elixir_to_params(%{"type" => "create" = type} = elixir) do |
||||||
|
%{ |
||||||
|
"action" => %{"from" => from_address_hash, "gas" => gas, "init" => init, "value" => value}, |
||||||
|
"index" => index, |
||||||
|
"traceAddress" => trace_address, |
||||||
|
"transactionHash" => transaction_hash |
||||||
|
} = elixir |
||||||
|
|
||||||
|
%{ |
||||||
|
from_address_hash: from_address_hash, |
||||||
|
gas: gas, |
||||||
|
index: index, |
||||||
|
init: init, |
||||||
|
trace_address: trace_address, |
||||||
|
transaction_hash: transaction_hash, |
||||||
|
type: type, |
||||||
|
value: value |
||||||
|
} |
||||||
|
|> put_create_error_or_result(elixir) |
||||||
|
end |
||||||
|
|
||||||
|
def elixir_to_params(%{"type" => "suicide" = type} = elixir) do |
||||||
|
%{ |
||||||
|
"action" => %{"address" => from_address_hash, "balance" => value, "refundAddress" => to_address_hash}, |
||||||
|
"index" => index, |
||||||
|
"traceAddress" => trace_address, |
||||||
|
"transactionHash" => transaction_hash |
||||||
|
} = elixir |
||||||
|
|
||||||
|
%{ |
||||||
|
from_address_hash: from_address_hash, |
||||||
|
index: index, |
||||||
|
to_address_hash: to_address_hash, |
||||||
|
trace_address: trace_address, |
||||||
|
transaction_hash: transaction_hash, |
||||||
|
type: type, |
||||||
|
value: value |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => "0x462534", |
||||||
|
...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "value" => "0x0" |
||||||
|
...> }, |
||||||
|
...> "index" => 0, |
||||||
|
...> "result" => %{ |
||||||
|
...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "gasUsed" => "0x28afb" |
||||||
|
...> }, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "type" => "create" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"action" => %{ |
||||||
|
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"gas" => 4597044, |
||||||
|
"init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"value" => 0 |
||||||
|
}, |
||||||
|
"index" => 0, |
||||||
|
"result" => %{ |
||||||
|
"address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
"code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"gasUsed" => 166651 |
||||||
|
}, |
||||||
|
"subtraces" => 0, |
||||||
|
"traceAddress" => [], |
||||||
|
"transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
"type" => "create" |
||||||
|
} |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => "0x462534", |
||||||
|
...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "value" => "0x0" |
||||||
|
...> }, |
||||||
|
...> "result" => %{ |
||||||
|
...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "gasUsed" => "0x28afb" |
||||||
|
...> }, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "type" => "create" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
** (ArgumentError) Caller must `Map.put/2` `"index"` and `"transactionHash"` in trace |
||||||
|
|
||||||
|
`"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. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", |
||||||
|
...> "balance" => "0x0", |
||||||
|
...> "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" |
||||||
|
...> }, |
||||||
|
...> "index" => 1, |
||||||
|
...> "result" => nil, |
||||||
|
...> "subtraces" => 0, |
||||||
|
...> "traceAddress" => [0], |
||||||
|
...> "transactionHash" => "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", |
||||||
|
...> "type" => "suicide" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"action" => %{ |
||||||
|
"address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", |
||||||
|
"balance" => 0, |
||||||
|
"refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" |
||||||
|
}, |
||||||
|
"index" => 1, |
||||||
|
"result" => nil, |
||||||
|
"subtraces" => 0, |
||||||
|
"traceAddress" => [0], |
||||||
|
"transactionHash" => "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", |
||||||
|
"type" => "suicide" |
||||||
|
} |
||||||
|
|
||||||
|
A call type trace can error and be reverted. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "action" => %{ |
||||||
|
...> "callType" => "call", |
||||||
|
...> "from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", |
||||||
|
...> "gas" => "0x73a468", |
||||||
|
...> "input" => "0xa6f2ae3a", |
||||||
|
...> "to" => "0xfdca0da4158740a93693441b35809b5bb463e527", |
||||||
|
...> "value" => "0x2386f26fc10000" |
||||||
|
...> }, |
||||||
|
...> "error" => "Reverted", |
||||||
|
...> "index" => 0, |
||||||
|
...> "subtraces" => 7, |
||||||
|
...> "traceAddress" => [], |
||||||
|
...> "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", |
||||||
|
...> "type" => "call" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"action" => %{ |
||||||
|
"callType" => "call", |
||||||
|
"from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", |
||||||
|
"gas" => 7578728, |
||||||
|
"input" => "0xa6f2ae3a", |
||||||
|
"to" => "0xfdca0da4158740a93693441b35809b5bb463e527", |
||||||
|
"value" => 10000000000000000 |
||||||
|
}, |
||||||
|
"error" => "Reverted", |
||||||
|
"index" => 0, |
||||||
|
"subtraces" => 7, |
||||||
|
"traceAddress" => [], |
||||||
|
"transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", |
||||||
|
"type" => "call" |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
def to_elixir(%{"index" => _, "transactionHash" => _} = trace) when is_map(trace) do |
||||||
|
Enum.into(trace, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
def to_elixir(_) do |
||||||
|
raise ArgumentError, ~S|Caller must `Map.put/2` `"index"` and `"transactionHash"` in trace| |
||||||
|
end |
||||||
|
|
||||||
|
# subtraces is an actual integer in JSON and not hex-encoded |
||||||
|
# traceAddress is a list of actual integers, not a list of hex-encoded |
||||||
|
defp entry_to_elixir({key, _} = entry) when key in ~w(subtraces traceAddress transactionHash type output), do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({"action" = key, action}) do |
||||||
|
{key, Action.to_elixir(action)} |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({"error", reason} = entry) when is_binary(reason), do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({"index", index} = entry) when is_integer(index), do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({"result" = key, result}) do |
||||||
|
{key, Result.to_elixir(result)} |
||||||
|
end |
||||||
|
|
||||||
|
defp put_call_error_or_result(params, %{"result" => %{"gasUsed" => gas_used, "output" => output}}) do |
||||||
|
Map.merge(params, %{gas_used: gas_used, output: output}) |
||||||
|
end |
||||||
|
|
||||||
|
defp put_call_error_or_result(params, %{"error" => error}) do |
||||||
|
Map.put(params, :error, error) |
||||||
|
end |
||||||
|
|
||||||
|
defp put_create_error_or_result(params, %{ |
||||||
|
"result" => %{"address" => created_contract_address_hash, "code" => code, "gasUsed" => gas_used} |
||||||
|
}) do |
||||||
|
Map.merge(params, %{ |
||||||
|
created_contract_code: code, |
||||||
|
created_contract_address_hash: created_contract_address_hash, |
||||||
|
gas_used: gas_used |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
defp put_create_error_or_result(params, %{"error" => error}) do |
||||||
|
Map.put(params, :error, error) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,52 @@ |
|||||||
|
defmodule EthereumJSONRPC.Parity.Trace.Action do |
||||||
|
@moduledoc """ |
||||||
|
The action that was peformed in a `t:EthereumJSONRPC.Parity.Trace.t/0` |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [quantity_to_integer: 1] |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => "0x462534", |
||||||
|
...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "value" => "0x0" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"gas" => 4597044, |
||||||
|
"init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"value" => 0 |
||||||
|
} |
||||||
|
|
||||||
|
For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address"` and `"refundAddress"` |
||||||
|
`t:EthereumJSONRPC.hash/0` pass through. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", |
||||||
|
...> "balance" => "0x0", |
||||||
|
...> "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", |
||||||
|
"balance" => 0, |
||||||
|
"refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
def to_elixir(action) when is_map(action) do |
||||||
|
Enum.into(action, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({key, _} = entry) when key in ~w(address callType from init input refundAddress to), do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({key, quantity}) when key in ~w(balance gas value) do |
||||||
|
{key, quantity_to_integer(quantity)} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,42 @@ |
|||||||
|
defmodule EthereumJSONRPC.Parity.Trace.Result do |
||||||
|
@moduledoc """ |
||||||
|
The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`. |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [quantity_to_integer: 1] |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "gasUsed" => "0x28afb" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
"code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"gasUsed" => 166651 |
||||||
|
} |
||||||
|
|
||||||
|
`nil` resultscan occur for suicide type traces. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir(nil) |
||||||
|
nil |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
def to_elixir(result) when is_map(result) do |
||||||
|
Enum.into(result, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
def to_elixir(nil), do: nil |
||||||
|
|
||||||
|
defp entry_to_elixir({key, _} = entry) when key in ~w(address code output), do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({key, quantity}) when key in ~w(gasUsed) do |
||||||
|
{key, quantity_to_integer(quantity)} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,17 @@ |
|||||||
|
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 EthereumJSONRPC.Parity.Trace |
||||||
|
|
||||||
|
def elixir_to_params(elixir) when is_list(elixir) do |
||||||
|
Enum.map(elixir, &Trace.elixir_to_params/1) |
||||||
|
end |
||||||
|
|
||||||
|
def to_elixir(traces) when is_list(traces) do |
||||||
|
Enum.map(traces, &Trace.to_elixir/1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,158 @@ |
|||||||
|
defmodule EthereumJSONRPC.Receipt do |
||||||
|
@moduledoc """ |
||||||
|
Receipts format as returned by |
||||||
|
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [quantity_to_integer: 1] |
||||||
|
|
||||||
|
alias Explorer.Chain.Receipt.Status |
||||||
|
alias EthereumJSONRPC |
||||||
|
alias EthereumJSONRPC.Logs |
||||||
|
|
||||||
|
@type elixir :: %{String.t() => String.t() | non_neg_integer} |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `"contractAddress"` - The contract `t:EthereumJSONRPC.address/0` created, if the transaction was a contract |
||||||
|
creation, otherwise `nil`. |
||||||
|
* `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block where `"transactionHash"` was in. |
||||||
|
* `"blockNumber"` - The block number `t:EthereumJSONRPC.quanity/0`. |
||||||
|
* `"cumulativeGasUsed"` - `t:EthereumJSONRPC.quantity/0` of gas used when this transaction was executed in the |
||||||
|
block. |
||||||
|
* `"gasUsed"` - `t:EthereumJSONRPC.quantity/0` of gas used by this specific transaction alone. |
||||||
|
* `"logs"` - `t:list/0` of log objects, which this transaction generated. |
||||||
|
* `"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. |
||||||
|
* `"root"` - `t:EthereumJSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) |
||||||
|
* `"status"` - `t:EthereumJSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) |
||||||
|
* `"transactionHash"` - `t:EthereumJSONRPC.hash/0` the transaction. |
||||||
|
* `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the transaction index in the block. |
||||||
|
""" |
||||||
|
@type t :: %{ |
||||||
|
String.t() => |
||||||
|
EthereumJSONRPC.address() |
||||||
|
| EthereumJSONRPC.data() |
||||||
|
| EthereumJSONRPC.hash() |
||||||
|
| EthereumJSONRPC.quantity() |
||||||
|
| list |
||||||
|
| nil |
||||||
|
} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Get `t:EthereumJSONRPC.Logs.elixir/0` from `t:elixir/0` |
||||||
|
""" |
||||||
|
@spec elixir_to_logs(elixir) :: Logs.elixir() |
||||||
|
def elixir_to_logs(%{"logs" => logs}), do: logs |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts `t:elixir/0` format to params used in `Explorer.Chain`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Receipt.elixir_to_params( |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "blockNumber" => 34, |
||||||
|
...> "contractAddress" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "cumulativeGasUsed" => 269607, |
||||||
|
...> "gasUsed" => 269607, |
||||||
|
...> "logs" => [], |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "root" => nil, |
||||||
|
...> "status" => :ok, |
||||||
|
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "transactionIndex" => 0 |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
cumulative_gas_used: 269607, |
||||||
|
gas_used: 269607, |
||||||
|
status: :ok, |
||||||
|
transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
transaction_index: 0 |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_params(elixir) :: %{ |
||||||
|
cumulative_gas_used: non_neg_integer, |
||||||
|
gas_used: non_neg_integer, |
||||||
|
status: Status.t(), |
||||||
|
transaction_hash: String.t(), |
||||||
|
transaction_index: non_neg_integer() |
||||||
|
} |
||||||
|
def elixir_to_params(%{ |
||||||
|
"cumulativeGasUsed" => cumulative_gas_used, |
||||||
|
"gasUsed" => gas_used, |
||||||
|
"status" => status, |
||||||
|
"transactionHash" => transaction_hash, |
||||||
|
"transactionIndex" => transaction_index |
||||||
|
}) do |
||||||
|
%{ |
||||||
|
cumulative_gas_used: cumulative_gas_used, |
||||||
|
gas_used: gas_used, |
||||||
|
status: status, |
||||||
|
transaction_hash: transaction_hash, |
||||||
|
transaction_index: transaction_index |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Receipt.to_elixir( |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "blockNumber" => "0x22", |
||||||
|
...> "contractAddress" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "cumulativeGasUsed" => "0x41d27", |
||||||
|
...> "gasUsed" => "0x41d27", |
||||||
|
...> "logs" => [], |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "root" => nil, |
||||||
|
...> "status" => "0x1", |
||||||
|
...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "transactionIndex" => "0x0" |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
%{ |
||||||
|
"blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
"blockNumber" => 34, |
||||||
|
"contractAddress" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
"cumulativeGasUsed" => 269607, |
||||||
|
"gasUsed" => 269607, |
||||||
|
"logs" => [], |
||||||
|
"logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"root" => nil, |
||||||
|
"status" => :ok, |
||||||
|
"transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
"transactionIndex" => 0 |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
@spec to_elixir(t) :: elixir |
||||||
|
def to_elixir(receipt) when is_map(receipt) do |
||||||
|
Enum.into(receipt, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
# double check that no new keys are being missed by requiring explicit match for passthrough |
||||||
|
# `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct |
||||||
|
# hash format |
||||||
|
defp entry_to_elixir({key, _} = entry) when key in ~w(blockHash contractAddress logsBloom root transactionHash), |
||||||
|
do: entry |
||||||
|
|
||||||
|
defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber cumulativeGasUsed gasUsed transactionIndex) do |
||||||
|
{key, quantity_to_integer(quantity)} |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({"logs" = key, logs}) do |
||||||
|
{key, Logs.to_elixir(logs)} |
||||||
|
end |
||||||
|
|
||||||
|
defp entry_to_elixir({"status" = key, status}) do |
||||||
|
elixir_status = |
||||||
|
case status do |
||||||
|
"0x0" -> :error |
||||||
|
"0x1" -> :ok |
||||||
|
end |
||||||
|
|
||||||
|
{key, elixir_status} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,216 @@ |
|||||||
|
defmodule EthereumJSONRPC.Receipts do |
||||||
|
@moduledoc """ |
||||||
|
Receipts format as returned by |
||||||
|
[`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt) from batch |
||||||
|
requests. |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [config: 1, json_rpc: 2] |
||||||
|
|
||||||
|
alias EthereumJSONRPC.{Logs, Receipt} |
||||||
|
|
||||||
|
@type elixir :: [Receipt.elixir()] |
||||||
|
@type t :: [Receipt.t()] |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Extracts logs from `t:elixir/0` |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Receipts.elixir_to_logs([ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => 37, |
||||||
|
...> "contractAddress" => nil, |
||||||
|
...> "cumulativeGasUsed" => 50450, |
||||||
|
...> "gasUsed" => 50450, |
||||||
|
...> "logs" => [ |
||||||
|
...> %{ |
||||||
|
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => 37, |
||||||
|
...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
...> "logIndex" => 0, |
||||||
|
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => 0, |
||||||
|
...> "transactionLogIndex" => 0, |
||||||
|
...> "type" => "mined" |
||||||
|
...> } |
||||||
|
...> ], |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "root" => nil, |
||||||
|
...> "status" => :ok, |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => 0 |
||||||
|
...> } |
||||||
|
...> ]) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
"blockNumber" => 37, |
||||||
|
"data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
"logIndex" => 0, |
||||||
|
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
"transactionIndex" => 0, |
||||||
|
"transactionLogIndex" => 0, |
||||||
|
"type" => "mined" |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_logs(elixir) :: Logs.elixir() |
||||||
|
def elixir_to_logs(elixir) when is_list(elixir) do |
||||||
|
Enum.flat_map(elixir, &Receipt.elixir_to_logs/1) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts each element of `t:elixir/0` to params used by `Explorer.Chain.Receipt.changeset/2`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Receipts.elixir_to_params([ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => 37, |
||||||
|
...> "contractAddress" => nil, |
||||||
|
...> "cumulativeGasUsed" => 50450, |
||||||
|
...> "gasUsed" => 50450, |
||||||
|
...> "logs" => [ |
||||||
|
...> %{ |
||||||
|
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => 37, |
||||||
|
...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
...> "logIndex" => 0, |
||||||
|
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => 0, |
||||||
|
...> "transactionLogIndex" => 0, |
||||||
|
...> "type" => "mined" |
||||||
|
...> } |
||||||
|
...> ], |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "root" => nil, |
||||||
|
...> "status" => :ok, |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => 0 |
||||||
|
...> } |
||||||
|
...> ]) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
cumulative_gas_used: 50450, |
||||||
|
gas_used: 50450, |
||||||
|
status: :ok, |
||||||
|
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
transaction_index: 0 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
@spec elixir_to_params(elixir) :: [map] |
||||||
|
def elixir_to_params(elixir) when is_list(elixir) do |
||||||
|
Enum.map(elixir, &Receipt.elixir_to_params/1) |
||||||
|
end |
||||||
|
|
||||||
|
def fetch(hashes) when is_list(hashes) do |
||||||
|
hashes |
||||||
|
|> Enum.map(&hash_to_json/1) |
||||||
|
|> json_rpc(config(:url)) |
||||||
|
|> case do |
||||||
|
{:ok, responses} -> |
||||||
|
elixir_receipts = |
||||||
|
responses |
||||||
|
|> responses_to_receipts() |
||||||
|
|> to_elixir() |
||||||
|
|
||||||
|
elixir_logs = elixir_to_logs(elixir_receipts) |
||||||
|
receipts = elixir_to_params(elixir_receipts) |
||||||
|
logs = Logs.elixir_to_params(elixir_logs) |
||||||
|
|
||||||
|
{:ok, %{logs: logs, receipts: receipts}} |
||||||
|
|
||||||
|
{:error, _reason} = err -> |
||||||
|
err |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts stringly typed fields to native Elixir types. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Receipts.to_elixir([ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => "0x25", |
||||||
|
...> "contractAddress" => nil, |
||||||
|
...> "cumulativeGasUsed" => "0xc512", |
||||||
|
...> "gasUsed" => "0xc512", |
||||||
|
...> "logs" => [ |
||||||
|
...> %{ |
||||||
|
...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
...> "blockNumber" => "0x25", |
||||||
|
...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
...> "logIndex" => "0x0", |
||||||
|
...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => "0x0", |
||||||
|
...> "transactionLogIndex" => "0x0", |
||||||
|
...> "type" => "mined" |
||||||
|
...> } |
||||||
|
...> ], |
||||||
|
...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
...> "root" => nil, |
||||||
|
...> "status" => "0x1", |
||||||
|
...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
...> "transactionIndex" => "0x0" |
||||||
|
...> } |
||||||
|
...> ]) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
"blockNumber" => 37, |
||||||
|
"contractAddress" => nil, |
||||||
|
"cumulativeGasUsed" => 50450, |
||||||
|
"gasUsed" => 50450, |
||||||
|
"logs" => [ |
||||||
|
%{ |
||||||
|
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", |
||||||
|
"blockNumber" => 37, |
||||||
|
"data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
"logIndex" => 0, |
||||||
|
"topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], |
||||||
|
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
"transactionIndex" => 0, |
||||||
|
"transactionLogIndex" => 0, |
||||||
|
"type" => "mined" |
||||||
|
} |
||||||
|
], |
||||||
|
"logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"root" => nil, |
||||||
|
"status" => :ok, |
||||||
|
"transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
"transactionIndex" => 0 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
@spec to_elixir(t) :: elixir |
||||||
|
def to_elixir(receipts) when is_list(receipts) do |
||||||
|
Enum.map(receipts, &Receipt.to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
defp hash_to_json(hash) do |
||||||
|
%{ |
||||||
|
"id" => hash, |
||||||
|
"jsonrpc" => "2.0", |
||||||
|
"method" => "eth_getTransactionReceipt", |
||||||
|
"params" => [hash] |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
defp response_to_receipt(%{"result" => receipt}), do: receipt |
||||||
|
|
||||||
|
defp responses_to_receipts(responses) when is_list(responses) do |
||||||
|
Enum.map(responses, &response_to_receipt/1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,155 @@ |
|||||||
|
defmodule EthereumJSONRPC.Transaction do |
||||||
|
@moduledoc """ |
||||||
|
Transaction format included in the return of |
||||||
|
[`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 returned by |
||||||
|
[`eth_getTransactionByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyhash), |
||||||
|
[`eth_getTransactionByBlockHashAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblockhashandindex), |
||||||
|
and [`eth_getTransactionByBlockNumberAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex) |
||||||
|
""" |
||||||
|
|
||||||
|
import EthereumJSONRPC, only: [quantity_to_integer: 1] |
||||||
|
|
||||||
|
alias EthereumJSONRPC |
||||||
|
|
||||||
|
@type elixir :: %{ |
||||||
|
String.t() => EthereumJSONRPC.address() | EthereumJSONRPC.hash() | String.t() | non_neg_integer() | nil |
||||||
|
} |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is |
||||||
|
pending. |
||||||
|
* `"blockNumber"` - `t:EthereumJSONRPC.quantity/0` for the block number this transaction is in. `nil` when |
||||||
|
transaction is pending. |
||||||
|
* `"chainId"` - the chain on which the transaction exists. |
||||||
|
* `"condition"` - UNKNOWN |
||||||
|
* `"creates"` - `t:EthereumJSONRPC.address/0` of the created contract, if the transaction creates a contract. |
||||||
|
* `"from"` - `t:EthereumJSONRPC.address/0` of the sender. |
||||||
|
* `"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. |
||||||
|
* `"gasPrice"` - `t:EthereumJSONRPC.quantity/0` of wei to pay per unit of gas used. |
||||||
|
* `"hash"` - `t:EthereumJSONRPC.hash/0` of the transaction |
||||||
|
* `"input"` - `t:EthereumJSONRPC.data/0` sent along with the transaction, such as input to the contract. |
||||||
|
* `"nonce"` - `t:EthereumJSONRPC.quantity/0` of transactions made by the sender prior to this one. |
||||||
|
* `"publicKey"` - `t:EthereumJSONRPC.hash/0` of the public key of the signer. |
||||||
|
* `"r"` - `t:EthereumJSONRPC.quantity/0` for the R field of the signature. |
||||||
|
* `"raw"` - Raw transaction `t:EthereumJSONRPC.data/0` |
||||||
|
* `"standardV"` - `t:EthereumJSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. |
||||||
|
* `"to"` - `t:EthereumJSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. |
||||||
|
* `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the index of the transaction in the block. `nil` when |
||||||
|
transaction is pending. |
||||||
|
* `"v"` - `t:EthereumJSONRPC.quantity/0` for the V field of the signature. |
||||||
|
* `"value"` - `t:EthereumJSONRPC.quantity/0` of wei transfered |
||||||
|
""" |
||||||
|
@type t :: %{ |
||||||
|
String.t() => |
||||||
|
EthereumJSONRPC.address() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | String.t() | nil |
||||||
|
} |
||||||
|
|
||||||
|
@type params :: %{ |
||||||
|
block_hash: EthereumJSONRPC.hash(), |
||||||
|
from_address_hash: EthereumJSONRPC.address(), |
||||||
|
gas: non_neg_integer(), |
||||||
|
gas_price: non_neg_integer(), |
||||||
|
hash: EthereumJSONRPC.hash(), |
||||||
|
index: non_neg_integer(), |
||||||
|
input: String.t(), |
||||||
|
nonce: non_neg_integer(), |
||||||
|
public_key: String.t(), |
||||||
|
r: non_neg_integer(), |
||||||
|
s: non_neg_integer(), |
||||||
|
standard_v: 0 | 1, |
||||||
|
to_address_hash: EthereumJSONRPC.address(), |
||||||
|
v: non_neg_integer(), |
||||||
|
value: non_neg_integer() |
||||||
|
} |
||||||
|
|
||||||
|
@spec elixir_to_params(elixir) :: params |
||||||
|
def elixir_to_params(%{ |
||||||
|
"blockHash" => block_hash, |
||||||
|
"from" => from_address_hash, |
||||||
|
"gas" => gas, |
||||||
|
"gasPrice" => gas_price, |
||||||
|
"hash" => hash, |
||||||
|
"input" => input, |
||||||
|
"nonce" => nonce, |
||||||
|
"publicKey" => public_key, |
||||||
|
"r" => r, |
||||||
|
"s" => s, |
||||||
|
"standardV" => standard_v, |
||||||
|
"to" => to_address_hash, |
||||||
|
"transactionIndex" => index, |
||||||
|
"v" => v, |
||||||
|
"value" => value |
||||||
|
}) do |
||||||
|
%{ |
||||||
|
block_hash: block_hash, |
||||||
|
from_address_hash: from_address_hash, |
||||||
|
gas: gas, |
||||||
|
gas_price: gas_price, |
||||||
|
hash: hash, |
||||||
|
index: index, |
||||||
|
input: input, |
||||||
|
nonce: nonce, |
||||||
|
public_key: public_key, |
||||||
|
r: r, |
||||||
|
s: s, |
||||||
|
standard_v: standard_v, |
||||||
|
to_address_hash: to_address_hash, |
||||||
|
v: v, |
||||||
|
value: value |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Extracts `t:EthereumJSONRPC.hash/0` from transaction `params` |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Transaction.params_to_hash( |
||||||
|
...> %{ |
||||||
|
...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> gas: 4700000, |
||||||
|
...> gas_price: 100000000000, |
||||||
|
...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> index: 0, |
||||||
|
...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> nonce: 0, |
||||||
|
...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", |
||||||
|
...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> standard_v: 0, |
||||||
|
...> v: "0x8d", |
||||||
|
...> value: 0 |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
"0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6" |
||||||
|
|
||||||
|
""" |
||||||
|
def params_to_hash(%{hash: hash}), do: hash |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. |
||||||
|
""" |
||||||
|
def to_elixir(transaction) when is_map(transaction) do |
||||||
|
Enum.into(transaction, %{}, &entry_to_elixir/1) |
||||||
|
end |
||||||
|
|
||||||
|
# double check that no new keys are being missed by requiring explicit match for passthrough |
||||||
|
# `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct |
||||||
|
# hash format |
||||||
|
# r s standardV and v pass through because they exceed postgres integer limits |
||||||
|
defp entry_to_elixir({key, value}) |
||||||
|
when key in ~w(blockHash condition creates from hash input jsonrpc publicKey r raw s standardV to v), |
||||||
|
do: {key, value} |
||||||
|
|
||||||
|
defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber gas gasPrice nonce transactionIndex value) do |
||||||
|
{key, quantity_to_integer(quantity)} |
||||||
|
end |
||||||
|
|
||||||
|
# chainId is *sometimes* nil |
||||||
|
defp entry_to_elixir({"chainId" = key, chainId}) do |
||||||
|
case chainId do |
||||||
|
nil -> {key, chainId} |
||||||
|
_ -> {key, quantity_to_integer(chainId)} |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,154 @@ |
|||||||
|
defmodule EthereumJSONRPC.Transactions do |
||||||
|
@moduledoc """ |
||||||
|
List of transactions format as included in return from |
||||||
|
[`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). |
||||||
|
""" |
||||||
|
|
||||||
|
alias EthereumJSONRPC.Transaction |
||||||
|
|
||||||
|
@type elixir :: [Transaction.elixir()] |
||||||
|
@type t :: [Transaction.t()] |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts each entry in `elixir` to params used in `Explorer.Chain.Transaction.changeset/2`. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Transactions.elixir_to_params( |
||||||
|
...> [ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "blockNumber" => 34, |
||||||
|
...> "chainId" => 77, |
||||||
|
...> "condition" => nil, |
||||||
|
...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => 4700000, |
||||||
|
...> "gasPrice" => 100000000000, |
||||||
|
...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "nonce" => 0, |
||||||
|
...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
...> "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "standardV" => "0x0", |
||||||
|
...> "to" => nil, |
||||||
|
...> "transactionIndex" => 0, |
||||||
|
...> "v" => "0xbd", |
||||||
|
...> "value" => 0 |
||||||
|
...> } |
||||||
|
...> ] |
||||||
|
...> ) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
gas: 4700000, |
||||||
|
gas_price: 100000000000, |
||||||
|
hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
index: 0, |
||||||
|
input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
nonce: 0, |
||||||
|
public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
r: "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
standard_v: "0x0", |
||||||
|
to_address_hash: nil, |
||||||
|
v: "0xbd", |
||||||
|
value: 0 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
def elixir_to_params(elixir) when is_list(elixir) do |
||||||
|
Enum.map(elixir, &Transaction.elixir_to_params/1) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Extract just the `t:Explorer.Chain.Transaction.t/0` `hash` from `params` list elements. |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Transactions.params_to_hashes( |
||||||
|
...> [ |
||||||
|
...> %{ |
||||||
|
...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> gas: 4700000, |
||||||
|
...> gas_price: 100000000000, |
||||||
|
...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> index: 0, |
||||||
|
...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> nonce: 0, |
||||||
|
...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
...> r: "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> standard_v: "0x0", |
||||||
|
...> to_address_hash: nil, |
||||||
|
...> v: "0xbd", |
||||||
|
...> value: 0 |
||||||
|
...> } |
||||||
|
...> ] |
||||||
|
...> ) |
||||||
|
["0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6"] |
||||||
|
|
||||||
|
""" |
||||||
|
def params_to_hashes(params) when is_list(params) do |
||||||
|
Enum.map(params, &Transaction.params_to_hash/1) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Decodes stringly typed fields in entries in `transactions` |
||||||
|
|
||||||
|
iex> EthereumJSONRPC.Transactions.to_elixir([ |
||||||
|
...> %{ |
||||||
|
...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
...> "blockNumber" => "0x22", |
||||||
|
...> "chainId" => "0x4d", |
||||||
|
...> "condition" => nil, |
||||||
|
...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
...> "gas" => "0x47b760", |
||||||
|
...> "gasPrice" => "0x174876e800", |
||||||
|
...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
...> "nonce" => "0x0", |
||||||
|
...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
...> "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
...> "standardV" => "0x0", |
||||||
|
...> "to" => nil, |
||||||
|
...> "transactionIndex" => "0x0", |
||||||
|
...> "v" => "0xbd", |
||||||
|
...> "value" => "0x0" |
||||||
|
...> } |
||||||
|
...> ]) |
||||||
|
[ |
||||||
|
%{ |
||||||
|
"blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", |
||||||
|
"blockNumber" => 34, |
||||||
|
"chainId" => 77, |
||||||
|
"condition" => nil, |
||||||
|
"creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", |
||||||
|
"from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", |
||||||
|
"gas" => 4700000, |
||||||
|
"gasPrice" => 100000000000, |
||||||
|
"hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", |
||||||
|
"input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", |
||||||
|
"nonce" => 0, |
||||||
|
"publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", |
||||||
|
"r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", |
||||||
|
"raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
"s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", |
||||||
|
"standardV" => "0x0", |
||||||
|
"to" => nil, |
||||||
|
"transactionIndex" => 0, |
||||||
|
"v" => "0xbd", |
||||||
|
"value" => 0 |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
""" |
||||||
|
def to_elixir(transactions) when is_list(transactions) do |
||||||
|
Enum.map(transactions, &Transaction.to_elixir/1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,65 @@ |
|||||||
|
defmodule EthereumJsonrpc.MixProject do |
||||||
|
use Mix.Project |
||||||
|
|
||||||
|
def project do |
||||||
|
[ |
||||||
|
aliases: aliases(Mix.env()), |
||||||
|
app: :ethereum_jsonrpc, |
||||||
|
build_path: "../../_build", |
||||||
|
config_path: "../../config/config.exs", |
||||||
|
deps: deps(), |
||||||
|
deps_path: "../../deps", |
||||||
|
dialyzer: [ |
||||||
|
plt_add_deps: :transitive, |
||||||
|
plt_add_apps: [:mix], |
||||||
|
ignore_warnings: "../../.dialyzer-ignore" |
||||||
|
], |
||||||
|
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, |
||||||
|
test_coverage: [tool: ExCoveralls], |
||||||
|
version: "0.1.0" |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications. |
||||||
|
def application do |
||||||
|
[ |
||||||
|
mod: {EthereumJSONRPC.Application, []}, |
||||||
|
extra_applications: [:logger] |
||||||
|
] |
||||||
|
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. |
||||||
|
defp deps do |
||||||
|
[ |
||||||
|
# Style Checking |
||||||
|
{:credo, "0.9.2", only: [:dev, :test], runtime: false}, |
||||||
|
# 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 |
@ -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 |
@ -0,0 +1,43 @@ |
|||||||
|
defmodule EthereumJSONRPC.ReceiptsTest do |
||||||
|
use ExUnit.Case, async: true |
||||||
|
|
||||||
|
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, EthereumJSONRPC, |
||||||
|
# trace_url: "https://sokol-trace.poa.network", |
||||||
|
# url: "https://sokol.poa.network" |
||||||
|
# |
||||||
|
describe "fetch/1" do |
||||||
|
test "with receipts and logs" do |
||||||
|
assert {:ok, |
||||||
|
%{ |
||||||
|
logs: [ |
||||||
|
%{ |
||||||
|
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", |
||||||
|
data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", |
||||||
|
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", |
||||||
|
fourth_topic: nil, |
||||||
|
index: 0, |
||||||
|
second_topic: nil, |
||||||
|
third_topic: nil, |
||||||
|
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
type: "mined" |
||||||
|
} |
||||||
|
], |
||||||
|
receipts: [ |
||||||
|
%{ |
||||||
|
cumulative_gas_used: 50450, |
||||||
|
gas_used: 50450, |
||||||
|
status: :ok, |
||||||
|
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", |
||||||
|
transaction_index: 0 |
||||||
|
} |
||||||
|
] |
||||||
|
}} = Receipts.fetch(["0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"]) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -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 |
@ -0,0 +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,14 +0,0 @@ |
|||||||
defmodule BackfillTransactionReceiptIds do |
|
||||||
@moduledoc "Backfills transactions with receipt_id values" |
|
||||||
alias Explorer.Repo |
|
||||||
|
|
||||||
def run do |
|
||||||
query = """ |
|
||||||
UPDATE transactions SET (receipt_id) = ( |
|
||||||
SELECT id FROM receipts WHERE receipts.transaction_id = transactions.id |
|
||||||
); |
|
||||||
""" |
|
||||||
|
|
||||||
{:ok, _result} = Repo.query(query, []) |
|
||||||
end |
|
||||||
end |
|
File diff suppressed because it is too large
Load Diff
@ -1,25 +0,0 @@ |
|||||||
defmodule Explorer.Chain.BlockTransaction do |
|
||||||
@moduledoc "Connects a Block to a Transaction" |
|
||||||
|
|
||||||
use Explorer.Schema |
|
||||||
|
|
||||||
alias Explorer.Chain.{Block, Transaction} |
|
||||||
|
|
||||||
@primary_key false |
|
||||||
schema "block_transactions" do |
|
||||||
belongs_to(:block, Block) |
|
||||||
belongs_to(:transaction, Transaction, primary_key: true) |
|
||||||
timestamps() |
|
||||||
end |
|
||||||
|
|
||||||
@required_attrs ~w(block_id transaction_id)a |
|
||||||
|
|
||||||
def changeset(%__MODULE__{} = block_transaction, attrs \\ %{}) do |
|
||||||
block_transaction |
|
||||||
|> cast(attrs, @required_attrs) |
|
||||||
|> validate_required(@required_attrs) |
|
||||||
|> cast_assoc(:block) |
|
||||||
|> cast_assoc(:transaction) |
|
||||||
|> unique_constraint(:transaction_id, name: :block_transactions_transaction_id_index) |
|
||||||
end |
|
||||||
end |
|
@ -1,21 +0,0 @@ |
|||||||
defmodule Explorer.Chain.FromAddress do |
|
||||||
@moduledoc false |
|
||||||
|
|
||||||
use Explorer.Schema |
|
||||||
|
|
||||||
alias Explorer.Chain.{Address, Transaction} |
|
||||||
|
|
||||||
@primary_key false |
|
||||||
schema "from_addresses" do |
|
||||||
belongs_to(:address, Address) |
|
||||||
belongs_to(:transaction, Transaction, primary_key: true) |
|
||||||
|
|
||||||
timestamps() |
|
||||||
end |
|
||||||
|
|
||||||
def changeset(%__MODULE__{} = to_address, attrs \\ %{}) do |
|
||||||
to_address |
|
||||||
|> cast(attrs, [:transaction_id, :address_id]) |
|
||||||
|> unique_constraint(:transaction_id, name: :from_addresses_transaction_id_index) |
|
||||||
end |
|
||||||
end |
|
@ -1,10 +1,224 @@ |
|||||||
defmodule Explorer.Chain.Hash do |
defmodule Explorer.Chain.Hash do |
||||||
@moduledoc """ |
@moduledoc """ |
||||||
Hash used throughout Ethereum chains. |
A [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash. |
||||||
""" |
""" |
||||||
|
|
||||||
|
import Bitwise |
||||||
|
|
||||||
|
@bits_per_byte 8 |
||||||
|
@hexadecimal_digits_per_byte 2 |
||||||
|
@max_byte_count 32 |
||||||
|
|
||||||
|
defstruct ~w(byte_count bytes)a |
||||||
|
|
||||||
@typedoc """ |
@typedoc """ |
||||||
[KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash as a string. |
A full [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash is #{@max_byte_count}, but it can also be truncated to |
||||||
|
fewer bytes. |
||||||
|
""" |
||||||
|
@type byte_count :: 1..unquote(@max_byte_count) |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
A module that implements this behaviour's callbacks |
||||||
|
""" |
||||||
|
@type t :: %__MODULE__{ |
||||||
|
byte_count: byte_count, |
||||||
|
bytes: <<_::_*8>> |
||||||
|
} |
||||||
|
|
||||||
|
@callback byte_count() :: byte_count() |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Number of bits in a byte |
||||||
|
""" |
||||||
|
def bits_per_byte, do: 8 |
||||||
|
|
||||||
|
@doc """ |
||||||
|
How many hexadecimal digits are used to represent a byte |
||||||
|
""" |
||||||
|
def hexadecimal_digits_per_byte, do: 2 |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Casts `term` to `t:t/0` using `c:byte_count/0` in `module` |
||||||
""" |
""" |
||||||
@type t :: String.t() |
@spec cast(module(), term()) :: {:ok, t()} | :error |
||||||
|
def cast(callback_module, term) when is_atom(callback_module) do |
||||||
|
byte_count = callback_module.byte_count() |
||||||
|
|
||||||
|
case term do |
||||||
|
%__MODULE__{byte_count: ^byte_count, bytes: <<_::big-integer-size(byte_count)-unit(@bits_per_byte)>>} = cast -> |
||||||
|
{:ok, cast} |
||||||
|
|
||||||
|
<<"0x", hexadecimal_digits::binary>> -> |
||||||
|
cast_hexadecimal_digits(hexadecimal_digits, byte_count) |
||||||
|
|
||||||
|
integer when is_integer(integer) -> |
||||||
|
cast_integer(integer, byte_count) |
||||||
|
|
||||||
|
_ -> |
||||||
|
:error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Dumps the `t` `bytes` to `:binary` (`bytea`) format used in database. |
||||||
|
""" |
||||||
|
@spec dump(module(), term()) :: {:ok, binary} | :error |
||||||
|
def dump(callback_module, term) when is_atom(callback_module) do |
||||||
|
byte_count = callback_module.byte_count() |
||||||
|
|
||||||
|
case term do |
||||||
|
# ensure inconsistent `t` with a different `byte_count` from the `callback_module` isn't dumped to the database, |
||||||
|
# in case `%__MODULE__{}` is set in a field value directly |
||||||
|
%__MODULE__{byte_count: ^byte_count, bytes: <<_::big-integer-size(byte_count)-unit(@bits_per_byte)>> = bytes} -> |
||||||
|
{:ok, bytes} |
||||||
|
|
||||||
|
_ -> |
||||||
|
:error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Loads the binary hash from the database into `t:t/0` if it has `c:byte_count/0` bytes from `callback_module`. |
||||||
|
""" |
||||||
|
@spec load(module(), term()) :: {:ok, t} | :error |
||||||
|
def load(callback_module, term) do |
||||||
|
byte_count = callback_module.byte_count() |
||||||
|
|
||||||
|
case term do |
||||||
|
# ensure that only hashes of `byte_count` that matches `callback_module` can be loaded back from database to |
||||||
|
# prevent using `Ecto.Type` with wrong byte_count on a database column |
||||||
|
<<_::big-integer-size(byte_count)-unit(@bits_per_byte)>> -> |
||||||
|
{:ok, %__MODULE__{byte_count: byte_count, bytes: term}} |
||||||
|
|
||||||
|
_ -> |
||||||
|
:error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts the `t:t/0` to the integer version of the hash |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.to_integer( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: |
||||||
|
...> big-integer-size(32)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b |
||||||
|
iex> Explorer.Chain.Hash.to_integer( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 20, |
||||||
|
...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed |
||||||
|
|
||||||
|
""" |
||||||
|
@spec to_integer(t()) :: pos_integer() |
||||||
|
def to_integer(%__MODULE__{byte_count: byte_count, bytes: bytes}) do |
||||||
|
<<integer::big-integer-size(byte_count)-unit(8)>> = bytes |
||||||
|
|
||||||
|
integer |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts the `t:t/0` to string representation shown to users. |
||||||
|
|
||||||
|
iex> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: |
||||||
|
...> big-integer-size(32)-unit(8)>> |
||||||
|
...> } |> |
||||||
|
...> Explorer.Chain.Hash.to_iodata() |> |
||||||
|
...> IO.iodata_to_binary() |
||||||
|
"0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" |
||||||
|
|
||||||
|
Always pads number, so that it is a valid format for casting. |
||||||
|
|
||||||
|
iex> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> |
||||||
|
...> } |> |
||||||
|
...> Explorer.Chain.Hash.to_iodata() |> |
||||||
|
...> IO.iodata_to_binary() |
||||||
|
"0x0000000000000000000000000000000000000000000000001234567890abcdef" |
||||||
|
|
||||||
|
""" |
||||||
|
@spec to_iodata(t) :: iodata() |
||||||
|
def to_iodata(%__MODULE__{byte_count: byte_count} = hash) do |
||||||
|
integer = to_integer(hash) |
||||||
|
hexadecimal_digit_count = byte_count_to_hexadecimal_digit_count(byte_count) |
||||||
|
unprefixed = :io_lib.format('~#{hexadecimal_digit_count}.16.0b', [integer]) |
||||||
|
|
||||||
|
["0x", unprefixed] |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Converts the `t:t/0` to string representation shown to users. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.to_string( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: |
||||||
|
...> big-integer-size(32)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
"0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" |
||||||
|
|
||||||
|
Always pads number, so that it is a valid format for casting. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.to_string( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
"0x0000000000000000000000000000000000000000000000001234567890abcdef" |
||||||
|
|
||||||
|
""" |
||||||
|
@spec to_string(t) :: String.t() |
||||||
|
def to_string(%__MODULE__{} = hash) do |
||||||
|
hash |
||||||
|
|> to_iodata() |
||||||
|
|> IO.iodata_to_binary() |
||||||
|
end |
||||||
|
|
||||||
|
defp byte_count_to_hexadecimal_digit_count(byte_count) do |
||||||
|
byte_count * @hexadecimal_digits_per_byte |
||||||
|
end |
||||||
|
|
||||||
|
defp byte_count_to_max_integer(byte_count) do |
||||||
|
(1 <<< (byte_count * @bits_per_byte + 1)) - 1 |
||||||
|
end |
||||||
|
|
||||||
|
defp cast_hexadecimal_digits(hexadecimal_digits, byte_count) when is_binary(hexadecimal_digits) do |
||||||
|
hexadecimal_digit_count = byte_count_to_hexadecimal_digit_count(byte_count) |
||||||
|
|
||||||
|
with ^hexadecimal_digit_count <- String.length(hexadecimal_digits), |
||||||
|
{integer, ""} <- Integer.parse(hexadecimal_digits, 16) do |
||||||
|
cast_integer(integer, byte_count) |
||||||
|
else |
||||||
|
_ -> :error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp cast_integer(integer, byte_count) when is_integer(integer) do |
||||||
|
max_integer = byte_count_to_max_integer(byte_count) |
||||||
|
|
||||||
|
case integer do |
||||||
|
in_range when 0 <= in_range and in_range <= max_integer -> |
||||||
|
{:ok, |
||||||
|
%__MODULE__{byte_count: byte_count, bytes: <<integer::big-integer-size(byte_count)-unit(@bits_per_byte)>>}} |
||||||
|
|
||||||
|
_ -> |
||||||
|
:error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defimpl String.Chars do |
||||||
|
def to_string(hash) do |
||||||
|
@for.to_string(hash) |
||||||
|
end |
||||||
|
end |
||||||
end |
end |
||||||
|
@ -0,0 +1,150 @@ |
|||||||
|
defmodule Explorer.Chain.Hash.Full do |
||||||
|
@moduledoc """ |
||||||
|
A 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash. |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.Chain.Hash |
||||||
|
|
||||||
|
@behaviour Ecto.Type |
||||||
|
@behaviour Hash |
||||||
|
|
||||||
|
@byte_count 32 |
||||||
|
@hexadecimal_digit_count Hash.hexadecimal_digits_per_byte() * @byte_count |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
A #{@byte_count}-byte hash of the `t:Explorer.Chain.Block.t/0` or `t:Explorer.Chain.Transaction.t/0`. |
||||||
|
""" |
||||||
|
@type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Casts `term` to `t:t/0` |
||||||
|
|
||||||
|
If the `term` is already in `t:t/0`, then it is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.cast( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: |
||||||
|
...> big-integer-size(32)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 32, |
||||||
|
bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
If the `term` is an `non_neg_integer`, then it is converted to `t:t/0` |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.cast(0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b) |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 32, |
||||||
|
bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
If the `non_neg_integer` is too large, then `:error` is returned. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.cast(0xffff_9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8) |
||||||
|
:error |
||||||
|
|
||||||
|
If the `term` is a `String.t` that starts with `0x`, then is converted to an integer and then to `t:t/0`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.cast("0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b") |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 32, |
||||||
|
bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
While `non_neg_integers` don't have to be the correct width (because zero padding it difficult with numbers), |
||||||
|
`String.t` format must always have #{@hexadecimal_digit_count} hexadecimal digits after the `0x` base prefix. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.cast("0x0") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec cast(term()) :: {:ok, t()} | :error |
||||||
|
def cast(term) do |
||||||
|
Hash.cast(__MODULE__, term) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Dumps the binary hash to `:binary` (`bytea`) format used in database. |
||||||
|
|
||||||
|
If the field from the struct is `t:t/0`, then it succeeds. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.dump( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: |
||||||
|
...> big-integer-size(32)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
{:ok, <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>>} |
||||||
|
|
||||||
|
If the field from the struct is an incorrect format such as `t:Explorer.Chain.Address.Hash.t/0`, `:error` is returned. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.dump( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 20, |
||||||
|
...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec dump(term()) :: {:ok, binary} | :error |
||||||
|
def dump(term) do |
||||||
|
Hash.dump(__MODULE__, term) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Loads the binary hash from the database. |
||||||
|
|
||||||
|
If the binary hash is the correct format, it is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.load( |
||||||
|
...> <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> |
||||||
|
...> ) |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 32, |
||||||
|
bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
If the binary hash is an incorrect format, such as if an `Explorer.Chain.Address.Hash` field is loaded, `:error` is |
||||||
|
returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Full.load( |
||||||
|
...> <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
...> ) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec load(term()) :: {:ok, t()} | :error |
||||||
|
def load(term) do |
||||||
|
Hash.load(__MODULE__, term) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The underlying database type: `binary`. `binary` is used because no Postgres integer type is 32 bytes long. |
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec type() :: :binary |
||||||
|
def type, do: :binary |
||||||
|
|
||||||
|
@impl Hash |
||||||
|
def byte_count, do: @byte_count |
||||||
|
end |
@ -0,0 +1,151 @@ |
|||||||
|
defmodule Explorer.Chain.Hash.Truncated do |
||||||
|
@moduledoc """ |
||||||
|
The address (40 (hex) characters / 160 bits / 20 bytes) is derived from the public key (128 (hex) characters / |
||||||
|
512 bits / 64 bytes) which is derived from the private key (64 (hex) characters / 256 bits / 32 bytes). |
||||||
|
|
||||||
|
The address is actually the last 40 characters of the keccak-256 hash of the public key with `0x` appended. |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.Chain.Hash |
||||||
|
|
||||||
|
@behaviour Ecto.Type |
||||||
|
@behaviour Hash |
||||||
|
|
||||||
|
@byte_count 20 |
||||||
|
@hexadecimal_digit_count Hash.hexadecimal_digits_per_byte() * @byte_count |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
A #{@byte_count}-byte hash of the address public key. |
||||||
|
""" |
||||||
|
@type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Casts `term` to `t:t/0`. |
||||||
|
|
||||||
|
If the `term` is already in `t:t/0`, then it is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.cast( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 20, |
||||||
|
...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 20, |
||||||
|
bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
If the `term` is an `non_neg_integer`, then it is converted to `t:t/0` |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.cast(0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed) |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 20, |
||||||
|
bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
If the `non_neg_integer` is too large, then `:error` is returned. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.cast(0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b) |
||||||
|
:error |
||||||
|
|
||||||
|
If the `term` is a `String.t` that starts with `0x`, then is converted to an integer and then to `t:t/0`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.cast("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 20, |
||||||
|
bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
While `non_neg_integers` don't have to be the correct width (because zero padding it difficult with numbers), |
||||||
|
`String.t` format must always have #{@hexadecimal_digit_count} digits after the `0x` base prefix. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.cast("0x0") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec cast(term()) :: {:ok, t()} | :error |
||||||
|
def cast(term) do |
||||||
|
Hash.cast(__MODULE__, term) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Dumps the binary hash to `:binary` (`bytea`) format used in database. |
||||||
|
|
||||||
|
If the field from the struct is `t:t/0`, then it succeeds |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.dump( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 20, |
||||||
|
...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
{:ok, <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>>} |
||||||
|
|
||||||
|
If the field from the struct is an incorrect format such as `t:Explorer.Chain.Hash.t/0`, `:error` is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.dump( |
||||||
|
...> %Explorer.Chain.Hash{ |
||||||
|
...> byte_count: 32, |
||||||
|
...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: |
||||||
|
...> big-integer-size(32)-unit(8)>> |
||||||
|
...> } |
||||||
|
...> ) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec dump(term()) :: {:ok, binary} | :error |
||||||
|
def dump(term) do |
||||||
|
Hash.dump(__MODULE__, term) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Loads the binary hash from the database. |
||||||
|
|
||||||
|
If the binary hash is the correct format, it is returned. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.load( |
||||||
|
...> <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
...> ) |
||||||
|
{ |
||||||
|
:ok, |
||||||
|
%Explorer.Chain.Hash{ |
||||||
|
byte_count: 20, |
||||||
|
bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
If the binary hash is an incorrect format, such as if an `Explorer.Chain.Hash` field is loaded, `:error` is returned. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Hash.Truncated.load( |
||||||
|
...> <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> |
||||||
|
...> ) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec load(term()) :: {:ok, t} | :error |
||||||
|
def load(term) do |
||||||
|
Hash.load(__MODULE__, term) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The underlying database type: `binary`. `binary` is used because no Postgres integer type is 20 bytes long. |
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec type() :: :binary |
||||||
|
def type, do: :binary |
||||||
|
|
||||||
|
@impl Hash |
||||||
|
def byte_count, do: @byte_count |
||||||
|
end |
@ -0,0 +1,116 @@ |
|||||||
|
defmodule Explorer.Chain.InternalTransaction.CallType do |
||||||
|
@moduledoc """ |
||||||
|
Internal transaction types |
||||||
|
""" |
||||||
|
|
||||||
|
@behaviour Ecto.Type |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `:call` - call a function in a contract by jumping into the contract's context |
||||||
|
* `:callcode` |
||||||
|
* `:delegatecall` - Instead of jumping into the code as with `"call"`, and using the call's contract's context, use |
||||||
|
the current contract's context with the delegated contract's code. There's some good chances for finding bugs |
||||||
|
when fuzzing these if the memory layout differs between the current contract and the delegated contract. |
||||||
|
* `:staticcall` |
||||||
|
""" |
||||||
|
@type t :: :call | :callcode | :delegatecall | :staticcall |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Casts `term` to `t:t/0` |
||||||
|
|
||||||
|
If the `term` is already in `t:t/0`, then it is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast(:call) |
||||||
|
{:ok, :call} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast(:callcode) |
||||||
|
{:ok, :callcode} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast(:delegatecall) |
||||||
|
{:ok, :delegatecall} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast(:staticcall) |
||||||
|
{:ok, :staticcall} |
||||||
|
|
||||||
|
If `term` is a `String.t`, then it is converted to the corresponding `t:t/0`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast("call") |
||||||
|
{:ok, :call} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast("callcode") |
||||||
|
{:ok, :callcode} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast("delegatecall") |
||||||
|
{:ok, :delegatecall} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast("staticcall") |
||||||
|
{:ok, :staticcall} |
||||||
|
|
||||||
|
Unsupported `String.t` return an `:error`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.cast("hard-fork") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec cast(term()) :: {:ok, t()} | :error |
||||||
|
def cast(t) when t in ~w(call callcode delegatecall staticcall)a, do: {:ok, t} |
||||||
|
def cast("call"), do: {:ok, :call} |
||||||
|
def cast("callcode"), do: {:ok, :callcode} |
||||||
|
def cast("delegatecall"), do: {:ok, :delegatecall} |
||||||
|
def cast("staticcall"), do: {:ok, :staticcall} |
||||||
|
def cast(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Dumps the `atom` format to `String.t` format used in the database. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.dump(:call) |
||||||
|
{:ok, "call"} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.dump(:callcode) |
||||||
|
{:ok, "callcode"} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.dump(:delegatecall) |
||||||
|
{:ok, "delegatecall"} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.dump(:staticcall) |
||||||
|
{:ok, "staticcall"} |
||||||
|
|
||||||
|
Other atoms return an error |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.dump(:other) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec dump(term()) :: {:ok, String.t()} | :error |
||||||
|
def dump(:call), do: {:ok, "call"} |
||||||
|
def dump(:callcode), do: {:ok, "callcode"} |
||||||
|
def dump(:delegatecall), do: {:ok, "delegatecall"} |
||||||
|
def dump(:staticcall), do: {:ok, "staticcall"} |
||||||
|
def dump(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Loads the `t:String.t/0` from the database. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.load("call") |
||||||
|
{:ok, :call} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.load("callcode") |
||||||
|
{:ok, :callcode} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.load("delegatecall") |
||||||
|
{:ok, :delegatecall} |
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.load("staticcall") |
||||||
|
{:ok, :staticcall} |
||||||
|
|
||||||
|
Other `t:String.t/0` return `:error` |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.CallType.load("other") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec load(term()) :: {:ok, t()} | :error |
||||||
|
def load("call"), do: {:ok, :call} |
||||||
|
def load("callcode"), do: {:ok, :callcode} |
||||||
|
def load("delegatecall"), do: {:ok, :delegatecall} |
||||||
|
def load("staticcall"), do: {:ok, :staticcall} |
||||||
|
def load(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The underlying database type: `:string` |
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec type() :: :string |
||||||
|
def type, do: :string |
||||||
|
end |
@ -0,0 +1,114 @@ |
|||||||
|
defmodule Explorer.Chain.InternalTransaction.Type do |
||||||
|
@moduledoc """ |
||||||
|
Internal transaction types |
||||||
|
""" |
||||||
|
|
||||||
|
@behaviour Ecto.Type |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `:call` |
||||||
|
* `:create` |
||||||
|
* `:reward` |
||||||
|
* `:suicide` |
||||||
|
""" |
||||||
|
@type t :: :call | :create | :reward | :suicide |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Casts `term` to `t:t/0` |
||||||
|
|
||||||
|
If the `term` is already in `t:t/0`, then it is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast(:call) |
||||||
|
{:ok, :call} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast(:create) |
||||||
|
{:ok, :create} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast(:reward) |
||||||
|
{:ok, :reward} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast(:suicide) |
||||||
|
{:ok, :suicide} |
||||||
|
|
||||||
|
If `term` is a `String.t`, then it is converted to the corresponding `t:t/0`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast("call") |
||||||
|
{:ok, :call} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast("create") |
||||||
|
{:ok, :create} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast("reward") |
||||||
|
{:ok, :reward} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast("suicide") |
||||||
|
{:ok, :suicide} |
||||||
|
|
||||||
|
Unsupported `String.t` return an `:error`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.cast("hard-fork") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec cast(term()) :: {:ok, t()} | :error |
||||||
|
def cast(t) when t in ~w(call create suicide reward)a, do: {:ok, t} |
||||||
|
def cast("call"), do: {:ok, :call} |
||||||
|
def cast("create"), do: {:ok, :create} |
||||||
|
def cast("reward"), do: {:ok, :reward} |
||||||
|
def cast("suicide"), do: {:ok, :suicide} |
||||||
|
def cast(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Dumps the `atom` format to `String.t` format used in the database. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.dump(:call) |
||||||
|
{:ok, "call"} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.dump(:create) |
||||||
|
{:ok, "create"} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.dump(:reward) |
||||||
|
{:ok, "reward"} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.dump(:suicide) |
||||||
|
{:ok, "suicide"} |
||||||
|
|
||||||
|
Other atoms return an error |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.dump(:other) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec dump(term()) :: {:ok, String.t()} | :error |
||||||
|
def dump(:call), do: {:ok, "call"} |
||||||
|
def dump(:create), do: {:ok, "create"} |
||||||
|
def dump(:reward), do: {:ok, "reward"} |
||||||
|
def dump(:suicide), do: {:ok, "suicide"} |
||||||
|
def dump(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Loads the `t:String.t/0` from the database. |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.load("call") |
||||||
|
{:ok, :call} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.load("create") |
||||||
|
{:ok, :create} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.load("reward") |
||||||
|
{:ok, :reward} |
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.load("suicide") |
||||||
|
{:ok, :suicide} |
||||||
|
|
||||||
|
Other `t:String.t/0` return `:error` |
||||||
|
|
||||||
|
iex> Explorer.Chain.InternalTransaction.Type.load("other") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec load(term()) :: {:ok, t()} | :error |
||||||
|
def load("call"), do: {:ok, :call} |
||||||
|
def load("create"), do: {:ok, :create} |
||||||
|
def load("reward"), do: {:ok, :reward} |
||||||
|
def load("suicide"), do: {:ok, :suicide} |
||||||
|
def load(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The underlying database type: `:string` |
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec type() :: :string |
||||||
|
def type, do: :string |
||||||
|
end |
@ -0,0 +1,109 @@ |
|||||||
|
defmodule Explorer.Chain.Receipt.Status do |
||||||
|
@moduledoc """ |
||||||
|
Whether a transaction succeeded (`:ok`) or failed (`:error`). |
||||||
|
|
||||||
|
Post-[Byzantium](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-609.md) status is `0x1` for success and `0x0` |
||||||
|
for failure, but instead of keeping track of just an integer and having to remember if its like C boolean (`0` for |
||||||
|
`false`, `1` for `true`) or a Posix exit code, let's represent it as native elixir - `:ok` for success and `:error` |
||||||
|
for failure. |
||||||
|
""" |
||||||
|
|
||||||
|
@behaviour Ecto.Type |
||||||
|
|
||||||
|
@typedoc """ |
||||||
|
* `:ok` - transaction succeeded |
||||||
|
* `:error` - transaction failed |
||||||
|
""" |
||||||
|
@type t :: :ok | :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Casts `term` to `t:t/0` |
||||||
|
|
||||||
|
If the `term` is already in `t:t/0`, then it is returned |
||||||
|
|
||||||
|
iex> Explorer.Chain.Receipt.Status.cast(:ok) |
||||||
|
{:ok, :ok} |
||||||
|
iex> Explorer.Chain.Receipt.Status.cast(:error) |
||||||
|
{:ok, :error} |
||||||
|
|
||||||
|
If the `term` is an `non_neg_integer`, then it is converted only if it is `0` or `1`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Receipt.Status.cast(0) |
||||||
|
{:ok, :error} |
||||||
|
iex> Explorer.Chain.Receipt.Status.cast(1) |
||||||
|
{:ok, :ok} |
||||||
|
iex> Explorer.Chain.Receipt.Status.cast(2) |
||||||
|
:error |
||||||
|
|
||||||
|
If the `term` is in the quantity format used by `Explorer.JSONRPC`, it is converted only if `0x0` or `0x1` |
||||||
|
|
||||||
|
iex> Explorer.Chain.Receipt.Status.cast("0x0") |
||||||
|
{:ok, :error} |
||||||
|
iex> Explorer.Chain.Receipt.Status.cast("0x1") |
||||||
|
{:ok, :ok} |
||||||
|
iex> Explorer.Chain.Receipt.Status.cast("0x2") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec cast(term()) :: {:ok, t()} | :error |
||||||
|
def cast(:error), do: {:ok, :error} |
||||||
|
def cast(:ok), do: {:ok, :ok} |
||||||
|
def cast(0), do: {:ok, :error} |
||||||
|
def cast(1), do: {:ok, :ok} |
||||||
|
def cast("0x0"), do: {:ok, :error} |
||||||
|
def cast("0x1"), do: {:ok, :ok} |
||||||
|
def cast(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Dumps the `atom` format to `integer` format used in database. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Receipt.Status.dump(:ok) |
||||||
|
{:ok, 1} |
||||||
|
iex> Explorer.Chain.Receipt.Status.dump(:error) |
||||||
|
{:ok, 0} |
||||||
|
|
||||||
|
If the value hasn't been cast first, it can't be dumped. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Receipt.Status.dump(0) |
||||||
|
:error |
||||||
|
iex> Explorer.Chain.Receipt.Status.dump(1) |
||||||
|
:error |
||||||
|
iex> Explorer.Chain.Receipt.Status.dump("0x0") |
||||||
|
:error |
||||||
|
iex> Explorer.Chain.Receipt.Status.dump("0x1") |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec dump(term()) :: {:ok, 0 | 1} | :error |
||||||
|
def dump(:error), do: {:ok, 0} |
||||||
|
def dump(:ok), do: {:ok, 1} |
||||||
|
def dump(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Loads the integer from the database. |
||||||
|
|
||||||
|
Only loads integers `0` and `1`. |
||||||
|
|
||||||
|
iex> Explorer.Chain.Receipt.Status.load(0) |
||||||
|
{:ok, :error} |
||||||
|
iex> Explorer.Chain.Receipt.Status.load(1) |
||||||
|
{:ok, :ok} |
||||||
|
iex> Explorer.Chain.Receipt.Status.load(2) |
||||||
|
:error |
||||||
|
|
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec load(term()) :: {:ok, t()} | :error |
||||||
|
def load(0), do: {:ok, :error} |
||||||
|
def load(1), do: {:ok, :ok} |
||||||
|
def load(_), do: :error |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The underlying database type: `:integer` |
||||||
|
""" |
||||||
|
@impl Ecto.Type |
||||||
|
@spec type() :: :integer |
||||||
|
def type, do: :integer |
||||||
|
end |
@ -1,20 +0,0 @@ |
|||||||
defmodule Explorer.Chain.ToAddress do |
|
||||||
@moduledoc false |
|
||||||
|
|
||||||
use Explorer.Schema |
|
||||||
|
|
||||||
alias Explorer.Chain.{Address, Transaction} |
|
||||||
|
|
||||||
@primary_key false |
|
||||||
schema "to_addresses" do |
|
||||||
belongs_to(:address, Address) |
|
||||||
belongs_to(:transaction, Transaction, primary_key: true) |
|
||||||
timestamps() |
|
||||||
end |
|
||||||
|
|
||||||
def changeset(%__MODULE__{} = to_address, attrs \\ %{}) do |
|
||||||
to_address |
|
||||||
|> cast(attrs, [:transaction_id, :address_id]) |
|
||||||
|> unique_constraint(:transaction_id, name: :to_addresses_transaction_id_index) |
|
||||||
end |
|
||||||
end |
|
@ -1,19 +0,0 @@ |
|||||||
defmodule Explorer.Ethereum do |
|
||||||
@client Application.get_env(:explorer, :ethereum)[:backend] |
|
||||||
|
|
||||||
defmodule API do |
|
||||||
@moduledoc false |
|
||||||
@callback download_balance(String.t()) :: String.t() |
|
||||||
end |
|
||||||
|
|
||||||
defdelegate download_balance(hash), to: @client |
|
||||||
|
|
||||||
def decode_integer_field(hex) do |
|
||||||
{"0x", base_16} = String.split_at(hex, 2) |
|
||||||
String.to_integer(base_16, 16) |
|
||||||
end |
|
||||||
|
|
||||||
def decode_time_field(field) do |
|
||||||
field |> decode_integer_field() |> Timex.from_unix() |
|
||||||
end |
|
||||||
end |
|
@ -1,14 +0,0 @@ |
|||||||
defmodule Explorer.Ethereum.Live do |
|
||||||
@moduledoc """ |
|
||||||
An implementation for Ethereum that uses the actual node. |
|
||||||
""" |
|
||||||
|
|
||||||
@behaviour Explorer.Ethereum.API |
|
||||||
|
|
||||||
import Ethereumex.HttpClient, only: [eth_get_balance: 1] |
|
||||||
|
|
||||||
def download_balance(hash) do |
|
||||||
{:ok, result} = eth_get_balance(hash) |
|
||||||
result |
|
||||||
end |
|
||||||
end |
|
@ -1,9 +0,0 @@ |
|||||||
defmodule Explorer.Ethereum.Test do |
|
||||||
@moduledoc """ |
|
||||||
An interface for the Ethereum node that does not hit the network |
|
||||||
""" |
|
||||||
@behaviour Explorer.Ethereum.API |
|
||||||
def download_balance(_hash) do |
|
||||||
"0x15d231fca629c7c0" |
|
||||||
end |
|
||||||
end |
|
@ -1,14 +0,0 @@ |
|||||||
defmodule Explorer.EthereumexExtensions do |
|
||||||
@moduledoc """ |
|
||||||
Downloads the trace for a Transaction from a node. |
|
||||||
""" |
|
||||||
|
|
||||||
alias Ethereumex.HttpClient |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, trace_transaction: 1} |
|
||||||
def trace_transaction(hash) do |
|
||||||
params = [hash, ["trace"]] |
|
||||||
{:ok, trace} = HttpClient.request("trace_replayTransaction", params, []) |
|
||||||
trace |
|
||||||
end |
|
||||||
end |
|
@ -1,5 +0,0 @@ |
|||||||
defmodule Explorer.ExqNodeIdentifier do |
|
||||||
@behaviour Exq.NodeIdentifier.Behaviour |
|
||||||
@moduledoc "Configure Exq with the current dyno name" |
|
||||||
def node_id, do: System.get_env("DYNO") |
|
||||||
end |
|
@ -1,17 +0,0 @@ |
|||||||
defmodule Explorer.BalanceImporter do |
|
||||||
@moduledoc "Imports a balance for a given address." |
|
||||||
|
|
||||||
alias Explorer.{Chain, Ethereum} |
|
||||||
|
|
||||||
def import(hash) do |
|
||||||
encoded_balance = Ethereum.download_balance(hash) |
|
||||||
|
|
||||||
persist_balance(hash, encoded_balance) |
|
||||||
end |
|
||||||
|
|
||||||
defp persist_balance(hash, encoded_balance) when is_binary(hash) do |
|
||||||
decoded_balance = Ethereum.decode_integer_field(encoded_balance) |
|
||||||
|
|
||||||
Chain.update_balance(hash, decoded_balance) |
|
||||||
end |
|
||||||
end |
|
@ -1,81 +0,0 @@ |
|||||||
defmodule Explorer.BlockImporter do |
|
||||||
@moduledoc "Imports a block." |
|
||||||
|
|
||||||
import Ecto.Query |
|
||||||
import Ethereumex.HttpClient, only: [eth_get_block_by_number: 2] |
|
||||||
|
|
||||||
alias Explorer.{BlockImporter, Ethereum} |
|
||||||
alias Explorer.Chain.Block |
|
||||||
alias Explorer.Repo.NewRelic, as: Repo |
|
||||||
alias Explorer.Workers.ImportTransaction |
|
||||||
|
|
||||||
def import(raw_block) when is_map(raw_block) do |
|
||||||
changes = extract_block(raw_block) |
|
||||||
block = changes.hash |> find() |
|
||||||
|
|
||||||
if is_nil(block.id), do: block |> Block.changeset(changes) |> Repo.insert() |
|
||||||
|
|
||||||
Enum.map(raw_block["transactions"], &ImportTransaction.perform/1) |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, import: 1} |
|
||||||
def import("pending") do |
|
||||||
raw_block = download_block("pending") |
|
||||||
Enum.map(raw_block["transactions"], &ImportTransaction.perform_later/1) |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, import: 1} |
|
||||||
def import(block_number) do |
|
||||||
block_number |> download_block() |> BlockImporter.import() |
|
||||||
end |
|
||||||
|
|
||||||
def find(hash) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
b in Block, |
|
||||||
where: fragment("lower(?)", b.hash) == ^String.downcase(hash), |
|
||||||
limit: 1 |
|
||||||
) |
|
||||||
|
|
||||||
query |> Repo.one() || %Block{} |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, download_block: 1} |
|
||||||
def download_block(block_number) do |
|
||||||
{:ok, block} = |
|
||||||
block_number |
|
||||||
|> encode_number() |
|
||||||
|> eth_get_block_by_number(true) |
|
||||||
|
|
||||||
block |
|
||||||
end |
|
||||||
|
|
||||||
def extract_block(raw_block) do |
|
||||||
%{ |
|
||||||
hash: raw_block["hash"], |
|
||||||
number: raw_block["number"] |> Ethereum.decode_integer_field(), |
|
||||||
gas_used: raw_block["gasUsed"] |> Ethereum.decode_integer_field(), |
|
||||||
timestamp: raw_block["timestamp"] |> Ethereum.decode_time_field(), |
|
||||||
parent_hash: raw_block["parentHash"], |
|
||||||
miner: raw_block["miner"], |
|
||||||
difficulty: raw_block["difficulty"] |> Ethereum.decode_integer_field(), |
|
||||||
total_difficulty: raw_block["totalDifficulty"] |> Ethereum.decode_integer_field(), |
|
||||||
size: raw_block["size"] |> Ethereum.decode_integer_field(), |
|
||||||
gas_limit: raw_block["gasLimit"] |> Ethereum.decode_integer_field(), |
|
||||||
nonce: raw_block["nonce"] || "0" |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
defp encode_number("latest"), do: "latest" |
|
||||||
defp encode_number("earliest"), do: "earliest" |
|
||||||
defp encode_number("pending"), do: "pending" |
|
||||||
defp encode_number("0x" <> number) when is_binary(number), do: number |
|
||||||
|
|
||||||
defp encode_number(number) when is_binary(number) do |
|
||||||
number |
|
||||||
|> String.to_integer() |
|
||||||
|> encode_number() |
|
||||||
end |
|
||||||
|
|
||||||
defp encode_number(number), do: "0x" <> Integer.to_string(number, 16) |
|
||||||
end |
|
@ -1,80 +0,0 @@ |
|||||||
defmodule Explorer.InternalTransactionImporter do |
|
||||||
@moduledoc "Imports a transaction's internal transactions given its hash." |
|
||||||
|
|
||||||
import Ecto.Query |
|
||||||
|
|
||||||
alias Explorer.{Chain, Ethereum, EthereumexExtensions, Repo} |
|
||||||
alias Explorer.Chain.{InternalTransaction, Transaction} |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, import: 1} |
|
||||||
def import(hash) do |
|
||||||
transaction = find_transaction(hash) |
|
||||||
|
|
||||||
hash |
|
||||||
|> download_trace |
|
||||||
|> extract_attrs |
|
||||||
|> persist_internal_transactions(transaction) |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, download_trace: 1} |
|
||||||
defp download_trace(hash) do |
|
||||||
EthereumexExtensions.trace_transaction(hash) |
|
||||||
end |
|
||||||
|
|
||||||
defp find_transaction(hash) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
t in Transaction, |
|
||||||
where: fragment("lower(?)", t.hash) == ^String.downcase(hash), |
|
||||||
limit: 1 |
|
||||||
) |
|
||||||
|
|
||||||
Repo.one!(query) |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, extract_attrs: 1} |
|
||||||
defp extract_attrs(attrs) do |
|
||||||
trace = attrs["trace"] |
|
||||||
trace |> Enum.with_index() |> Enum.map(&extract_trace/1) |
|
||||||
end |
|
||||||
|
|
||||||
def extract_trace({trace, index}) do |
|
||||||
%{ |
|
||||||
index: index, |
|
||||||
call_type: trace["action"]["callType"] || trace["type"], |
|
||||||
to_address_id: trace |> to_address() |> address_id(), |
|
||||||
from_address_id: trace |> from_address() |> address_id(), |
|
||||||
trace_address: trace["traceAddress"], |
|
||||||
value: trace["action"]["value"] |> Ethereum.decode_integer_field(), |
|
||||||
gas: trace["action"]["gas"] |> Ethereum.decode_integer_field(), |
|
||||||
gas_used: trace["result"]["gasUsed"] |> Ethereum.decode_integer_field(), |
|
||||||
input: trace["action"]["input"], |
|
||||||
output: trace["result"]["output"] |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
defp to_address(%{"action" => %{"to" => address}}) |
|
||||||
when not is_nil(address), |
|
||||||
do: address |
|
||||||
|
|
||||||
defp to_address(%{"result" => %{"address" => address}}), do: address |
|
||||||
|
|
||||||
defp from_address(%{"action" => %{"from" => address}}), do: address |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, persist_internal_transactions: 2} |
|
||||||
defp persist_internal_transactions(traces, transaction) do |
|
||||||
Enum.map(traces, fn trace -> |
|
||||||
trace = Map.merge(trace, %{transaction_id: transaction.id}) |
|
||||||
|
|
||||||
%InternalTransaction{} |
|
||||||
|> InternalTransaction.changeset(trace) |
|
||||||
|> Repo.insert() |
|
||||||
end) |
|
||||||
end |
|
||||||
|
|
||||||
defp address_id(hash) do |
|
||||||
{:ok, address} = Chain.ensure_hash_address(hash) |
|
||||||
|
|
||||||
address.id |
|
||||||
end |
|
||||||
end |
|
@ -1,79 +0,0 @@ |
|||||||
defmodule Explorer.ReceiptImporter do |
|
||||||
@moduledoc "Imports a transaction receipt given a transaction hash." |
|
||||||
|
|
||||||
import Ecto.Query |
|
||||||
import Ethereumex.HttpClient, only: [eth_get_transaction_receipt: 1] |
|
||||||
|
|
||||||
alias Explorer.{Chain, Repo} |
|
||||||
alias Explorer.Chain.{Receipt, Transaction} |
|
||||||
|
|
||||||
def import(hash) do |
|
||||||
transaction = hash |> find_transaction() |
|
||||||
|
|
||||||
hash |
|
||||||
|> download_receipt() |
|
||||||
|> extract_receipt() |
|
||||||
|> Map.put(:transaction_id, transaction.id) |
|
||||||
|> save_receipt() |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, download_receipt: 1} |
|
||||||
defp download_receipt(hash) do |
|
||||||
{:ok, receipt} = eth_get_transaction_receipt(hash) |
|
||||||
receipt || %{} |
|
||||||
end |
|
||||||
|
|
||||||
defp find_transaction(hash) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
transaction in Transaction, |
|
||||||
left_join: receipt in assoc(transaction, :receipt), |
|
||||||
where: fragment("lower(?)", transaction.hash) == ^hash, |
|
||||||
where: is_nil(receipt.id), |
|
||||||
limit: 1 |
|
||||||
) |
|
||||||
|
|
||||||
Repo.one(query) || Transaction.null() |
|
||||||
end |
|
||||||
|
|
||||||
defp save_receipt(receipt) do |
|
||||||
unless is_nil(receipt.transaction_id) do |
|
||||||
%Receipt{} |
|
||||||
|> Receipt.changeset(receipt) |
|
||||||
|> Repo.insert() |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
defp extract_receipt(receipt) do |
|
||||||
logs = receipt["logs"] || [] |
|
||||||
|
|
||||||
%{ |
|
||||||
index: receipt["transactionIndex"] |> decode_integer_field(), |
|
||||||
cumulative_gas_used: receipt["cumulativeGasUsed"] |> decode_integer_field(), |
|
||||||
gas_used: receipt["gasUsed"] |> decode_integer_field(), |
|
||||||
status: receipt["status"] |> decode_integer_field(), |
|
||||||
logs: logs |> Enum.map(&extract_log/1) |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
defp extract_log(log) do |
|
||||||
{:ok, address} = Chain.ensure_hash_address(log["address"]) |
|
||||||
|
|
||||||
%{ |
|
||||||
address_id: address.id, |
|
||||||
index: log["logIndex"] |> decode_integer_field(), |
|
||||||
data: log["data"], |
|
||||||
type: log["type"], |
|
||||||
first_topic: log["topics"] |> Enum.at(0), |
|
||||||
second_topic: log["topics"] |> Enum.at(1), |
|
||||||
third_topic: log["topics"] |> Enum.at(2), |
|
||||||
fourth_topic: log["topics"] |> Enum.at(3) |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
defp decode_integer_field("0x" <> hex) when is_binary(hex) do |
|
||||||
String.to_integer(hex, 16) |
|
||||||
end |
|
||||||
|
|
||||||
defp decode_integer_field(field), do: field |
|
||||||
end |
|
@ -1,142 +0,0 @@ |
|||||||
defmodule Explorer.TransactionImporter do |
|
||||||
@moduledoc "Imports a transaction given a unique hash." |
|
||||||
|
|
||||||
import Ecto.Query |
|
||||||
import Ethereumex.HttpClient, only: [eth_get_transaction_by_hash: 1] |
|
||||||
|
|
||||||
alias Explorer.{Chain, Ethereum, Repo, BalanceImporter} |
|
||||||
alias Explorer.Chain.{Block, BlockTransaction, Transaction} |
|
||||||
|
|
||||||
def import(hash) when is_binary(hash) do |
|
||||||
hash |> download_transaction() |> persist_transaction() |
|
||||||
end |
|
||||||
|
|
||||||
def import(raw_transaction) when is_map(raw_transaction) do |
|
||||||
persist_transaction(raw_transaction) |
|
||||||
end |
|
||||||
|
|
||||||
def persist_transaction(raw_transaction) do |
|
||||||
found_transaction = raw_transaction["hash"] |> find() |
|
||||||
|
|
||||||
transaction = |
|
||||||
case is_nil(found_transaction.id) do |
|
||||||
false -> |
|
||||||
found_transaction |
|
||||||
|
|
||||||
true -> |
|
||||||
to_address = |
|
||||||
raw_transaction |
|
||||||
|> to_address() |
|
||||||
|> fetch_address() |
|
||||||
|
|
||||||
from_address = |
|
||||||
raw_transaction |
|
||||||
|> from_address() |
|
||||||
|> fetch_address() |
|
||||||
|
|
||||||
changes = |
|
||||||
raw_transaction |
|
||||||
|> extract_attrs() |
|
||||||
|> Map.put(:to_address_id, to_address.id) |
|
||||||
|> Map.put(:from_address_id, from_address.id) |
|
||||||
|
|
||||||
found_transaction |> Transaction.changeset(changes) |> Repo.insert!() |
|
||||||
end |
|
||||||
|
|
||||||
transaction |
|
||||||
|> create_block_transaction(raw_transaction["blockHash"]) |
|
||||||
|
|
||||||
refresh_account_balances(raw_transaction) |
|
||||||
|
|
||||||
transaction |
|
||||||
end |
|
||||||
|
|
||||||
def find(hash) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
t in Transaction, |
|
||||||
where: fragment("lower(?)", t.hash) == ^String.downcase(hash), |
|
||||||
limit: 1 |
|
||||||
) |
|
||||||
|
|
||||||
query |> Repo.one() || %Transaction{} |
|
||||||
end |
|
||||||
|
|
||||||
def download_transaction(hash) do |
|
||||||
{:ok, payload} = eth_get_transaction_by_hash(hash) |
|
||||||
payload |
|
||||||
end |
|
||||||
|
|
||||||
def extract_attrs(raw_transaction) do |
|
||||||
%{ |
|
||||||
hash: raw_transaction["hash"], |
|
||||||
value: raw_transaction["value"] |> Ethereum.decode_integer_field(), |
|
||||||
gas: raw_transaction["gas"] |> Ethereum.decode_integer_field(), |
|
||||||
gas_price: raw_transaction["gasPrice"] |> Ethereum.decode_integer_field(), |
|
||||||
input: raw_transaction["input"], |
|
||||||
nonce: raw_transaction["nonce"] |> Ethereum.decode_integer_field(), |
|
||||||
public_key: raw_transaction["publicKey"], |
|
||||||
r: raw_transaction["r"], |
|
||||||
s: raw_transaction["s"], |
|
||||||
standard_v: raw_transaction["standardV"], |
|
||||||
transaction_index: raw_transaction["transactionIndex"], |
|
||||||
v: raw_transaction["v"] |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
def create_block_transaction(transaction, hash) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
t in Block, |
|
||||||
where: fragment("lower(?)", t.hash) == ^String.downcase(hash), |
|
||||||
limit: 1 |
|
||||||
) |
|
||||||
|
|
||||||
block = query |> Repo.one() |
|
||||||
|
|
||||||
if block do |
|
||||||
changes = %{block_id: block.id, transaction_id: transaction.id} |
|
||||||
|
|
||||||
case Repo.get_by(BlockTransaction, transaction_id: transaction.id) do |
|
||||||
nil -> |
|
||||||
%BlockTransaction{} |
|
||||||
|> BlockTransaction.changeset(changes) |
|
||||||
|> Repo.insert() |
|
||||||
|
|
||||||
block_transaction -> |
|
||||||
block_transaction |
|
||||||
|> BlockTransaction.changeset(%{block_id: block.id}) |
|
||||||
|> Repo.update() |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
transaction |
|
||||||
end |
|
||||||
|
|
||||||
def to_address(%{"to" => to}) when not is_nil(to), do: to |
|
||||||
def to_address(%{"creates" => creates}) when not is_nil(creates), do: creates |
|
||||||
def to_address(hash) when is_bitstring(hash), do: hash |
|
||||||
|
|
||||||
def from_address(%{"from" => from}), do: from |
|
||||||
def from_address(hash) when is_bitstring(hash), do: hash |
|
||||||
|
|
||||||
def fetch_address(hash) when is_bitstring(hash) do |
|
||||||
{:ok, address} = Chain.ensure_hash_address(hash) |
|
||||||
|
|
||||||
address |
|
||||||
end |
|
||||||
|
|
||||||
defp refresh_account_balances(raw_transaction) do |
|
||||||
raw_transaction |
|
||||||
|> to_address() |
|
||||||
|> update_balance() |
|
||||||
|
|
||||||
raw_transaction |
|
||||||
|> from_address() |
|
||||||
|> update_balance() |
|
||||||
end |
|
||||||
|
|
||||||
defp update_balance(address_hash) do |
|
||||||
BalanceImporter.import(address_hash) |
|
||||||
end |
|
||||||
end |
|
@ -0,0 +1,55 @@ |
|||||||
|
defmodule Explorer.Indexer do |
||||||
|
@moduledoc """ |
||||||
|
Indexes an Ethereum-based chain using JSONRPC. |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The maximum `t:Explorer.Chain.Block.t/0` `number` that was indexed |
||||||
|
|
||||||
|
If blocks are skipped and inserted out of number order, the max number is still returned |
||||||
|
|
||||||
|
iex> insert(:block, number: 2) |
||||||
|
iex> insert(:block, number: 1) |
||||||
|
iex> Explorer.Indexer.max_block_number() |
||||||
|
2 |
||||||
|
|
||||||
|
If there are no blocks, `0` is returned to indicate to index from genesis block. |
||||||
|
|
||||||
|
iex> Explorer.Indexer.max_block_number() |
||||||
|
0 |
||||||
|
|
||||||
|
""" |
||||||
|
def max_block_number do |
||||||
|
case Chain.max_block_number() do |
||||||
|
{:ok, number} -> number |
||||||
|
{:error, :not_found} -> 0 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
The next `t:Explorer.Chain.Block.t/0` `number` that needs to be indexed (excluding skipped blocks) |
||||||
|
|
||||||
|
When there are no blocks the next block is the 0th block |
||||||
|
|
||||||
|
iex> Explorer.Indexer.max_block_number() |
||||||
|
0 |
||||||
|
iex> Explorer.Indexer.next_block_number() |
||||||
|
0 |
||||||
|
|
||||||
|
When there is a block, it is the successive block number |
||||||
|
|
||||||
|
iex> insert(:block, number: 2) |
||||||
|
iex> insert(:block, number: 1) |
||||||
|
iex> Explorer.Indexer.next_block_number() |
||||||
|
3 |
||||||
|
|
||||||
|
""" |
||||||
|
def next_block_number do |
||||||
|
case max_block_number() do |
||||||
|
0 -> 0 |
||||||
|
num -> num + 1 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,157 @@ |
|||||||
|
defmodule Explorer.Indexer.AddressFetcher do |
||||||
|
@moduledoc """ |
||||||
|
Fetches and indexes `t:Explorer.Chain.Address.t/0` balances. |
||||||
|
""" |
||||||
|
use GenServer |
||||||
|
require Logger |
||||||
|
|
||||||
|
alias EthereumJSONRPC |
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.{Address, Hash} |
||||||
|
|
||||||
|
@fetch_interval :timer.seconds(3) |
||||||
|
@max_batch_size 100 |
||||||
|
@max_concurrency 2 |
||||||
|
|
||||||
|
def async_fetch_balances(address_hashes) do |
||||||
|
GenServer.cast(__MODULE__, {:buffer_addresses, address_hashes}) |
||||||
|
end |
||||||
|
|
||||||
|
def start_link(opts) do |
||||||
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
def init(opts) do |
||||||
|
opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) |
||||||
|
send(self(), :fetch_unfetched_addresses) |
||||||
|
|
||||||
|
state = %{ |
||||||
|
debug_logs: Keyword.get(opts, :debug_logs, false), |
||||||
|
flush_timer: nil, |
||||||
|
fetch_interval: Keyword.get(opts, :fetch_interval, @fetch_interval), |
||||||
|
max_batch_size: Keyword.get(opts, :max_batch_size, @max_batch_size), |
||||||
|
buffer: :queue.new(), |
||||||
|
tasks: %{} |
||||||
|
} |
||||||
|
|
||||||
|
{:ok, state} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info(:fetch_unfetched_addresses, state) do |
||||||
|
{:noreply, stream_unfetched_addresses(state)} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info(:flush, state) do |
||||||
|
{:noreply, state |> fetch_next_batch([]) |> schedule_next_buffer_flush()} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:async_fetch, hashes}, state) do |
||||||
|
{:noreply, fetch_next_batch(state, hashes)} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({ref, {:fetched_balances, results}}, state) do |
||||||
|
:ok = Chain.update_balances(results) |
||||||
|
{:noreply, drop_task(state, ref)} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, _ref, :process, _pid, :normal}, state) do |
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do |
||||||
|
batch = Map.fetch!(state.tasks, ref) |
||||||
|
|
||||||
|
new_state = |
||||||
|
state |
||||||
|
|> drop_task(ref) |
||||||
|
|> buffer_addresses(batch) |
||||||
|
|
||||||
|
{:noreply, new_state} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_cast({:buffer_addresses, address_hashes}, state) do |
||||||
|
string_hashes = for hash <- address_hashes, do: Hash.to_string(hash) |
||||||
|
{:noreply, buffer_addresses(state, string_hashes)} |
||||||
|
end |
||||||
|
|
||||||
|
defp drop_task(state, ref) do |
||||||
|
schedule_async_fetch([]) |
||||||
|
%{state | tasks: Map.delete(state.tasks, ref)} |
||||||
|
end |
||||||
|
|
||||||
|
defp buffer_addresses(state, string_hashes) do |
||||||
|
%{state | buffer: :queue.join(state.buffer, :queue.from_list(string_hashes))} |
||||||
|
end |
||||||
|
|
||||||
|
defp stream_unfetched_addresses(state) do |
||||||
|
state.buffer |
||||||
|
|> Chain.stream_unfetched_addresses(fn %Address{hash: hash}, batch -> |
||||||
|
batch = :queue.in(Hash.to_string(hash), batch) |
||||||
|
|
||||||
|
if :queue.len(batch) >= state.max_batch_size do |
||||||
|
schedule_async_fetch(:queue.to_list(batch)) |
||||||
|
:queue.new() |
||||||
|
else |
||||||
|
batch |
||||||
|
end |
||||||
|
end) |
||||||
|
|> fetch_remaining() |
||||||
|
|
||||||
|
schedule_next_buffer_flush(state) |
||||||
|
end |
||||||
|
|
||||||
|
defp fetch_remaining({:ok, batch}) do |
||||||
|
if :queue.len(batch) > 0 do |
||||||
|
schedule_async_fetch(:queue.to_list(batch)) |
||||||
|
end |
||||||
|
|
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
defp do_fetch_addresses(address_hashes) do |
||||||
|
EthereumJSONRPC.fetch_balances_by_hash(address_hashes) |
||||||
|
end |
||||||
|
|
||||||
|
defp take_batch(queue) do |
||||||
|
{hashes, remaining_queue} = |
||||||
|
Enum.reduce_while(1..@max_batch_size, {[], queue}, fn _, {hashes, queue_acc} -> |
||||||
|
case :queue.out(queue_acc) do |
||||||
|
{{:value, hash}, new_queue} -> {:cont, {[hash | hashes], new_queue}} |
||||||
|
{:empty, new_queue} -> {:halt, {hashes, new_queue}} |
||||||
|
end |
||||||
|
end) |
||||||
|
|
||||||
|
{Enum.reverse(hashes), remaining_queue} |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_async_fetch(hashes, after_ms \\ 0) do |
||||||
|
Process.send_after(self(), {:async_fetch, hashes}, after_ms) |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_next_buffer_flush(state) do |
||||||
|
timer = Process.send_after(self(), :flush, state.fetch_interval) |
||||||
|
%{state | flush_timer: timer} |
||||||
|
end |
||||||
|
|
||||||
|
defp fetch_next_batch(state, hashes) do |
||||||
|
state = buffer_addresses(state, hashes) |
||||||
|
|
||||||
|
if Enum.count(state.tasks) < @max_concurrency and :queue.len(state.buffer) > 0 do |
||||||
|
{batch, new_queue} = take_batch(state.buffer) |
||||||
|
|
||||||
|
task = |
||||||
|
Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> |
||||||
|
debug(state, fn -> "fetching #{Enum.count(batch)} balances" end) |
||||||
|
{:ok, balances} = do_fetch_addresses(batch) |
||||||
|
{:fetched_balances, balances} |
||||||
|
end) |
||||||
|
|
||||||
|
%{state | tasks: Map.put(state.tasks, task.ref, batch), buffer: new_queue} |
||||||
|
else |
||||||
|
buffer_addresses(state, hashes) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp debug(%{debug_logs: true}, func), do: Logger.debug(func) |
||||||
|
defp debug(%{debug_logs: false}, _func), do: :noop |
||||||
|
end |
@ -0,0 +1,305 @@ |
|||||||
|
defmodule Explorer.Indexer.BlockFetcher do |
||||||
|
@moduledoc """ |
||||||
|
Fetches and indexes block ranges from gensis to realtime. |
||||||
|
""" |
||||||
|
|
||||||
|
use GenServer |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
alias EthereumJSONRPC |
||||||
|
alias EthereumJSONRPC.Transactions |
||||||
|
alias Explorer.{Chain, Indexer} |
||||||
|
alias Explorer.Indexer.{AddressFetcher, Sequence} |
||||||
|
|
||||||
|
# dialyzer thinks that Logger.debug functions always have no_local_return |
||||||
|
@dialyzer {:nowarn_function, import_range: 3} |
||||||
|
|
||||||
|
# These are all the *default* values for options. |
||||||
|
# DO NOT use them directly in the code. Get options from `state`. |
||||||
|
|
||||||
|
@debug_logs false |
||||||
|
|
||||||
|
@blocks_batch_size 10 |
||||||
|
@blocks_concurrency 10 |
||||||
|
|
||||||
|
@internal_transactions_batch_size 50 |
||||||
|
@internal_transactions_concurrency 8 |
||||||
|
|
||||||
|
# milliseconds |
||||||
|
@block_rate 5_000 |
||||||
|
|
||||||
|
@receipts_batch_size 250 |
||||||
|
@receipts_concurrency 20 |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Starts the server. |
||||||
|
|
||||||
|
## Options |
||||||
|
|
||||||
|
Default options are pulled from application config under the |
||||||
|
`:explorer, :indexer` keyspace. The follow options can be overridden: |
||||||
|
|
||||||
|
* `:debug_logs` - When `true` logs verbose index progress. Defaults `#{@debug_logs}`. |
||||||
|
* `:blocks_batch_size` - The number of blocks to request in one call to the JSONRPC. Defaults to |
||||||
|
`#{@blocks_batch_size}`. Block requests also include the transactions for those blocks. *These transactions |
||||||
|
are not paginated.* |
||||||
|
* `:blocks_concurrency` - The number of concurrent requests of `:blocks_batch_size` to allow against the JSONRPC. |
||||||
|
Defaults to #{@blocks_concurrency}. So upto `blocks_concurrency * block_batch_size` (defaults to |
||||||
|
`#{@blocks_concurrency * @blocks_batch_size}`) blocks can be requested from the JSONRPC at once over all |
||||||
|
connections. |
||||||
|
* `:block_rate` - The millisecond rate new blocks are published at. Defaults to `#{@block_rate}` milliseconds. |
||||||
|
* `:internal transactions_batch_size` - The number of transaction hashes to request internal transactions for |
||||||
|
in one call to the JSONRPC. Defaults to `#{@internal_transactions_batch_size}`. |
||||||
|
* `:internal transactions_concurrency` - The number of concurrent requests of `:internal transactions_batch_size` to |
||||||
|
allow against the JSONRPC **for each block range**. Defaults to `#{@internal_transactions_concurrency}`. So upto |
||||||
|
`block_concurrency * internal_transactions_batch_size * internal transactions_concurrency` (defaults to |
||||||
|
`#{@blocks_concurrency * @internal_transactions_concurrency * @internal_transactions_batch_size}`) transactions |
||||||
|
can be requesting their internal transactions can be requested from the JSONRPC at once over all connections. |
||||||
|
*The internal transactions for individual transactions cannot be paginated, so the total number of internal |
||||||
|
transactions that could be produced is unknown.* |
||||||
|
* `:receipts_batch_size` - The number of receipts to request in one call to the JSONRPC. Defaults to |
||||||
|
`#{@receipts_batch_size}`. Receipt requests also include the logs for when the transaction was collated into the |
||||||
|
block. *These logs are not paginated.* |
||||||
|
* `:receipts_concurrency` - The number of concurrent requests of `:receipts_batch_size` to allow against the JSONRPC |
||||||
|
**for each block range**. Defaults to `#{@receipts_concurrency}`. So upto |
||||||
|
`block_concurrency * receipts_batch_size * receipts_concurrency` (defaults to |
||||||
|
`#{@blocks_concurrency * @receipts_concurrency * @receipts_batch_size}`) receipts can be requested from the |
||||||
|
JSONRPC at once over all connections. *Each transaction only has one receipt.* |
||||||
|
""" |
||||||
|
def start_link(opts) do |
||||||
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
@impl GenServer |
||||||
|
def init(opts) do |
||||||
|
opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) |
||||||
|
:timer.send_interval(15_000, self(), :debug_count) |
||||||
|
|
||||||
|
state = %{ |
||||||
|
genesis_task: nil, |
||||||
|
realtime_task: nil, |
||||||
|
debug_logs: Keyword.get(opts, :debug_logs, @debug_logs), |
||||||
|
realtime_interval: (opts[:block_rate] || @block_rate) * 2, |
||||||
|
blocks_batch_size: Keyword.get(opts, :blocks_batch_size, @blocks_batch_size), |
||||||
|
blocks_concurrency: Keyword.get(opts, :blocks_concurrency, @blocks_concurrency), |
||||||
|
internal_transactions_batch_size: |
||||||
|
Keyword.get(opts, :internal_transactions_batch_size, @internal_transactions_batch_size), |
||||||
|
internal_transactions_concurrency: |
||||||
|
Keyword.get(opts, :internal_transactions_concurrency, @internal_transactions_concurrency), |
||||||
|
receipts_batch_size: Keyword.get(opts, :receipts_batch_size, @receipts_batch_size), |
||||||
|
receipts_concurrency: Keyword.get(opts, :receipts_concurrency, @receipts_concurrency) |
||||||
|
} |
||||||
|
|
||||||
|
{:ok, schedule_next_catchup_index(state)} |
||||||
|
end |
||||||
|
|
||||||
|
@impl GenServer |
||||||
|
def handle_info(:catchup_index, %{} = state) do |
||||||
|
{:ok, genesis_task, _ref} = monitor_task(fn -> genesis_task(state) end) |
||||||
|
|
||||||
|
{:noreply, %{state | genesis_task: genesis_task}} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info(:realtime_index, %{} = state) do |
||||||
|
{:ok, realtime_task, _ref} = monitor_task(fn -> realtime_task(state) end) |
||||||
|
|
||||||
|
{:noreply, %{state | realtime_task: realtime_task}} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, _ref, :process, pid, :normal}, %{realtime_task: pid} = state) do |
||||||
|
{:noreply, schedule_next_realtime_fetch(%{state | realtime_task: nil})} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, _ref, :process, pid, _reason}, %{realtime_task: pid} = state) do |
||||||
|
Logger.error(fn -> "realtime index stream exited. Restarting" end) |
||||||
|
{:noreply, schedule_next_realtime_fetch(%{state | realtime_task: nil})} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, _ref, :process, pid, :normal}, %{genesis_task: pid} = state) do |
||||||
|
Logger.info(fn -> "Finished index from genesis. Transitioning to realtime index." end) |
||||||
|
{:noreply, schedule_next_realtime_fetch(%{state | genesis_task: nil})} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info({:DOWN, _ref, :process, pid, _reason}, %{genesis_task: pid} = state) do |
||||||
|
Logger.error(fn -> "gensis index stream exited. Restarting" end) |
||||||
|
|
||||||
|
{:noreply, schedule_next_catchup_index(%{state | genesis_task: nil})} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_info(:debug_count, %{} = state) do |
||||||
|
debug(state, fn -> |
||||||
|
""" |
||||||
|
|
||||||
|
================================ |
||||||
|
persisted counts |
||||||
|
================================ |
||||||
|
blocks: #{Chain.block_count()} |
||||||
|
internal transactions: #{Chain.internal_transaction_count()} |
||||||
|
receipts: #{Chain.receipt_count()} |
||||||
|
logs: #{Chain.log_count()} |
||||||
|
addresses: #{Chain.address_count()} |
||||||
|
""" |
||||||
|
end) |
||||||
|
|
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
defp cap_seq(seq, :end_of_chain, {_block_start, _block_end}, _state) do |
||||||
|
:ok = Sequence.cap(seq) |
||||||
|
end |
||||||
|
|
||||||
|
defp cap_seq(_seq, :more, {block_start, block_end}, %{} = state) do |
||||||
|
debug(state, fn -> "got blocks #{block_start} - #{block_end}" end) |
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
defp fetch_internal_transactions(_state, []), do: {:ok, []} |
||||||
|
|
||||||
|
defp fetch_internal_transactions(%{} = state, hashes) do |
||||||
|
debug(state, fn -> "fetching internal transactions for #{length(hashes)} transactions" end) |
||||||
|
stream_opts = [max_concurrency: state.internal_transactions_concurrency, timeout: :infinity] |
||||||
|
|
||||||
|
hashes |
||||||
|
|> Enum.chunk_every(state.internal_transactions_batch_size) |
||||||
|
|> Task.async_stream(&EthereumJSONRPC.fetch_internal_transactions(&1), stream_opts) |
||||||
|
|> Enum.reduce_while({:ok, []}, fn |
||||||
|
{:ok, {:ok, internal_transactions}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions}} |
||||||
|
{:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} |
||||||
|
{:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp fetch_transaction_receipts(_state, []), do: {:ok, %{logs: [], receipts: []}} |
||||||
|
|
||||||
|
defp fetch_transaction_receipts(%{} = state, hashes) do |
||||||
|
debug(state, fn -> "fetching #{length(hashes)} transaction receipts" end) |
||||||
|
stream_opts = [max_concurrency: state.receipts_concurrency, timeout: :infinity] |
||||||
|
|
||||||
|
hashes |
||||||
|
|> Enum.chunk_every(state.receipts_batch_size) |
||||||
|
|> Task.async_stream(&EthereumJSONRPC.fetch_transaction_receipts(&1), stream_opts) |
||||||
|
|> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn |
||||||
|
{:ok, {:ok, %{logs: logs, receipts: receipts}}}, {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> |
||||||
|
{:cont, {:ok, %{logs: acc_logs ++ logs, receipts: acc_receipts ++ receipts}}} |
||||||
|
|
||||||
|
{:ok, {:error, reason}}, {:ok, _acc} -> |
||||||
|
{:halt, {:error, reason}} |
||||||
|
|
||||||
|
{:error, reason}, {:ok, _acc} -> |
||||||
|
{:halt, {:error, reason}} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp genesis_task(%{} = state) do |
||||||
|
{count, missing_ranges} = missing_block_numbers(state) |
||||||
|
current_block = Indexer.next_block_number() |
||||||
|
|
||||||
|
debug(state, fn -> "#{count} missed block ranges between genesis and #{current_block}" end) |
||||||
|
|
||||||
|
{:ok, seq} = Sequence.start_link(missing_ranges, current_block, state.blocks_batch_size) |
||||||
|
stream_import(state, seq, max_concurrency: state.blocks_concurrency) |
||||||
|
end |
||||||
|
|
||||||
|
defp insert(%{} = state, seq, range, params) do |
||||||
|
with {:ok, %{addresses: address_hashes}} = ok <- Chain.import_blocks(params) do |
||||||
|
:ok = AddressFetcher.async_fetch_balances(address_hashes) |
||||||
|
ok |
||||||
|
else |
||||||
|
{:error, step, reason} = error -> |
||||||
|
debug(state, fn -> |
||||||
|
"failed to insert blocks during #{step} #{inspect(range)}: #{inspect(reason)}. Retrying" |
||||||
|
end) |
||||||
|
|
||||||
|
:ok = Sequence.inject_range(seq, range) |
||||||
|
|
||||||
|
error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp missing_block_numbers(%{blocks_batch_size: blocks_batch_size}) do |
||||||
|
{count, missing_ranges} = Chain.missing_block_numbers() |
||||||
|
|
||||||
|
chunked_ranges = |
||||||
|
Enum.flat_map(missing_ranges, fn |
||||||
|
{start, ending} when ending - start <= blocks_batch_size -> |
||||||
|
[{start, ending}] |
||||||
|
|
||||||
|
{start, ending} -> |
||||||
|
start |
||||||
|
|> Stream.iterate(&(&1 + blocks_batch_size)) |
||||||
|
|> Enum.reduce_while([], fn |
||||||
|
chunk_start, acc when chunk_start + blocks_batch_size >= ending -> |
||||||
|
{:halt, [{chunk_start, ending} | acc]} |
||||||
|
|
||||||
|
chunk_start, acc -> |
||||||
|
{:cont, [{chunk_start, chunk_start + blocks_batch_size - 1} | acc]} |
||||||
|
end) |
||||||
|
|> Enum.reverse() |
||||||
|
end) |
||||||
|
|
||||||
|
{count, chunked_ranges} |
||||||
|
end |
||||||
|
|
||||||
|
defp realtime_task(%{} = state) do |
||||||
|
{:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) |
||||||
|
stream_import(state, seq, max_concurrency: 1) |
||||||
|
end |
||||||
|
|
||||||
|
defp stream_import(state, seq, task_opts) do |
||||||
|
seq |
||||||
|
|> Sequence.build_stream() |
||||||
|
|> Task.async_stream(&import_range(&1, state, seq), Keyword.merge(task_opts, timeout: :infinity)) |
||||||
|
|> Stream.run() |
||||||
|
end |
||||||
|
|
||||||
|
# Run at state.blocks_concurrency max_concurrency when called by `stream_import/3` |
||||||
|
# Only public for testing |
||||||
|
@doc false |
||||||
|
def import_range({block_start, block_end} = range, %{} = state, seq) do |
||||||
|
with {:blocks, {:ok, next, result}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(block_start, block_end)}, |
||||||
|
%{blocks: blocks, transactions: transactions} = result, |
||||||
|
cap_seq(seq, next, range, state), |
||||||
|
transaction_hashes = Transactions.params_to_hashes(transactions), |
||||||
|
{:receipts, {:ok, receipt_params}} <- {:receipts, fetch_transaction_receipts(state, transaction_hashes)}, |
||||||
|
%{logs: logs, receipts: receipts} = receipt_params, |
||||||
|
{:internal_transactions, {:ok, internal_transactions}} <- |
||||||
|
{:internal_transactions, fetch_internal_transactions(state, transaction_hashes)} do |
||||||
|
insert(state, seq, range, %{ |
||||||
|
blocks: blocks, |
||||||
|
internal_transactions: internal_transactions, |
||||||
|
logs: logs, |
||||||
|
receipts: receipts, |
||||||
|
transactions: transactions |
||||||
|
}) |
||||||
|
else |
||||||
|
{step, {:error, reason}} -> |
||||||
|
debug(state, fn -> |
||||||
|
"failed to fetch #{step} for blocks #{block_start} - #{block_end}: #{inspect(reason)}. Retrying block range." |
||||||
|
end) |
||||||
|
|
||||||
|
:ok = Sequence.inject_range(seq, range) |
||||||
|
|
||||||
|
{:error, step, reason} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_next_catchup_index(state) do |
||||||
|
send(self(), :catchup_index) |
||||||
|
state |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_next_realtime_fetch(state) do |
||||||
|
Process.send_after(self(), :realtime_index, state.realtime_interval) |
||||||
|
state |
||||||
|
end |
||||||
|
|
||||||
|
defp monitor_task(task_func) do |
||||||
|
{:ok, pid} = Task.Supervisor.start_child(Indexer.TaskSupervisor, task_func) |
||||||
|
ref = Process.monitor(pid) |
||||||
|
{:ok, pid, ref} |
||||||
|
end |
||||||
|
|
||||||
|
defp debug(%{debug_logs: true}, func), do: Logger.debug(func) |
||||||
|
defp debug(%{debug_logs: false}, _func), do: :noop |
||||||
|
end |
@ -0,0 +1,80 @@ |
|||||||
|
defmodule Explorer.Indexer.Sequence do |
||||||
|
@moduledoc false |
||||||
|
|
||||||
|
use Agent |
||||||
|
|
||||||
|
defstruct ~w(current mode queue step)a |
||||||
|
|
||||||
|
@type range :: {pos_integer(), pos_integer()} |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Builds an enumerable stream using a sequencer agent. |
||||||
|
""" |
||||||
|
@spec build_stream(pid()) :: Enumerable.t() |
||||||
|
def build_stream(sequencer) when is_pid(sequencer) do |
||||||
|
Stream.resource( |
||||||
|
fn -> sequencer end, |
||||||
|
fn seq -> |
||||||
|
case pop(seq) do |
||||||
|
:halt -> {:halt, seq} |
||||||
|
range -> {[range], seq} |
||||||
|
end |
||||||
|
end, |
||||||
|
fn seq -> seq end |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Changes the mode for the sequencer to signal continuous streaming mode. |
||||||
|
""" |
||||||
|
@spec cap(pid()) :: :ok |
||||||
|
def cap(sequencer) when is_pid(sequencer) do |
||||||
|
Agent.update(sequencer, fn state -> |
||||||
|
%__MODULE__{state | mode: :finite} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Adds a range of block numbers to the sequence. |
||||||
|
""" |
||||||
|
@spec inject_range(pid(), range()) :: :ok |
||||||
|
def inject_range(sequencer, {_first, _last} = range) when is_pid(sequencer) do |
||||||
|
Agent.update(sequencer, fn state -> |
||||||
|
%__MODULE__{state | queue: :queue.in(range, state.queue)} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Pops the next block range from the sequence. |
||||||
|
""" |
||||||
|
@spec pop(pid()) :: range() | :halt |
||||||
|
def pop(sequencer) when is_pid(sequencer) do |
||||||
|
Agent.get_and_update(sequencer, fn %__MODULE__{current: current, step: step} = state -> |
||||||
|
case {state.mode, :queue.out(state.queue)} do |
||||||
|
{_, {{:value, {starting, ending}}, new_queue}} -> |
||||||
|
{{starting, ending}, %__MODULE__{state | queue: new_queue}} |
||||||
|
|
||||||
|
{:infinite, {:empty, new_queue}} -> |
||||||
|
{{current, current + step - 1}, %__MODULE__{state | current: current + step, queue: new_queue}} |
||||||
|
|
||||||
|
{:finite, {:empty, new_queue}} -> |
||||||
|
{:halt, %__MODULE__{state | queue: new_queue}} |
||||||
|
end |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Stars a process for managing a block sequence. |
||||||
|
""" |
||||||
|
@spec start_link([range()], pos_integer(), pos_integer()) :: Agent.on_start() |
||||||
|
def start_link(initial_ranges, range_start, step) do |
||||||
|
Agent.start_link(fn -> |
||||||
|
%__MODULE__{ |
||||||
|
current: range_start, |
||||||
|
step: step, |
||||||
|
mode: :infinite, |
||||||
|
queue: :queue.from_list(initial_ranges) |
||||||
|
} |
||||||
|
end) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,24 @@ |
|||||||
|
defmodule Explorer.Indexer.Supervisor do |
||||||
|
@moduledoc """ |
||||||
|
Supervising the fetchers for the `Explorer.Indexer` |
||||||
|
""" |
||||||
|
|
||||||
|
use Supervisor |
||||||
|
|
||||||
|
alias Explorer.Indexer.{AddressFetcher, BlockFetcher} |
||||||
|
|
||||||
|
def start_link(opts) do |
||||||
|
Supervisor.start_link(__MODULE__, opts, name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
@impl Supervisor |
||||||
|
def init(_opts) do |
||||||
|
children = [ |
||||||
|
{Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}, |
||||||
|
{AddressFetcher, []}, |
||||||
|
{BlockFetcher, []} |
||||||
|
] |
||||||
|
|
||||||
|
Supervisor.init(children, strategy: :rest_for_one) |
||||||
|
end |
||||||
|
end |
@ -1,4 +0,0 @@ |
|||||||
defmodule Explorer.Scheduler do |
|
||||||
@moduledoc false |
|
||||||
use Quantum.Scheduler, otp_app: :explorer |
|
||||||
end |
|
@ -1,21 +0,0 @@ |
|||||||
defmodule Explorer.SkippedBalances do |
|
||||||
@moduledoc "Gets a list of Addresses that do not have balances." |
|
||||||
|
|
||||||
alias Explorer.Chain.Address |
|
||||||
alias Explorer.Repo.NewRelic, as: Repo |
|
||||||
|
|
||||||
import Ecto.Query, only: [from: 2] |
|
||||||
|
|
||||||
def fetch(count) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
address in Address, |
|
||||||
select: address.hash, |
|
||||||
where: is_nil(address.balance), |
|
||||||
limit: ^count |
|
||||||
) |
|
||||||
|
|
||||||
query |
|
||||||
|> Repo.all() |
|
||||||
end |
|
||||||
end |
|
@ -1,32 +0,0 @@ |
|||||||
defmodule Explorer.SkippedBlocks do |
|
||||||
@moduledoc """ |
|
||||||
Fill in older blocks that were skipped during processing. |
|
||||||
""" |
|
||||||
import Ecto.Query, only: [from: 2, limit: 2] |
|
||||||
|
|
||||||
alias Explorer.Chain.Block |
|
||||||
alias Explorer.Repo.NewRelic, as: Repo |
|
||||||
|
|
||||||
@missing_number_query "SELECT generate_series(?, 0, -1) AS missing_number" |
|
||||||
|
|
||||||
def first, do: first(1) |
|
||||||
|
|
||||||
def first(count) do |
|
||||||
blocks = |
|
||||||
from( |
|
||||||
b in Block, |
|
||||||
right_join: fragment(@missing_number_query, ^latest_block_number()), |
|
||||||
on: b.number == fragment("missing_number"), |
|
||||||
select: fragment("missing_number::text"), |
|
||||||
where: is_nil(b.id), |
|
||||||
limit: ^count |
|
||||||
) |
|
||||||
|
|
||||||
Repo.all(blocks) |
|
||||||
end |
|
||||||
|
|
||||||
def latest_block_number do |
|
||||||
block = Repo.one(Block |> Block.latest() |> limit(1)) || Block.null() |
|
||||||
block.number |
|
||||||
end |
|
||||||
end |
|
@ -1,25 +0,0 @@ |
|||||||
defmodule Explorer.SkippedInternalTransactions do |
|
||||||
@moduledoc """ |
|
||||||
Find transactions that do not have internal transactions. |
|
||||||
""" |
|
||||||
import Ecto.Query, only: [from: 2] |
|
||||||
|
|
||||||
alias Explorer.Chain.Transaction |
|
||||||
alias Explorer.Repo.NewRelic, as: Repo |
|
||||||
|
|
||||||
def first, do: first(1) |
|
||||||
|
|
||||||
def first(count) do |
|
||||||
transactions = |
|
||||||
from( |
|
||||||
transaction in Transaction, |
|
||||||
left_join: internal_transactions in assoc(transaction, :internal_transactions), |
|
||||||
select: fragment("hash"), |
|
||||||
group_by: transaction.id, |
|
||||||
having: count(internal_transactions.id) == 0, |
|
||||||
limit: ^count |
|
||||||
) |
|
||||||
|
|
||||||
Repo.all(transactions) |
|
||||||
end |
|
||||||
end |
|
@ -1,25 +0,0 @@ |
|||||||
defmodule Explorer.SkippedReceipts do |
|
||||||
@moduledoc """ |
|
||||||
Find transactions that do not have a receipt. |
|
||||||
""" |
|
||||||
import Ecto.Query, only: [from: 2] |
|
||||||
|
|
||||||
alias Explorer.Chain.Transaction |
|
||||||
alias Explorer.Repo.NewRelic, as: Repo |
|
||||||
|
|
||||||
def first, do: first(1) |
|
||||||
|
|
||||||
def first(count) do |
|
||||||
transactions = |
|
||||||
from( |
|
||||||
transaction in Transaction, |
|
||||||
left_join: receipt in assoc(transaction, :receipt), |
|
||||||
select: fragment("hash"), |
|
||||||
group_by: transaction.id, |
|
||||||
having: count(receipt.id) == 0, |
|
||||||
limit: ^count |
|
||||||
) |
|
||||||
|
|
||||||
Repo.all(transactions) |
|
||||||
end |
|
||||||
end |
|
@ -1,13 +0,0 @@ |
|||||||
defmodule Explorer.Workers.ImportBalance do |
|
||||||
@moduledoc "A worker that imports the balance for a given address." |
|
||||||
|
|
||||||
alias Explorer.BalanceImporter |
|
||||||
|
|
||||||
def perform(hash) do |
|
||||||
BalanceImporter.import(hash) |
|
||||||
end |
|
||||||
|
|
||||||
def perform_later(hash) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "balances", __MODULE__, [hash]) |
|
||||||
end |
|
||||||
end |
|
@ -1,26 +0,0 @@ |
|||||||
defmodule Explorer.Workers.ImportBlock do |
|
||||||
@moduledoc "Imports blocks by web3 conventions." |
|
||||||
|
|
||||||
import Ethereumex.HttpClient, only: [eth_block_number: 0] |
|
||||||
|
|
||||||
alias Explorer.BlockImporter |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, perform: 1} |
|
||||||
def perform("latest") do |
|
||||||
case eth_block_number() do |
|
||||||
{:ok, number} -> perform_later(number) |
|
||||||
_ -> nil |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, perform: 1} |
|
||||||
def perform(number), do: BlockImporter.import("#{number}") |
|
||||||
|
|
||||||
def perform_later("0x" <> number) when is_binary(number) do |
|
||||||
number |> String.to_integer(16) |> perform_later() |
|
||||||
end |
|
||||||
|
|
||||||
def perform_later(number) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "blocks", __MODULE__, [number]) |
|
||||||
end |
|
||||||
end |
|
@ -1,12 +0,0 @@ |
|||||||
defmodule Explorer.Workers.ImportInternalTransaction do |
|
||||||
@moduledoc "Imports internal transactions via Parity trace endpoints." |
|
||||||
|
|
||||||
alias Explorer.InternalTransactionImporter |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, perform: 1} |
|
||||||
def perform(hash), do: InternalTransactionImporter.import(hash) |
|
||||||
|
|
||||||
def perform_later(hash) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "internal_transactions", __MODULE__, [hash]) |
|
||||||
end |
|
||||||
end |
|
@ -1,12 +0,0 @@ |
|||||||
defmodule Explorer.Workers.ImportReceipt do |
|
||||||
@moduledoc "Imports transaction by web3 conventions." |
|
||||||
|
|
||||||
alias Explorer.ReceiptImporter |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, perform: 1} |
|
||||||
def perform(hash), do: ReceiptImporter.import(hash) |
|
||||||
|
|
||||||
def perform_later(hash) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "receipts", __MODULE__, [hash]) |
|
||||||
end |
|
||||||
end |
|
@ -1,18 +0,0 @@ |
|||||||
defmodule Explorer.Workers.ImportSkippedBlocks do |
|
||||||
alias Explorer.SkippedBlocks |
|
||||||
alias Explorer.Workers.ImportBlock |
|
||||||
|
|
||||||
@moduledoc "Imports skipped blocks." |
|
||||||
|
|
||||||
def perform, do: perform(1) |
|
||||||
|
|
||||||
def perform(count) do |
|
||||||
count |> SkippedBlocks.first() |> Enum.map(&ImportBlock.perform_later/1) |
|
||||||
end |
|
||||||
|
|
||||||
def perform_later, do: perform_later(1) |
|
||||||
|
|
||||||
def perform_later(count) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "default", __MODULE__, [count]) |
|
||||||
end |
|
||||||
end |
|
@ -1,26 +0,0 @@ |
|||||||
defmodule Explorer.Workers.ImportTransaction do |
|
||||||
@moduledoc """ |
|
||||||
Manages the lifecycle of importing a single Transaction from web3. |
|
||||||
""" |
|
||||||
|
|
||||||
alias Explorer.TransactionImporter |
|
||||||
alias Explorer.Workers.{ImportInternalTransaction, ImportReceipt} |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, perform: 1} |
|
||||||
def perform(hash) when is_binary(hash) do |
|
||||||
TransactionImporter.import(hash) |
|
||||||
ImportInternalTransaction.perform_later(hash) |
|
||||||
ImportReceipt.perform_later(hash) |
|
||||||
end |
|
||||||
|
|
||||||
@dialyzer {:nowarn_function, perform: 1} |
|
||||||
def perform(raw_transaction) when is_map(raw_transaction) do |
|
||||||
TransactionImporter.import(raw_transaction) |
|
||||||
ImportInternalTransaction.perform_later(raw_transaction["hash"]) |
|
||||||
ImportReceipt.perform_later(raw_transaction["hash"]) |
|
||||||
end |
|
||||||
|
|
||||||
def perform_later(hash) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "transactions", __MODULE__, [hash]) |
|
||||||
end |
|
||||||
end |
|
@ -1,29 +0,0 @@ |
|||||||
defmodule Explorer.Workers.RefreshBalance do |
|
||||||
@moduledoc """ |
|
||||||
Refreshes the Credit and Debit balance views. |
|
||||||
""" |
|
||||||
|
|
||||||
alias Ecto.Adapters.SQL |
|
||||||
alias Explorer.Chain.{Credit, Debit} |
|
||||||
alias Explorer.Repo |
|
||||||
|
|
||||||
def perform("credit"), do: unless(refreshing("credits"), do: Credit.refresh()) |
|
||||||
def perform("debit"), do: unless(refreshing("debits"), do: Debit.refresh()) |
|
||||||
|
|
||||||
def perform do |
|
||||||
perform_later(["credit"]) |
|
||||||
perform_later(["debit"]) |
|
||||||
end |
|
||||||
|
|
||||||
def perform_later(args \\ []) do |
|
||||||
Exq.enqueue(Exq.Enqueuer, "default", __MODULE__, args) |
|
||||||
end |
|
||||||
|
|
||||||
def refreshing(table) do |
|
||||||
query = "REFRESH MATERIALIZED VIEW CONCURRENTLY #{table}%" |
|
||||||
|
|
||||||
result = SQL.query!(Repo, "SELECT TRUE FROM pg_stat_activity WHERE query ILIKE '$#{query}'", []) |
|
||||||
|
|
||||||
Enum.count(result.rows) > 0 |
|
||||||
end |
|
||||||
end |
|
@ -1,45 +0,0 @@ |
|||||||
defmodule GiantAddressMigrator do |
|
||||||
@moduledoc "Migrate away from Address join tables." |
|
||||||
|
|
||||||
require Logger |
|
||||||
|
|
||||||
alias Explorer.Repo |
|
||||||
|
|
||||||
def migrate do |
|
||||||
for n <- 1..20 do |
|
||||||
chunk_size = 500_000 |
|
||||||
lower = n * chunk_size - chunk_size |
|
||||||
upper = n * chunk_size |
|
||||||
|
|
||||||
Logger.info("fetching results between #{lower} and #{upper}") |
|
||||||
work_on_transactions_between_ids(lower, upper) |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
def work_on_transactions_between_ids(lower, upper) do |
|
||||||
query = """ |
|
||||||
select transactions.id, from_addresses.address_id as from_address_id, to_addresses.address_id as to_address_id |
|
||||||
FROM transactions |
|
||||||
inner join from_addresses on from_addresses.transaction_id = id |
|
||||||
inner join to_addresses on to_addresses.transaction_id = id |
|
||||||
where transactions.id >= #{lower} AND transactions.id < #{upper} |
|
||||||
; |
|
||||||
""" |
|
||||||
|
|
||||||
{:ok, result} = Repo.query(query, []) |
|
||||||
Logger.info("got em!") |
|
||||||
|
|
||||||
result.rows |
|
||||||
|> Enum.each(&sweet_update/1) |
|
||||||
end |
|
||||||
|
|
||||||
def sweet_update([transaction_id, from_address_id, to_address_id]) do |
|
||||||
query = """ |
|
||||||
UPDATE transactions SET from_address_id = $1, to_address_id = $2 WHERE id = $3 |
|
||||||
""" |
|
||||||
|
|
||||||
{:ok, _status} = Repo.query(query, [from_address_id, to_address_id, transaction_id]) |
|
||||||
end |
|
||||||
|
|
||||||
def sweet_update(_), do: nil |
|
||||||
end |
|
@ -1,25 +0,0 @@ |
|||||||
defmodule Mix.Tasks.Exq.Start do |
|
||||||
@moduledoc "Starts the Exq worker" |
|
||||||
use Mix.Task |
|
||||||
|
|
||||||
alias Explorer.{Repo, Scheduler} |
|
||||||
|
|
||||||
def run(["scheduler"]) do |
|
||||||
[:postgrex, :ecto, :ethereumex, :tzdata] |
|
||||||
|> Enum.each(&Application.ensure_all_started/1) |
|
||||||
|
|
||||||
Repo.start_link() |
|
||||||
Exq.start_link(mode: :enqueuer) |
|
||||||
Scheduler.start_link() |
|
||||||
:timer.sleep(:infinity) |
|
||||||
end |
|
||||||
|
|
||||||
def run(_) do |
|
||||||
[:postgrex, :ecto, :ethereumex, :tzdata] |
|
||||||
|> Enum.each(&Application.ensure_all_started/1) |
|
||||||
|
|
||||||
Repo.start_link() |
|
||||||
Exq.start_link(mode: :default) |
|
||||||
:timer.sleep(:infinity) |
|
||||||
end |
|
||||||
end |
|
@ -1,24 +0,0 @@ |
|||||||
defmodule Mix.Tasks.Scrape.Balances do |
|
||||||
@moduledoc "Populate Address balances." |
|
||||||
|
|
||||||
use Mix.Task |
|
||||||
|
|
||||||
alias Explorer.{BalanceImporter, Repo, SkippedBalances} |
|
||||||
|
|
||||||
def run([]), do: run(1) |
|
||||||
|
|
||||||
def run(count) do |
|
||||||
[:postgrex, :ecto, :ethereumex, :tzdata] |
|
||||||
|> Enum.each(&Application.ensure_all_started/1) |
|
||||||
|
|
||||||
Repo.start_link() |
|
||||||
Exq.start_link(mode: :enqueuer) |
|
||||||
|
|
||||||
"#{count}" |
|
||||||
|> String.to_integer() |
|
||||||
|> SkippedBalances.fetch() |
|
||||||
|> Flow.from_enumerable() |
|
||||||
|> Flow.map(&BalanceImporter.import/1) |
|
||||||
|> Enum.to_list() |
|
||||||
end |
|
||||||
end |
|
@ -1,26 +0,0 @@ |
|||||||
defmodule Mix.Tasks.Scrape.Blocks do |
|
||||||
@moduledoc "Scrapes blocks from web3" |
|
||||||
|
|
||||||
use Mix.Task |
|
||||||
|
|
||||||
alias Explorer.{BlockImporter, Repo, SkippedBlocks} |
|
||||||
|
|
||||||
def run([]), do: run(1) |
|
||||||
|
|
||||||
def run(count) do |
|
||||||
[:postgrex, :ecto, :ethereumex, :tzdata] |
|
||||||
|> Enum.each(&Application.ensure_all_started/1) |
|
||||||
|
|
||||||
Repo.start_link() |
|
||||||
Exq.start_link(mode: :enqueuer) |
|
||||||
|
|
||||||
"#{count}" |
|
||||||
|> String.to_integer() |
|
||||||
|> SkippedBlocks.first() |
|
||||||
|> Enum.shuffle() |
|
||||||
|> Flow.from_enumerable() |
|
||||||
|> Flow.map(&BlockImporter.download_block/1) |
|
||||||
|> Flow.map(&BlockImporter.import/1) |
|
||||||
|> Enum.to_list() |
|
||||||
end |
|
||||||
end |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue