Merge pull request #1589 from poanetwork/rpc-list-accounts

feat: rpc endpoint to list addresses
pull/1597/head
Victor Baranov 6 years ago committed by GitHub
commit 44f523e566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  2. 66
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  3. 12
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  4. 43
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  5. 13
      apps/explorer/lib/explorer/chain.ex
  6. 7
      apps/explorer/priv/repo/migrations/20190318151809_add_inserted_at_index_to_accounts.exs

@ -4,6 +4,20 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
alias Explorer.{Chain, Etherscan} alias Explorer.{Chain, Etherscan}
alias Explorer.Chain.{Address, Wei} alias Explorer.Chain.{Address, Wei}
def listaccounts(conn, params) do
options =
params
|> optional_params()
|> Map.put_new(:page_number, 0)
|> Map.put_new(:page_size, 10)
accounts = list_accounts(options)
conn
|> put_status(200)
|> render(:listaccounts, %{accounts: accounts})
end
def balance(conn, params, template \\ :balance) do def balance(conn, params, template \\ :balance) do
with {:address_param, {:ok, address_param}} <- fetch_address(params), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do {:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do
@ -260,6 +274,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
Enum.any?(address_hashes, &(&1 == :error)) Enum.any?(address_hashes, &(&1 == :error))
end end
defp list_accounts(%{page_number: page_number, page_size: page_size}) do
offset = (max(page_number, 1) - 1) * page_size
# limit is just page_size
Chain.list_ordered_addresses(offset, page_size)
end
defp hashes_to_addresses(address_hashes) do defp hashes_to_addresses(address_hashes) do
address_hashes address_hashes
|> Chain.hashes_to_addresses() |> Chain.hashes_to_addresses()

@ -168,6 +168,17 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@account_listaccounts_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"address" => "0x0000000000000000000000000000000000000000",
"balance" => "135499"
}
]
}
@account_getminedblocks_example_value_error %{ @account_getminedblocks_example_value_error %{
"status" => "0", "status" => "0",
"message" => "No blocks found", "message" => "No blocks found",
@ -720,6 +731,14 @@ defmodule BlockScoutWeb.Etherscan do
} }
} }
@account_model %{
name: "Account",
fields: %{
"address" => @address_hash_type,
"balance" => @wei_type
}
}
@contract_model %{ @contract_model %{
name: "Contract", name: "Contract",
fields: %{ fields: %{
@ -1289,6 +1308,50 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@account_listaccounts_action %{
name: "listaccounts",
description:
"Get a list of accounts and their balances, sorted ascending by the time they were first seen by the explorer.",
required_params: [],
optional_params: [
%{
key: "page",
type: "integer",
description:
"A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction."
},
%{
key: "offset",
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@account_listaccounts_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "array",
array_type: @account_model
}
}
}
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@account_getminedblocks_example_value_error)
}
]
}
@logs_getlogs_action %{ @logs_getlogs_action %{
name: "getLogs", name: "getLogs",
description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.", description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.",
@ -1767,7 +1830,8 @@ defmodule BlockScoutWeb.Etherscan do
@account_tokentx_action, @account_tokentx_action,
@account_tokenbalance_action, @account_tokenbalance_action,
@account_tokenlist_action, @account_tokenlist_action,
@account_getminedblocks_action @account_getminedblocks_action,
@account_listaccounts_action
] ]
} }

@ -3,6 +3,11 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.RPCView
def render("listaccounts.json", %{accounts: accounts}) do
accounts = Enum.map(accounts, &prepare_account/1)
RPCView.render("show.json", data: accounts)
end
def render("balance.json", %{addresses: [address]}) do def render("balance.json", %{addresses: [address]}) do
RPCView.render("show.json", data: "#{address.fetched_coin_balance.value}") RPCView.render("show.json", data: "#{address.fetched_coin_balance.value}")
end end
@ -56,6 +61,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end end
defp prepare_account(address) do
%{
"balance" => to_string(address.fetched_coin_balance.value),
"address" => to_string(address.hash)
}
end
defp prepare_transaction(transaction) do defp prepare_transaction(transaction) do
%{ %{
"blockNumber" => "#{transaction.block_number}", "blockNumber" => "#{transaction.block_number}",

@ -6,6 +6,49 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
alias Explorer.Chain.{Transaction, Wei} alias Explorer.Chain.{Transaction, Wei}
alias BlockScoutWeb.API.RPC.AddressController alias BlockScoutWeb.API.RPC.AddressController
describe "listaccounts" do
setup do
%{params: %{"module" => "account", "action" => "listaccounts"}}
end
test "with no addresses", %{params: params, conn: conn} do
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == []
end
test "with existing addresses", %{params: params, conn: conn} do
first_address = insert(:address, fetched_coin_balance: 10, inserted_at: Timex.shift(Timex.now(), minutes: -10))
second_address = insert(:address, fetched_coin_balance: 100, inserted_at: Timex.shift(Timex.now(), minutes: -5))
first_address_hash = to_string(first_address.hash)
second_address_hash = to_string(second_address.hash)
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert [
%{
"address" => ^first_address_hash,
"balance" => "10"
},
%{
"address" => ^second_address_hash,
"balance" => "100"
}
] = response["result"]
end
end
describe "balance" do describe "balance" do
test "with missing address hash", %{conn: conn} do test "with missing address hash", %{conn: conn} do
params = %{ params = %{

@ -724,6 +724,19 @@ defmodule Explorer.Chain do
Repo.all(query) Repo.all(query)
end end
@spec list_ordered_addresses(non_neg_integer(), non_neg_integer()) :: [Address.t()]
def list_ordered_addresses(offset, limit) do
query =
from(
address in Address,
order_by: [asc: address.inserted_at],
offset: ^offset,
limit: ^limit
)
Repo.all(query)
end
def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query = query =
from( from(

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.AddInsertedAtIndexToAccounts do
use Ecto.Migration
def change do
create(index(:addresses, :inserted_at))
end
end
Loading…
Cancel
Save