Add support to Pluggable Transport and HTTP transport modules

pull/279/head
Amanda Sposito 6 years ago
parent b8a8f083d2
commit dae9ff2f10
  1. 19
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  2. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
  3. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
  4. 39
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs
  5. 6
      apps/explorer/config/dev.exs
  6. 12
      apps/explorer/config/dev/geth.exs
  7. 16
      apps/explorer/config/dev/parity.exs
  8. 6
      apps/explorer/config/prod.exs
  9. 12
      apps/explorer/config/prod/geth.exs
  10. 16
      apps/explorer/config/prod/parity.exs
  11. 14
      apps/explorer/config/test.exs
  12. 8
      apps/explorer/config/test/geth.exs
  13. 9
      apps/explorer/config/test/parity.exs
  14. 4
      apps/explorer/lib/explorer/smart_contract/reader.ex
  15. 60
      apps/explorer/test/explorer/smart_contract/reader_test.exs
  16. 2
      apps/explorer/test/test_helper.exs

@ -83,6 +83,11 @@ defmodule EthereumJSONRPC do
"""
@type timestamp :: String.t()
@typedoc """
JSONRPC request id can be a `String.t` or Integer
"""
@type request_id :: String.t() | non_neg_integer()
@doc """
Execute smart contract functions.
@ -109,10 +114,14 @@ defmodule EthereumJSONRPC do
}
]}
"""
def execute_contract_functions(functions) do
@spec execute_contract_functions(
[%{contract_address: String.t(), data: String.t(), id: String.t()}],
json_rpc_named_arguments
) :: {:ok, []}
def execute_contract_functions(functions, json_rpc_named_arguments) do
functions
|> Enum.map(&build_eth_call_payload/1)
|> json_rpc(config(:url))
|> json_rpc(json_rpc_named_arguments)
end
defp build_eth_call_payload(%{contract_address: address, data: data, id: id}) do
@ -277,9 +286,9 @@ defmodule EthereumJSONRPC do
@doc """
A request payload for a JSONRPC.
"""
@spec request(%{id: non_neg_integer(), method: String.t(), params: list()}) :: Transport.request()
def request(%{id: id, method: method, params: params} = map)
when is_integer(id) and is_binary(method) and is_list(params) do
@spec request(%{id: request_id, method: String.t(), params: list()}) :: Transport.request()
def request(%{method: method, params: params} = map)
when is_binary(method) and is_list(params) do
Map.put(map, :jsonrpc, "2.0")
end

@ -82,18 +82,18 @@ defmodule EthereumJSONRPC.Encoder do
end
defp join_result_and_selector(result, selectors) do
{result, Enum.find(selectors, &(&1.function == result["id"]))}
{result, Enum.find(selectors, &(&1.function == result[:id]))}
end
@doc """
Given a result from the blockchain, and the function selector, returns the result decoded.
"""
@spec decode_result({map(), %ABI.FunctionSelector{}}) :: {String.t(), [String.t()]}
def decode_result({%{"error" => %{"code" => code, "message" => message}, "id" => id}, _selector}) do
def decode_result({%{error: %{code: code, message: message}, id: id}, _selector}) do
{id, ["#{code} => #{message}"]}
end
def decode_result({%{"id" => id, "result" => result}, function_selector}) do
def decode_result({%{id: id, result: result}, function_selector}) do
types_list = format_list_types(function_selector.returns)
decoded_result =

@ -124,7 +124,7 @@ defmodule EthereumJSONRPC.HTTP do
# restrict response to only those fields supported by the JSON-RPC 2.0 standard, which means that level of keys is
# validated, so we can indicate that with switch to atom keys.
defp standardize_response(%{"jsonrpc" => "2.0" = jsonrpc, "id" => id} = unstandardized) when is_integer(id) do
defp standardize_response(%{"jsonrpc" => "2.0" = jsonrpc, "id" => id} = unstandardized) do
standardized = %{jsonrpc: jsonrpc, id: id}
case unstandardized do

@ -92,25 +92,25 @@ defmodule EthereumJSONRPC.EncoderTest do
end
end
describe "decode_results/3" do
describe "decode_abi_results/3" do
test "separates the selectors and map the results" do
result =
{:ok,
[
%{
"id" => "get1",
"jsonrpc" => "2.0",
"result" => "0x000000000000000000000000000000000000000000000000000000000000002a"
id: "get1",
jsonrpc: "2.0",
result: "0x000000000000000000000000000000000000000000000000000000000000002a"
},
%{
"id" => "get2",
"jsonrpc" => "2.0",
"result" => "0x000000000000000000000000000000000000000000000000000000000000002a"
id: "get2",
jsonrpc: "2.0",
result: "0x000000000000000000000000000000000000000000000000000000000000002a"
},
%{
"id" => "get3",
"jsonrpc" => "2.0",
"result" => "0x0000000000000000000000000000000000000000000000000000000000000020"
id: "get3",
jsonrpc: "2.0",
result: "0x0000000000000000000000000000000000000000000000000000000000000020"
}
]}
@ -161,9 +161,9 @@ defmodule EthereumJSONRPC.EncoderTest do
describe "decode_result/1" do
test "correclty decodes the blockchain result" do
result = %{
"id" => "sum",
"jsonrpc" => "2.0",
"result" => "0x000000000000000000000000000000000000000000000000000000000000002a"
id: "sum",
jsonrpc: "2.0",
result: "0x000000000000000000000000000000000000000000000000000000000000002a"
}
selector = %ABI.FunctionSelector{
@ -177,12 +177,12 @@ defmodule EthereumJSONRPC.EncoderTest do
test "correclty handles the blockchain error response" do
result = %{
"error" => %{
"code" => -32602,
"message" => "Invalid params: Invalid hex: Invalid character 'x' at position 134."
error: %{
code: -32602,
message: "Invalid params: Invalid hex: Invalid character 'x' at position 134."
},
"id" => "sum",
"jsonrpc" => "2.0"
id: "sum",
jsonrpc: "2.0"
}
selector = %ABI.FunctionSelector{
@ -201,8 +201,7 @@ defmodule EthereumJSONRPC.EncoderTest do
selector = %ABI.FunctionSelector{function: "name", types: [], returns: :string}
assert Encoder.decode_result({%{"id" => "storedName", "result" => result}, selector}) ==
{"storedName", [["AION"]]}
assert Encoder.decode_result({%{id: "storedName", result: result}, selector}) == {"storedName", [["AION"]]}
end
end
end

@ -11,3 +11,9 @@ config :explorer, Explorer.Repo,
timeout: 80_000
import_config "dev.secret.exs"
variant = System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity"
# Import variant specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "dev/#{variant}.exs"

@ -0,0 +1,12 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY",
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Geth
]

@ -0,0 +1,16 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://sokol.poa.network",
method_to_url: [
eth_getBalance: "https://sokol-trace.poa.network",
trace_replayTransaction: "https://sokol-trace.poa.network"
],
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Parity
]

@ -8,3 +8,9 @@ config :explorer, Explorer.Repo,
ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"),
prepare: :unnamed,
timeout: 60_000
variant = System.get_env("ETHEREUM_JSONRPC_VARIANT") || "parity"
# Import variant specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "prod/#{variant}.exs"

@ -0,0 +1,12 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY",
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Geth
]

@ -0,0 +1,16 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.HTTP,
transport_options: [
http: EthereumJSONRPC.HTTP.HTTPoison,
url: "https://sokol.poa.network",
method_to_url: [
eth_getBalance: "https://sokol-trace.poa.network",
trace_replayTransaction: "https://sokol-trace.poa.network"
],
http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]]
],
variant: EthereumJSONRPC.Parity
]

@ -18,3 +18,17 @@ config :explorer, Explorer.ExchangeRates, enabled: false
config :explorer, Explorer.Indexer.Supervisor, enabled: false
config :explorer, Explorer.Market.History.Cataloger, enabled: false
variant =
if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do
"parity"
else
System.get_env("ETHEREUM_JSONRPC_VARIANT")
|> String.split(".")
|> List.last()
|> String.downcase()
end
# Import variant specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "test/#{variant}.exs"

@ -0,0 +1,8 @@
use Mix.Config
config :explorer,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.Mox,
transport_options: [],
variant: EthereumJSONRPC.Geth
]

@ -0,0 +1,9 @@
use Mix.Config
config :explorer,
transport: EthereumJSONRPC.HTTP,
json_rpc_named_arguments: [
transport: EthereumJSONRPC.Mox,
transport_options: [],
variant: EthereumJSONRPC.Parity
]

@ -40,6 +40,8 @@ defmodule Explorer.SmartContract.Reader do
"""
@spec query_contract(%Explorer.Chain.Hash{}, %{String.t() => [term()]}) :: map()
def query_contract(address_hash, functions) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
contract_address = Hash.to_string(address_hash)
abi =
@ -52,7 +54,7 @@ defmodule Explorer.SmartContract.Reader do
abi
|> Encoder.encode_abi(functions)
|> Enum.map(&setup_call_payload(&1, contract_address))
|> EthereumJSONRPC.execute_contract_functions()
|> EthereumJSONRPC.execute_contract_functions(json_rpc_named_arguments)
Encoder.decode_abi_results(blockchain_result, abi, functions)
rescue

@ -1,42 +1,24 @@
defmodule Explorer.SmartContract.ReaderTest do
use ExUnit.Case
use EthereumJSONRPC.Case
use Explorer.DataCase
doctest Explorer.SmartContract.Reader
alias Explorer.SmartContract.Reader
alias Plug.Conn
import Mox
@ethereum_jsonrpc_original Application.get_env(:ethereum_jsonrpc, :url)
setup do
bypass = Bypass.open()
Application.put_env(:ethereum_jsonrpc, :url, "http://localhost:#{bypass.port}")
on_exit(fn ->
Application.put_env(:ethereum_jsonrpc, :url, @ethereum_jsonrpc_original)
end)
{:ok, bypass: bypass}
end
setup :verify_on_exit!
describe "query_contract/2" do
test "correctly returns the results of the smart contract functions", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
Conn.resp(
conn,
200,
~s[{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":"get"}]
)
end)
test "correctly returns the results of the smart contract functions" do
hash =
:smart_contract
|> insert()
|> Map.get(:address_hash)
blockchain_get_function_mock()
assert Reader.query_contract(hash, %{"get" => []}) == %{"get" => [0]}
end
@ -83,7 +65,7 @@ defmodule Explorer.SmartContract.ReaderTest do
end
describe "read_only_functions/1" do
test "fetches the smart contract read only functions with the blockchain value", %{bypass: bypass} do
test "fetches the smart contract read only functions with the blockchain value" do
smart_contract =
insert(
:smart_contract,
@ -109,13 +91,7 @@ defmodule Explorer.SmartContract.ReaderTest do
]
)
Bypass.expect(bypass, fn conn ->
Conn.resp(
conn,
200,
~s[{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":"get"}]
)
end)
blockchain_get_function_mock()
response = Reader.read_only_functions(smart_contract.address_hash)
@ -143,16 +119,10 @@ defmodule Explorer.SmartContract.ReaderTest do
end
describe "query_function/2" do
test "given the arguments, fetches the function value from the blockchain", %{bypass: bypass} do
test "given the arguments, fetches the function value from the blockchain" do
smart_contract = insert(:smart_contract)
Bypass.expect(bypass, fn conn ->
Conn.resp(
conn,
200,
~s[{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000000000000000000000","id":"get"}]
)
end)
blockchain_get_function_mock()
assert [
%{
@ -216,4 +186,14 @@ defmodule Explorer.SmartContract.ReaderTest do
] = Reader.link_outputs_and_values(blockchain_values, outputs, function_name)
end
end
defp blockchain_get_function_mock() do
EthereumJSONRPC.Mox
|> expect(
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}]}], _options ->
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000000"}]}
end
)
end
end

@ -15,3 +15,5 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual)
Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source)
Mox.defmock(Explorer.Market.History.Source.TestSource, for: Explorer.Market.History.Source)
Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)

Loading…
Cancel
Save