Why: * We'd like to support GraphQL API queries for getting single or multiple addresses by hashes. API users could use this instead of the `balance` and `balancemulti` actions on the RPC API. Sample document: ``` query ($hashes: [AddressHash!]!) { addresses(hashes: $hashes) { hash fetched_coin_balance fetched_coin_balance_block_number contract_code } } ``` * Issue link: n/a This change addresses the need by: * Creating `BlockScoutWeb.Resolvers.Address` with a single resolver function that gets addresses by a list of hashes. * Adding `:data` scalar to `BlockScoutWeb.Schema.Scalars`. * Adding `address` object type to `BlockScoutWeb.Schema.Types`. Uses new `:data` scalar mentioned above. * Adding `addresses` field to query in `BlockScoutWeb.Schema`. Uses the new `address` object type and the resolver function mentioned above. * Editing `Abinthe.Plug` and `GraphiQL` in router to analyze complexity and set `max_complexity` to 50.pull/1031/head
parent
2d7b37db6f
commit
67c68b6f41
@ -0,0 +1,12 @@ |
|||||||
|
defmodule BlockScoutWeb.Resolvers.Address do |
||||||
|
@moduledoc false |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
|
||||||
|
def get_by(_, %{hashes: hashes}, _) do |
||||||
|
case Chain.hashes_to_addresses(hashes) do |
||||||
|
[] -> {:error, "Addresses not found."} |
||||||
|
result -> {:ok, result} |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,142 @@ |
|||||||
|
defmodule BlockScoutWeb.Schema.Query.AddressTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
describe "address field" do |
||||||
|
test "with valid argument 'hashes', returns all expected fields", %{conn: conn} do |
||||||
|
address = insert(:address, fetched_coin_balance: 100) |
||||||
|
|
||||||
|
query = """ |
||||||
|
query ($hashes: [AddressHash!]!) { |
||||||
|
addresses(hashes: $hashes) { |
||||||
|
hash |
||||||
|
fetched_coin_balance |
||||||
|
fetched_coin_balance_block_number |
||||||
|
contract_code |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
variables = %{"hashes" => to_string(address.hash)} |
||||||
|
|
||||||
|
conn = get(conn, "/graphql", query: query, variables: variables) |
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{ |
||||||
|
"data" => %{ |
||||||
|
"addresses" => [ |
||||||
|
%{ |
||||||
|
"hash" => to_string(address.hash), |
||||||
|
"fetched_coin_balance" => to_string(address.fetched_coin_balance.value), |
||||||
|
"fetched_coin_balance_block_number" => address.fetched_coin_balance_block_number, |
||||||
|
"contract_code" => nil |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
test "with contract address, `contract_code` is serialized as expected", %{conn: conn} do |
||||||
|
address = insert(:contract_address, fetched_coin_balance: 100) |
||||||
|
|
||||||
|
query = """ |
||||||
|
query ($hashes: [AddressHash!]!) { |
||||||
|
addresses(hashes: $hashes) { |
||||||
|
contract_code |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
variables = %{"hashes" => to_string(address.hash)} |
||||||
|
|
||||||
|
conn = get(conn, "/graphql", query: query, variables: variables) |
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{ |
||||||
|
"data" => %{ |
||||||
|
"addresses" => [ |
||||||
|
%{ |
||||||
|
"contract_code" => to_string(address.contract_code) |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
test "errors for non-existent address hashes", %{conn: conn} do |
||||||
|
address = build(:address) |
||||||
|
|
||||||
|
query = """ |
||||||
|
query ($hashes: [AddressHash!]!) { |
||||||
|
addresses(hashes: $hashes) { |
||||||
|
fetched_coin_balance |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
variables = %{"hashes" => [to_string(address.hash)]} |
||||||
|
|
||||||
|
conn = get(conn, "/graphql", query: query, variables: variables) |
||||||
|
|
||||||
|
assert %{"errors" => [error]} = json_response(conn, 200) |
||||||
|
assert error["message"] =~ ~s(Addresses not found.) |
||||||
|
end |
||||||
|
|
||||||
|
test "errors if argument 'hashes' is missing", %{conn: conn} do |
||||||
|
query = """ |
||||||
|
query { |
||||||
|
addresses { |
||||||
|
fetched_coin_balance |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
variables = %{} |
||||||
|
|
||||||
|
conn = get(conn, "/graphql", query: query, variables: variables) |
||||||
|
|
||||||
|
assert %{"errors" => [error]} = json_response(conn, 200) |
||||||
|
assert error["message"] == ~s(In argument "hashes": Expected type "[AddressHash!]!", found null.) |
||||||
|
end |
||||||
|
|
||||||
|
test "errors if argument 'hashes' is not a list of address hashes", %{conn: conn} do |
||||||
|
query = """ |
||||||
|
query ($hashes: [AddressHash!]!) { |
||||||
|
addresses(hashes: $hashes) { |
||||||
|
fetched_coin_balance |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
variables = %{"hashes" => ["someInvalidHash"]} |
||||||
|
|
||||||
|
conn = get(conn, "/graphql", query: query, variables: variables) |
||||||
|
|
||||||
|
assert %{"errors" => [error]} = json_response(conn, 200) |
||||||
|
assert error["message"] =~ ~s(Argument "hashes" has invalid value) |
||||||
|
end |
||||||
|
|
||||||
|
test "correlates complexity to size of 'hashes' argument", %{conn: conn} do |
||||||
|
# max of 12 addresses with four fields of complexity 1 can be fetched |
||||||
|
# per query: |
||||||
|
# 12 * 4 = 48, which is less than a max complexity of 50 |
||||||
|
hashes = 13 |> build_list(:address) |> Enum.map(&to_string(&1.hash)) |
||||||
|
|
||||||
|
query = """ |
||||||
|
query ($hashes: [AddressHash!]!) { |
||||||
|
addresses(hashes: $hashes) { |
||||||
|
hash |
||||||
|
fetched_coin_balance |
||||||
|
fetched_coin_balance_block_number |
||||||
|
contract_code |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
variables = %{"hashes" => hashes} |
||||||
|
|
||||||
|
conn = get(conn, "/graphql", query: query, variables: variables) |
||||||
|
|
||||||
|
assert %{"errors" => [error1, error2]} = json_response(conn, 200) |
||||||
|
assert error1["message"] =~ ~s(Field addresses is too complex) |
||||||
|
assert error2["message"] =~ ~s(Operation is too complex) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue