fetch codes

pull/1347/head
Ayrat Badykov 6 years ago
parent 3e19a8afab
commit e40e4dca27
No known key found for this signature in database
GPG Key ID: B44668E265E9396F
  1. 25
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  2. 50
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_code.ex
  3. 49
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex
  4. 82
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs

@ -29,6 +29,7 @@ defmodule EthereumJSONRPC do
Block,
Blocks,
FetchedBalances,
FetchedCodes,
Receipts,
RequestCoordinator,
Subscription,
@ -54,6 +55,11 @@ defmodule EthereumJSONRPC do
"""
@type data :: String.t()
@typedoc """
Contract code encoded as a single hexadecimal number in a `String.t`
"""
@type code :: 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`
@ -201,6 +207,25 @@ defmodule EthereumJSONRPC do
end
end
@doc """
Fetches code for each given `address` at the `block_number`.
"""
@spec fetch_codes(
[%{required(:block_quantity) => quantity, required(:address) => address()}],
json_rpc_named_arguments
) :: {:ok, FetchedCodes.t()} | {:error, reason :: term}
def fetch_codes(params_list, json_rpc_named_arguments)
when is_list(params_list) and is_list(json_rpc_named_arguments) do
id_to_params = id_to_params(params_list)
with {:ok, responses} <-
id_to_params
|> FetchedCodes.requests()
|> json_rpc(json_rpc_named_arguments) do
{:ok, FetchedCodes.from_responses(responses, id_to_params)}
end
end
@doc """
Fetches block reward contract beneficiaries from variant API.
"""

@ -0,0 +1,50 @@
defmodule EthereumJSONRPC.FetchedCode do
@moduledoc """
A single code fetched from `eth_getCode`.
"""
import EthereumJSONRPC, only: [quantity_to_integer: 1]
@type params :: %{address: EthereumJSONRPC.address(), block_number: non_neg_integer(), code: non_neg_integer()}
@type error :: %{code: integer(), message: String.t(), data: %{block_quantity: String.t(), address: String.t()}}
@doc """
Converts `response` to code params or annotated error.
"""
def from_response(%{id: id, result: fetched_code}, id_to_params) when is_map(id_to_params) do
%{block_quantity: block_quantity, address: address} = Map.fetch!(id_to_params, id)
{:ok,
%{
address: address,
block_number: quantity_to_integer(block_quantity),
code: fetched_code
}}
end
@spec from_response(%{id: id, result: String.t()}, %{id => %{block_quantity: block_quantity, address: address}}) ::
{:ok, params()}
when id: non_neg_integer(), block_quantity: String.t(), address: String.t()
def from_response(%{id: id, error: %{code: code, message: message} = error}, id_to_params)
when is_integer(code) and is_binary(message) and is_map(id_to_params) do
%{block_quantity: block_quantity, address: address} = Map.fetch!(id_to_params, id)
annotated_error = Map.put(error, :data, %{block_quantity: block_quantity, address: address})
{:error, annotated_error}
end
@spec request(%{id: id, block_quantity: block_quantity, address: address}) :: %{
jsonrpc: String.t(),
id: id,
method: String.t(),
params: [address | block_quantity]
}
when id: EthereumJSONRPC.request_id(),
block_quantity: EthereumJSONRPC.quantity(),
address: EthereumJSONRPC.address()
def request(%{id: id, block_quantity: block_quantity, address: address}) do
EthereumJSONRPC.request(%{id: id, method: "eth_getCode", params: [address, block_quantity]})
end
end

@ -0,0 +1,49 @@
defmodule EthereumJSONRPC.FetchedCodes do
@moduledoc """
Code params and errors from a batch request from `eth_getCode`.
"""
alias EthereumJSONRPC.FetchedCode
defstruct params_list: [],
errors: []
@typedoc """
* `params_list` - all the code params from requests that succeeded in the batch.
* `errors` - all the errors from requests that failed in the batch.
"""
@type t :: %__MODULE__{params_list: [FetchedCode.params()], errors: [FetchedCode.error()]}
@doc """
`eth_getCode` requests for `id_to_params`.
"""
@spec requests(%{id => %{block_quantity: block_quantity, address: address}}) :: [
%{jsonrpc: String.t(), id: id, method: String.t(), params: [address | block_quantity]}
]
when id: EthereumJSONRPC.request_id(),
block_quantity: EthereumJSONRPC.quantity(),
address: EthereumJSONRPC.address()
def requests(id_to_params) when is_map(id_to_params) do
Enum.map(id_to_params, fn {id, %{block_quantity: block_quantity, address: address}} ->
FetchedCode.request(%{id: id, block_quantity: block_quantity, address: address})
end)
end
@doc """
Converts `responses` to `t/0`.
"""
def from_responses(responses, id_to_params) do
responses
|> Enum.map(&FetchedCode.from_response(&1, id_to_params))
|> Enum.reduce(
%__MODULE__{},
fn
{:ok, params}, %__MODULE__{params_list: params_list} = acc ->
%__MODULE__{acc | params_list: [params | params_list]}
{:error, reason}, %__MODULE__{errors: errors} = acc ->
%__MODULE__{acc | errors: [reason | errors]}
end
)
end
end

@ -4,7 +4,7 @@ defmodule EthereumJSONRPCTest do
import EthereumJSONRPC.Case
import Mox
alias EthereumJSONRPC.{Blocks, FetchedBalances, FetchedBeneficiaries, Subscription}
alias EthereumJSONRPC.{Blocks, FetchedBalances, FetchedBeneficiaries, FetchedCodes, Subscription}
alias EthereumJSONRPC.WebSocket.WebSocketClient
setup :verify_on_exit!
@ -173,6 +173,86 @@ defmodule EthereumJSONRPCTest do
end
end
describe "fetch_balances/2" do
test "returns both codes and errors", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{
:ok,
[
%{
id: 0,
result:
"0x600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905056"
},
%{
id: 1,
error: %{
code: -32602,
message:
"Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
}
},
%{
id: 2,
result:
"0x7009600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905"
}
]
}
end)
end
assert {:ok, %FetchedCodes{params_list: params_list, errors: errors}} =
EthereumJSONRPC.fetch_codes(
[
# start with :ok
%{
block_quantity: "0x1",
address: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
},
# :ok, :error clause
%{
block_quantity: "0x2",
address: "0x3"
},
# :error, :ok clause
%{
block_quantity: "0x35",
address: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
}
],
json_rpc_named_arguments
)
assert params_list == [
%{
address: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: 53,
code:
"0x7009600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905"
},
%{
address: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: 1,
code:
"0x600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905056"
}
]
assert errors == [
%{
code: -32602,
data: %{address: "0x3", block_quantity: "0x2"},
message:
"Invalid params: invalid length 1, expected a 0x-prefixed, padded, hex-encoded hash with length 40."
}
]
end
end
describe "fetch_beneficiaries/2" do
@tag :no_geth
test "fetches benefeciaries from variant API", %{json_rpc_named_arguments: json_rpc_named_arguments} do

Loading…
Cancel
Save