Add filterby flag to account#txlist

Why:

* For API users to get a list of transactions in which the given address
is the 'to' in the transaction.

  Example usage:
    ```
    /api?module=account&action&txlist&address={someAddress}&filterby=to
    ```

* Issue link: https://github.com/poanetwork/blockscout/issues/138

This change addresses the need by:

* Editing `API.RPC.AddressController`'s `txlist/2` action to support a
`filterby` flag in which the only allowed value for now is `to`. This is
an optional parameter. When given this filters the returned transactions
to only those in which the given address matches the `to` field. When
not given, the address provided could match the to, from, or contract
address for the transaction.
* Editing `Explorer.Etherscan`'s `list_transactions/3` to support the
new `filterby` flag mentioned above.
* Editing API docs data in `BlockScoutWeb.Etherscan` to include
information about the new `filterby` flag added in this commit.
pull/629/head
Sebastian Abondano 6 years ago
parent 8bdf93386d
commit 24e5da232f
  1. 11
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  2. 9
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  3. 16
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  4. 21
      apps/explorer/lib/explorer/etherscan.ex
  5. 36
      apps/explorer/test/explorer/etherscan_test.exs

@ -118,6 +118,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
|> put_pagination_options(params)
|> put_start_block(params)
|> put_end_block(params)
|> put_filter_by(params)
end
defp fetch_address(params) do
@ -246,6 +247,16 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end
end
defp put_filter_by(options, params) do
case params do
%{"filterby" => filter_by} when filter_by in ["to"] ->
Map.put(options, :filter_by, filter_by)
_ ->
options
end
end
defp list_transactions(address_hash, options) do
case Etherscan.list_transactions(address_hash, options) do
[] -> {:error, :not_found}

@ -603,6 +603,15 @@ defmodule BlockScoutWeb.Etherscan do
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
},
%{
key: "filterby",
type: "string",
description: """
A string representing the field to filter by. If none is given
it returns transactions that match to, from, or contract address.
Available values: to
"""
}
],
responses: [

@ -1522,7 +1522,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
# page number
"page" => "1",
# page size
"offset" => "2"
"offset" => "2",
"filterby" => "to"
}
optional_params = AddressController.optional_params(params)
@ -1532,6 +1533,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert optional_params.order_by_direction == :asc
assert optional_params.start_block == 100
assert optional_params.end_block == 120
assert optional_params.filter_by == "to"
end
test "'sort' values can be 'asc' or 'desc'" do
@ -1552,6 +1554,18 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert AddressController.optional_params(params3) == %{}
end
test "'filterby' value can only be 'to'" do
params1 = %{"filterby" => "to"}
optional_params = AddressController.optional_params(params1)
assert optional_params.filter_by == "to"
params2 = %{"filterby" => "invalid"}
assert AddressController.optional_params(params2) == %{}
end
test "only includes optional params when they're given" do
assert AddressController.optional_params(%{}) == %{}
end

@ -3,7 +3,7 @@ defmodule Explorer.Etherscan do
The etherscan context.
"""
import Ecto.Query, only: [from: 2, where: 3]
import Ecto.Query, only: [from: 2, where: 3, or_where: 3]
alias Explorer.Etherscan.Logs
alias Explorer.{Repo, Chain}
@ -30,6 +30,10 @@ defmodule Explorer.Etherscan do
@doc """
Gets a list of transactions for a given `t:Explorer.Chain.Hash.Address.t/0`.
If `filter_by: "to"` is given as an option, address matching is only done
against the `to_address_hash` column. If not, `to_address_hash`,
`from_address_hash`, and `created_contract_address_hash` are all considered.
"""
@spec list_transactions(Hash.Address.t()) :: [map()]
def list_transactions(
@ -179,9 +183,6 @@ defmodule Explorer.Etherscan do
from(
t in Transaction,
inner_join: b in assoc(t, :block),
where: t.to_address_hash == ^address_hash,
or_where: t.from_address_hash == ^address_hash,
or_where: t.created_contract_address_hash == ^address_hash,
order_by: [{^options.order_by_direction, t.block_number}],
limit: ^options.page_size,
offset: ^offset(options),
@ -193,11 +194,23 @@ defmodule Explorer.Etherscan do
)
query
|> where_address_match(address_hash, options)
|> where_start_block_match(options)
|> where_end_block_match(options)
|> Repo.all()
end
defp where_address_match(query, address_hash, %{filter_by: "to"}) do
where(query, [t], t.to_address_hash == ^address_hash)
end
defp where_address_match(query, address_hash, _) do
query
|> where([t], t.to_address_hash == ^address_hash)
|> or_where([t], t.from_address_hash == ^address_hash)
|> or_where([t], t.created_contract_address_hash == ^address_hash)
end
@token_transfer_fields ~w(
token_contract_address_hash
transaction_hash

@ -343,6 +343,42 @@ defmodule Explorer.EtherscanTest do
assert transaction.block_number in expected_block_numbers
end
end
test "with filter_by: 'to' option with one matching transaction" do
address = insert(:address)
contract_address = insert(:contract_address)
:transaction
|> insert(to_address: address)
|> with_block()
:transaction
|> insert(from_address: address, to_address: nil)
|> with_contract_creation(contract_address)
|> with_block()
options = %{filter_by: "to"}
found_transactions = Etherscan.list_transactions(address.hash, options)
assert length(found_transactions) == 1
end
test "with filter_by: 'to' option with non-matching transaction" do
address = insert(:address)
contract_address = insert(:contract_address)
:transaction
|> insert(from_address: address, to_address: nil)
|> with_contract_creation(contract_address)
|> with_block()
options = %{filter_by: "to"}
found_transactions = Etherscan.list_transactions(address.hash, options)
assert length(found_transactions) == 0
end
end
describe "list_internal_transactions/1" do

Loading…
Cancel
Save