Merge pull request #910 from poanetwork/sa-api-txlist-timestamp-range-option

account#txlist API endpoint takes timestamp range
pull/890/merge
Sebastian Abondano 6 years ago committed by GitHub
commit aae86d7e11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  2. 10
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  3. 156
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  4. 18
      apps/explorer/lib/explorer/etherscan.ex
  5. 35
      apps/explorer/test/explorer/etherscan_test.exs

@ -178,6 +178,8 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
|> put_start_block(params) |> put_start_block(params)
|> put_end_block(params) |> put_end_block(params)
|> put_filter_by(params) |> put_filter_by(params)
|> put_start_timestamp(params)
|> put_end_timestamp(params)
end end
@doc """ @doc """
@ -363,6 +365,28 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end end
end end
defp put_start_timestamp(options, params) do
with %{"starttimestamp" => starttimestamp_param} <- params,
{unix_timestamp, ""} <- Integer.parse(starttimestamp_param),
{:ok, start_timestamp} <- DateTime.from_unix(unix_timestamp) do
Map.put(options, :start_timestamp, start_timestamp)
else
_ ->
options
end
end
defp put_end_timestamp(options, params) do
with %{"endtimestamp" => endtimestamp_param} <- params,
{unix_timestamp, ""} <- Integer.parse(endtimestamp_param),
{:ok, end_timestamp} <- DateTime.from_unix(unix_timestamp) do
Map.put(options, :end_timestamp, end_timestamp)
else
_ ->
options
end
end
defp list_transactions(address_hash, options) do defp list_transactions(address_hash, options) do
case Etherscan.list_transactions(address_hash, options) do case Etherscan.list_transactions(address_hash, options) do
[] -> {:error, :not_found} [] -> {:error, :not_found}

@ -896,6 +896,16 @@ defmodule BlockScoutWeb.Etherscan do
it returns transactions that match to, from, or contract address. it returns transactions that match to, from, or contract address.
Available values: to, from Available values: to, from
""" """
},
%{
key: "starttimestamp",
type: "unix timestamp",
description: "Represents the starting block timestamp."
},
%{
key: "endtimestamp",
type: "unix timestamp",
description: "Represents the ending block timestamp."
} }
], ],
responses: [ responses: [

@ -986,6 +986,145 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
test "with starttimestamp and endtimestamp params", %{conn: conn} do
now = Timex.now()
timestamp1 = Timex.shift(now, hours: -6)
timestamp2 = Timex.shift(now, hours: -3)
timestamp3 = Timex.shift(now, hours: -1)
blocks1 = insert_list(2, :block, timestamp: timestamp1)
blocks2 = [third_block, fourth_block] = insert_list(2, :block, timestamp: timestamp2)
blocks3 = insert_list(2, :block, timestamp: timestamp3)
address = insert(:address)
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
|> insert_list(:transaction, from_address: address)
|> with_block(block)
end
start_timestamp = now |> Timex.shift(hours: -4) |> Timex.to_unix()
end_timestamp = now |> Timex.shift(hours: -2) |> Timex.to_unix()
params = %{
"module" => "account",
"action" => "txlist",
"address" => "#{address.hash}",
"starttimestamp" => "#{start_timestamp}",
"endtimestamp" => "#{end_timestamp}"
}
expected_block_numbers = [
"#{third_block.number}",
"#{fourth_block.number}"
]
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 4
for transaction <- response["result"] do
assert transaction["blockNumber"] in expected_block_numbers
end
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with starttimestamp but without endtimestamp", %{conn: conn} do
now = Timex.now()
timestamp1 = Timex.shift(now, hours: -6)
timestamp2 = Timex.shift(now, hours: -3)
timestamp3 = Timex.shift(now, hours: -1)
blocks1 = insert_list(2, :block, timestamp: timestamp1)
blocks2 = [third_block, fourth_block] = insert_list(2, :block, timestamp: timestamp2)
blocks3 = [fifth_block, sixth_block] = insert_list(2, :block, timestamp: timestamp3)
address = insert(:address)
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
|> insert_list(:transaction, from_address: address)
|> with_block(block)
end
start_timestamp = now |> Timex.shift(hours: -4) |> Timex.to_unix()
params = %{
"module" => "account",
"action" => "txlist",
"address" => "#{address.hash}",
"starttimestamp" => "#{start_timestamp}"
}
expected_block_numbers = [
"#{third_block.number}",
"#{fourth_block.number}",
"#{fifth_block.number}",
"#{sixth_block.number}"
]
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 8
for transaction <- response["result"] do
assert transaction["blockNumber"] in expected_block_numbers
end
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with endtimestamp but without starttimestamp", %{conn: conn} do
now = Timex.now()
timestamp1 = Timex.shift(now, hours: -6)
timestamp2 = Timex.shift(now, hours: -3)
timestamp3 = Timex.shift(now, hours: -1)
blocks1 = [first_block, second_block] = insert_list(2, :block, timestamp: timestamp1)
blocks2 = insert_list(2, :block, timestamp: timestamp2)
blocks3 = insert_list(2, :block, timestamp: timestamp3)
address = insert(:address)
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
|> insert_list(:transaction, from_address: address)
|> with_block(block)
end
end_timestamp = now |> Timex.shift(hours: -5) |> Timex.to_unix()
params = %{
"module" => "account",
"action" => "txlist",
"address" => "#{address.hash}",
"endtimestamp" => "#{end_timestamp}"
}
expected_block_numbers = [
"#{first_block.number}",
"#{second_block.number}"
]
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 4
for transaction <- response["result"] do
assert transaction["blockNumber"] in expected_block_numbers
end
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with filterby=to option", %{conn: conn} do test "with filterby=to option", %{conn: conn} do
block = insert(:block) block = insert(:block)
address = insert(:address) address = insert(:address)
@ -2017,17 +2156,24 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
"page" => "1", "page" => "1",
# page size # page size
"offset" => "2", "offset" => "2",
"filterby" => "to" "filterby" => "to",
"starttimestamp" => "1539186474",
"endtimestamp" => "1539186474"
} }
optional_params = AddressController.optional_params(params) optional_params = AddressController.optional_params(params)
# 1539186474 equals "2018-10-10 15:47:54Z"
{:ok, expected_timestamp, _} = DateTime.from_iso8601("2018-10-10 15:47:54Z")
assert optional_params.page_number == 1 assert optional_params.page_number == 1
assert optional_params.page_size == 2 assert optional_params.page_size == 2
assert optional_params.order_by_direction == :asc assert optional_params.order_by_direction == :asc
assert optional_params.start_block == 100 assert optional_params.start_block == 100
assert optional_params.end_block == 120 assert optional_params.end_block == 120
assert optional_params.filter_by == "to" assert optional_params.filter_by == "to"
assert optional_params.start_timestamp == expected_timestamp
assert optional_params.end_timestamp == expected_timestamp
end end
test "'sort' values can be 'asc' or 'desc'" do test "'sort' values can be 'asc' or 'desc'" do
@ -2076,7 +2222,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
"endblock" => "invalid", "endblock" => "invalid",
"sort" => "invalid", "sort" => "invalid",
"page" => "invalid", "page" => "invalid",
"offset" => "invalid" "offset" => "invalid",
"starttimestamp" => "invalid",
"endtimestamp" => "invalid"
} }
assert AddressController.optional_params(params1) == %{} assert AddressController.optional_params(params1) == %{}
@ -2086,7 +2234,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
"endblock" => "10", "endblock" => "10",
"sort" => "invalid", "sort" => "invalid",
"page" => "invalid", "page" => "invalid",
"offset" => "invalid" "offset" => "invalid",
"starttimestamp" => "invalid",
"endtimestamp" => "invalid"
} }
optional_params = AddressController.optional_params(params2) optional_params = AddressController.optional_params(params2)

@ -16,7 +16,9 @@ defmodule Explorer.Etherscan do
page_number: 1, page_number: 1,
page_size: 10_000, page_size: 10_000,
start_block: nil, start_block: nil,
end_block: nil end_block: nil,
start_timestamp: nil,
end_timestamp: nil
} }
@doc """ @doc """
@ -289,6 +291,8 @@ defmodule Explorer.Etherscan do
|> where_address_match(address_hash, options) |> where_address_match(address_hash, options)
|> where_start_block_match(options) |> where_start_block_match(options)
|> where_end_block_match(options) |> where_end_block_match(options)
|> where_start_timestamp_match(options)
|> where_end_timestamp_match(options)
|> Repo.all() |> Repo.all()
end end
@ -365,6 +369,18 @@ defmodule Explorer.Etherscan do
where(query, [..., block], block.number <= ^end_block) where(query, [..., block], block.number <= ^end_block)
end end
defp where_start_timestamp_match(query, %{start_timestamp: nil}), do: query
defp where_start_timestamp_match(query, %{start_timestamp: start_timestamp}) do
where(query, [..., block], ^start_timestamp <= block.timestamp)
end
defp where_end_timestamp_match(query, %{end_timestamp: nil}), do: query
defp where_end_timestamp_match(query, %{end_timestamp: end_timestamp}) do
where(query, [..., block], block.timestamp <= ^end_timestamp)
end
defp where_contract_address_match(query, nil), do: query defp where_contract_address_match(query, nil), do: query
defp where_contract_address_match(query, contract_address_hash) do defp where_contract_address_match(query, contract_address_hash) do

@ -344,6 +344,41 @@ defmodule Explorer.EtherscanTest do
end end
end end
test "with start and end timestamp options" do
now = Timex.now()
timestamp1 = Timex.shift(now, hours: -1)
timestamp2 = Timex.shift(now, hours: -3)
timestamp3 = Timex.shift(now, hours: -6)
blocks1 = insert_list(2, :block, timestamp: timestamp1)
blocks2 = [third_block, fourth_block] = insert_list(2, :block, timestamp: timestamp2)
blocks3 = insert_list(2, :block, timestamp: timestamp3)
address = insert(:address)
for block <- Enum.concat([blocks1, blocks2, blocks3]) do
2
|> insert_list(:transaction, from_address: address)
|> with_block(block)
end
start_timestamp = Timex.shift(now, hours: -4)
end_timestamp = Timex.shift(now, hours: -2)
options = %{
start_timestamp: start_timestamp,
end_timestamp: end_timestamp
}
found_transactions = Etherscan.list_transactions(address.hash, options)
expected_block_numbers = [third_block.number, fourth_block.number]
assert length(found_transactions) == 4
for transaction <- found_transactions do
assert transaction.block_number in expected_block_numbers
end
end
test "with filter_by: 'to' option with one matching transaction" do test "with filter_by: 'to' option with one matching transaction" do
address = insert(:address) address = insert(:address)
contract_address = insert(:contract_address) contract_address = insert(:contract_address)

Loading…
Cancel
Save