From a07998547f6c8ac3740b42c3d5462a3f251641ee Mon Sep 17 00:00:00 2001 From: Sebastian Abondano Date: Thu, 30 Aug 2018 21:07:46 -0400 Subject: [PATCH] API account#txlist filterby=from support Why: * For API users to be able to get a list of transactions in which a given address is the 'sender' (address is the 'from' in transaction). Example usage: ``` /api?module=account&action&txlist&address={someAddress}&filterby=from ``` * Issue link: https://github.com/poanetwork/blockscout/issues/635 This change addresses the need by: * Editing `where_address_match/3` in `Explorer.Etherscan` to support `filter_by: "from"` option. * Editing `put_filter_by/2` in `API.RPC.AddressController` to support 'from' as an allowed value of the `filterby` parameter. * Editing API docs page per changes mentioned above. --- .../controllers/api/rpc/address_controller.ex | 2 +- .../lib/block_scout_web/etherscan.ex | 2 +- .../api/rpc/address_controller_test.exs | 73 +++++++++++++++++-- apps/explorer/lib/explorer/etherscan.ex | 4 + .../explorer/test/explorer/etherscan_test.exs | 33 +++++++++ 5 files changed, 107 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index 8d65c6febe..d9341a7b42 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -322,7 +322,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do defp put_filter_by(options, params) do case params do - %{"filterby" => filter_by} when filter_by in ["to"] -> + %{"filterby" => filter_by} when filter_by in ["from", "to"] -> Map.put(options, :filter_by, filter_by) _ -> diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index a09416952f..a73e7866b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -661,7 +661,7 @@ defmodule BlockScoutWeb.Etherscan do 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 + Available values: to, from """ } ], diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 5b58a65795..1ceb9c1413 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -985,6 +985,63 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert response["message"] == "OK" end + test "with filterby=to option", %{conn: conn} do + block = insert(:block) + address = insert(:address) + + insert(:transaction, from_address: address) + |> with_block(block) + + insert(:transaction, to_address: address) + |> with_block(block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "filterby" => "to" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 1 + assert response["status"] == "1" + assert response["message"] == "OK" + end + + test "with filterby=from option", %{conn: conn} do + block = insert(:block) + address = insert(:address) + + insert(:transaction, from_address: address) + |> with_block(block) + + insert(:transaction, from_address: address) + |> with_block(block) + + insert(:transaction, to_address: address) + |> with_block(block) + + params = %{ + "module" => "account", + "action" => "txlist", + "address" => "#{address.hash}", + "filterby" => "from" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert length(response["result"]) == 2 + assert response["status"] == "1" + assert response["message"] == "OK" + end + test "supports GET and POST requests", %{conn: conn} do address = insert(:address) @@ -1706,16 +1763,22 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert AddressController.optional_params(params3) == %{} end - test "'filterby' value can only be 'to'" do + test "'filterby' value can be 'to' or 'from'" do params1 = %{"filterby" => "to"} - optional_params = AddressController.optional_params(params1) + optional_params1 = AddressController.optional_params(params1) - assert optional_params.filter_by == "to" + assert optional_params1.filter_by == "to" + + params2 = %{"filterby" => "from"} - params2 = %{"filterby" => "invalid"} + optional_params2 = AddressController.optional_params(params2) - assert AddressController.optional_params(params2) == %{} + assert optional_params2.filter_by == "from" + + params3 = %{"filterby" => "invalid"} + + assert AddressController.optional_params(params3) == %{} end test "only includes optional params when they're given" do diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 3f6dd9f6ce..f4b9f646cd 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -226,6 +226,10 @@ defmodule Explorer.Etherscan do where(query, [t], t.to_address_hash == ^address_hash) end + defp where_address_match(query, address_hash, %{filter_by: "from"}) do + where(query, [t], t.from_address_hash == ^address_hash) + end + defp where_address_match(query, address_hash, _) do query |> where([t], t.to_address_hash == ^address_hash) diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 52b60f8870..418c7123ba 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -379,6 +379,39 @@ defmodule Explorer.EtherscanTest do assert length(found_transactions) == 0 end + + test "with filter_by: 'from' option with one matching transaction" do + address = insert(:address) + + :transaction + |> insert(to_address: address) + |> with_block() + + :transaction + |> insert(from_address: address) + |> with_block() + + options = %{filter_by: "from"} + + found_transactions = Etherscan.list_transactions(address.hash, options) + + assert length(found_transactions) == 1 + end + + test "with filter_by: 'from' option with non-matching transaction" do + address = insert(:address) + other_address = insert(:address) + + :transaction + |> insert(from_address: other_address, to_address: nil) + |> with_block() + + options = %{filter_by: "from"} + + found_transactions = Etherscan.list_transactions(address.hash, options) + + assert length(found_transactions) == 0 + end end describe "list_internal_transactions/1" do