Last seen paging on address page #230
pull/307/head
John Stamates 7 years ago committed by GitHub
commit 3160ac1967
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 364
      apps/explorer/lib/explorer/chain.ex
  2. 1
      apps/explorer/lib/explorer/repo.ex
  3. 3
      apps/explorer/mix.exs
  4. 398
      apps/explorer/test/explorer/chain_test.exs
  5. 3
      apps/explorer_web/lib/explorer_web.ex
  6. 87
      apps/explorer_web/lib/explorer_web/chain.ex
  7. 40
      apps/explorer_web/lib/explorer_web/controllers/address_internal_transaction_controller.ex
  8. 44
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_controller.ex
  9. 18
      apps/explorer_web/lib/explorer_web/controllers/block_controller.ex
  10. 35
      apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
  11. 35
      apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex
  12. 43
      apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex
  13. 26
      apps/explorer_web/lib/explorer_web/controllers/transaction_internal_transaction_controller.ex
  14. 20
      apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex
  15. 30
      apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex
  16. 30
      apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
  17. 29
      apps/explorer_web/lib/explorer_web/templates/block/index.html.eex
  18. 28
      apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
  19. 23
      apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex
  20. 4
      apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex
  21. 30
      apps/explorer_web/lib/explorer_web/templates/transaction_internal_transaction/index.html.eex
  22. 13
      apps/explorer_web/lib/explorer_web/templates/transaction_log/index.html.eex
  23. 2
      apps/explorer_web/mix.exs
  24. 11
      apps/explorer_web/priv/gettext/default.pot
  25. 11
      apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
  26. 129
      apps/explorer_web/test/explorer_web/controllers/address_internal_transaction_controller_test.exs
  27. 64
      apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
  28. 43
      apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs
  29. 33
      apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs
  30. 57
      apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs
  31. 29
      apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs
  32. 76
      apps/explorer_web/test/explorer_web/controllers/transaction_internal_transaction_controller_test.exs
  33. 52
      apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs
  34. 4
      apps/explorer_web/test/explorer_web/features/viewing_blocks_test.exs
  35. 8
      mix.lock

@ -9,7 +9,6 @@ defmodule Explorer.Chain do
join: 4,
join: 5,
limit: 2,
or_where: 3,
order_by: 2,
order_by: 3,
preload: 2,
@ -36,6 +35,8 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Block.Reward
alias Explorer.{PagingOptions, Repo}
@default_paging_options %PagingOptions{page_size: 50}
@typedoc """
The name of an association on the `t:Ecto.Schema.t/0`
"""
@ -55,16 +56,8 @@ defmodule Explorer.Chain do
"""
@type necessity_by_association :: %{association => necessity}
@typedoc """
Pagination params used by `scrivener`
"""
@type pagination :: map()
@typep direction_option :: {:direction, direction}
@typep inserted_after_option :: {:inserted_after, DateTime.t()}
@typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association}
@typep on_conflict_option :: {:on_conflict, :nothing | :replace_all}
@typep pagination_option :: {:pagination, pagination}
@typep paging_options :: {:paging_options, PagingOptions.t()}
@typep params_option :: {:params, [map()]}
@typep timeout_option :: {:timeout, timeout}
@ -92,13 +85,18 @@ defmodule Explorer.Chain do
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{block_number, transaction_index, index}`) and. Results will be the internal
transactions older than the `block_number`, `transaction index`, and `index` that are passed.
"""
@spec address_to_internal_transactions(Address.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t()
]
def address_to_internal_transactions(%Address{hash: hash}, options \\ []) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
direction = Keyword.get(options, :direction)
pagination = Keyword.get(options, :pagination, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
InternalTransaction
|> join(
@ -109,6 +107,8 @@ defmodule Explorer.Chain do
|> join(:left, [internal_transaction, transaction], block in assoc(transaction, :block))
|> where_address_fields_match(hash, direction)
|> where_transaction_has_multiple_internal_transactions()
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
|> order_by(
[it, transaction, block],
desc: block.number,
@ -117,7 +117,7 @@ defmodule Explorer.Chain do
)
|> preload(transaction: :block)
|> join_associations(necessity_by_association)
|> Repo.paginate(pagination)
|> Repo.all()
end
@doc """
@ -135,21 +135,29 @@ defmodule Explorer.Chain do
## Options
* `:direction` - if specified, will filter transactions by address type. If `:to` is specified, only transactions
where the "to" address matches will be returned. Likewise, if `:from` is specified, only transactions where the
"from" address matches will be returned. If `:direction` is omitted, transactions either to or from the address
will be returned.
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than
the `block_number` and `index` that are passed.
"""
@spec address_to_transactions(Address.t(), [
direction_option | necessity_by_association_option | pagination_option
]) :: %Scrivener.Page{entries: [Transaction.t()]}
def address_to_transactions(%Address{hash: hash}, options \\ []) when is_list(options) do
address_hash_to_transactions(hash, options)
@spec address_to_transactions(Address.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
def address_to_transactions(
%Address{hash: %Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash},
options \\ []
)
when is_list(options) do
direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
options
|> Keyword.get(:paging_options, @default_paging_options)
|> fetch_transactions()
|> where_address_fields_match(address_hash, direction)
|> join_associations(necessity_by_association)
|> Repo.all()
end
@doc """
@ -399,33 +407,28 @@ defmodule Explorer.Chain do
end
@doc """
Finds all `t:Explorer.Chain.Transaction.t/0` in the `t:Explorer.Chain.Block.t/0`.
Finds all `t:Explorer.Chain.Transaction.t/0`s in the `t:Explorer.Chain.Block.t/0`.
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than
the `index` that are passed.
"""
@spec block_to_transactions(Block.t()) :: %Scrivener.Page{entries: [Transaction.t()]}
@spec block_to_transactions(Block.t(), [necessity_by_association_option | pagination_option]) :: %Scrivener.Page{
entries: [Transaction.t()]
}
@spec block_to_transactions(Block.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
def block_to_transactions(%Block{hash: block_hash}, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
pagination = Keyword.get(options, :pagination, %{})
Transaction
|> load_contract_creation()
|> select_merge([_, internal_transaction], %{
created_contract_address_hash: internal_transaction.created_contract_address_hash
})
options
|> Keyword.get(:paging_options, @default_paging_options)
|> fetch_transactions()
|> join(:inner, [transaction], block in assoc(transaction, :block))
|> where([_, _, block], block.hash == ^block_hash)
|> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash)
|> join_associations(necessity_by_association)
|> Repo.paginate(pagination)
|> Repo.all()
end
@doc """
@ -677,12 +680,8 @@ defmodule Explorer.Chain do
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
Transaction
fetch_transactions()
|> where(hash: ^hash)
|> load_contract_creation()
|> select_merge([_, internal_transaction], %{
created_contract_address_hash: internal_transaction.created_contract_address_hash
})
|> join_associations(necessity_by_association)
|> Repo.one()
|> case do
@ -1309,20 +1308,22 @@ defmodule Explorer.Chain do
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Block.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{block_number}`) and. Results will be the internal
transactions older than the `block_number` that are passed.
"""
@spec list_blocks([necessity_by_association_option | pagination_option]) :: %Scrivener.Page{
entries: [Block.t()]
}
@spec list_blocks([paging_options | necessity_by_association_option]) :: [Block.t()]
def list_blocks(options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
pagination = Keyword.get(options, :pagination, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Block
|> join_associations(necessity_by_association)
|> page_blocks(paging_options)
|> limit(^paging_options.page_size)
|> order_by(desc: :number)
|> Repo.paginate(pagination)
|> Repo.all()
end
@doc """
@ -1901,37 +1902,30 @@ defmodule Explorer.Chain do
`:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest {block_number, index}) and. Results will be the transactions older than
the block number and index that are passed.
`:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than
the `block_number` and `index` that are passed.
"""
@spec recent_collated_transactions([paging_options | necessity_by_association_option]) :: [
Transaction.t()
]
@spec recent_collated_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()]
def recent_collated_transactions(options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, %PagingOptions{page_size: 50})
Transaction
|> load_contract_creation()
|> select_merge([_, internal_transaction], %{
created_contract_address_hash: internal_transaction.created_contract_address_hash
})
options
|> Keyword.get(:paging_options, @default_paging_options)
|> fetch_transactions()
|> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index))
|> page_transaction(paging_options)
|> limit(^paging_options.page_size)
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
|> join_associations(necessity_by_association)
|> Repo.all()
end
@doc """
Return the list of pending transactions that occurred recently (10).
Return the list of pending transactions that occurred recently.
iex> 2 |> insert_list(:transaction)
iex> :transaction |> insert() |> with_block()
iex> 8 |> insert_list(:transaction)
iex> %Scrivener.Page{entries: recent_pending_transactions} = Explorer.Chain.recent_pending_transactions()
iex> recent_pending_transactions = Explorer.Chain.recent_pending_transactions()
iex> length(recent_pending_transactions)
10
iex> Enum.all?(recent_pending_transactions, fn %Explorer.Chain.Transaction{block_hash: block_hash} ->
@ -1939,69 +1933,28 @@ defmodule Explorer.Chain do
...> end)
true
A `t:Explorer.Chain.Transaction.t/0` `inserted_at` can be supplied to the `:inserted_after` option, then only pending
transactions inserted after that transaction will be returned. This can be used to generate paging for pending
transactions.
iex> {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
iex> insert(:transaction, inserted_at: first_inserted_at)
iex> {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z")
iex> insert(:transaction, inserted_at: second_inserted_at)
iex> %Scrivener.Page{entries: after_first_transaction} = Explorer.Chain.recent_pending_transactions(
...> inserted_after: first_inserted_at
...> )
iex> length(after_first_transaction)
1
iex> %Scrivener.Page{entries: after_second_transaction} = Explorer.Chain.recent_pending_transactions(
...> inserted_after: second_inserted_at
...> )
iex> length(after_second_transaction)
0
When there are no pending transaction and a collated transaction's inserted_at is used, an empty list is returned
iex> {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
iex> :transaction |> insert(inserted_at: first_inserted_at) |> with_block()
iex> {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z")
iex> :transaction |> insert(inserted_at: second_inserted_at) |> with_block()
iex> %Scrivener.Page{entries: entries} = Explorer.Chain.recent_pending_transactions(
...> after_inserted_at: first_inserted_at
...> )
iex> entries
[]
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` (defaults to
`#{@default_paging_options.page_size}`) and `:key` (a tuple of the lowest/oldest `{inserted_at, hash}`) and.
Results will be the transactions older than the `inserted_at` and `hash` that are passed.
"""
@spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: %Scrivener.Page{
entries: [Transaction.t()]
}
@spec recent_pending_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()]
def recent_pending_transactions(options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
pagination = Keyword.get(options, :pagination, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
query =
from(
transaction in Transaction,
where: is_nil(transaction.block_hash),
order_by: [
desc: transaction.inserted_at,
# arbitary tie-breaker when inserted at is the same. hash is random distribution, but using it keeps order
# consistent at least
desc: transaction.hash
],
limit: 10
)
query
|> inserted_after(options)
Transaction
|> page_pending_transaction(paging_options)
|> limit(^paging_options.page_size)
|> where([transaction], is_nil(transaction.block_hash))
|> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash)
|> join_associations(necessity_by_association)
|> Repo.paginate(pagination)
|> Repo.all()
end
@doc """
@ -2098,29 +2051,31 @@ defmodule Explorer.Chain do
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the internal transactions older than
the `index` that is passed.
"""
@spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: %Scrivener.Page{
entries: [InternalTransaction.t()]
}
@spec transaction_hash_to_internal_transactions(Hash.Full.t(), [
necessity_by_association_option | pagination_option
]) :: %Scrivener.Page{entries: [InternalTransaction.t()]}
def transaction_hash_to_internal_transactions(
%Hash{byte_count: unquote(Hash.Full.byte_count())} = hash,
@spec transaction_to_internal_transactions(Transaction.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t()
]
def transaction_to_internal_transactions(
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash},
options \\ []
)
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
pagination = Keyword.get(options, :pagination, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
InternalTransaction
|> for_parent_transaction(hash)
|> join_associations(necessity_by_association)
|> where_transaction_has_multiple_internal_transactions()
|> order_by(:index)
|> Repo.paginate(pagination)
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
|> order_by([internal_transaction], desc: internal_transaction.index)
|> Repo.all()
end
@doc """
@ -2131,14 +2086,28 @@ defmodule Explorer.Chain do
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Log.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Log.t/0` will not be included in the page `entries`.
* `:pagination` - pagination params to pass to scrivener.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than
the `index` that are passed.
"""
@spec transaction_to_logs(Transaction.t(), [
necessity_by_association_option | pagination_option
]) :: %Scrivener.Page{entries: [Log.t()]}
def transaction_to_logs(%Transaction{hash: hash}, options \\ []) when is_list(options) do
transaction_hash_to_logs(hash, options)
@spec transaction_to_logs(Transaction.t(), [paging_options | necessity_by_association_option]) :: [Log.t()]
def transaction_to_logs(
%Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
options \\ []
)
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Log
|> join(:inner, [log], transaction in assoc(log, :transaction))
|> where([_, transaction], transaction.hash == ^transaction_hash)
|> page_logs(paging_options)
|> limit(^paging_options.page_size)
|> order_by([log], asc: log.index)
|> join_associations(necessity_by_association)
|> Repo.all()
end
@doc """
@ -2195,26 +2164,6 @@ defmodule Explorer.Chain do
|> Repo.insert()
end
defp address_hash_to_transactions(
%Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash,
named_arguments
)
when is_list(named_arguments) do
direction = Keyword.get(named_arguments, :direction)
necessity_by_association = Keyword.get(named_arguments, :necessity_by_association, %{})
pagination = Keyword.get(named_arguments, :pagination, %{})
Transaction
|> load_contract_creation()
|> select_merge([_, internal_transaction], %{
created_contract_address_hash: internal_transaction.created_contract_address_hash
})
|> join_associations(necessity_by_association)
|> reverse_chronologically()
|> where_address_fields_match(address_hash, direction)
|> Repo.paginate(pagination)
end
@spec changes_list(params :: map, [{:for, module} | {:with, :atom}]) ::
{:ok, changes :: map} | {:error, [Changeset.t()]}
defp changes_list(params, options) when is_list(options) do
@ -2274,6 +2223,16 @@ defmodule Explorer.Chain do
end)
end
defp fetch_transactions(paging_options \\ nil) do
Transaction
|> load_contract_creation()
|> select_merge([_, internal_transaction], %{
created_contract_address_hash: internal_transaction.created_contract_address_hash
})
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
|> handle_paging_options(paging_options)
end
defp for_parent_transaction(query, %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash) do
from(
child in query,
@ -2492,14 +2451,12 @@ defmodule Explorer.Chain do
{:ok, for(transaction <- transactions, do: transaction.hash)}
end
defp inserted_after(query, options) do
case Keyword.fetch(options, :inserted_after) do
{:ok, inserted_after} ->
from(transaction in query, where: ^inserted_after < transaction.inserted_at)
defp handle_paging_options(query, nil), do: query
:error ->
query
end
defp handle_paging_options(query, paging_options) do
query
|> page_transaction(paging_options)
|> limit(^paging_options.page_size)
end
defp join_association(query, association, necessity) when is_atom(association) do
@ -2519,8 +2476,8 @@ defmodule Explorer.Chain do
end
defp load_contract_creation(query) do
query
|> join(
join(
query,
:left,
[transaction],
internal_transaction in assoc(transaction, :internal_transactions),
@ -2528,19 +2485,58 @@ defmodule Explorer.Chain do
)
end
defp page_blocks(query, %PagingOptions{key: nil}), do: query
defp page_blocks(query, %PagingOptions{key: {block_number}}) do
where(query, [block], block.number < ^block_number)
end
defp page_internal_transaction(query, %PagingOptions{key: nil}), do: query
defp page_internal_transaction(query, %PagingOptions{key: {block_number, transaction_index, index}}) do
where(
query,
[internal_transaction, transaction],
transaction.block_number < ^block_number or
(transaction.block_number == ^block_number and transaction.index < ^transaction_index) or
(transaction.block_number == ^block_number and transaction.index == ^transaction_index and
internal_transaction.index < ^index)
)
end
defp page_internal_transaction(query, %PagingOptions{key: {index}}) do
where(query, [internal_transaction], internal_transaction.index < ^index)
end
defp page_logs(query, %PagingOptions{key: nil}), do: query
defp page_logs(query, %PagingOptions{key: {index}}) do
where(query, [log], log.index > ^index)
end
defp page_pending_transaction(query, %PagingOptions{key: nil}), do: query
defp page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do
where(
query,
[transaction],
transaction.inserted_at < ^inserted_at or (transaction.inserted_at == ^inserted_at and transaction.hash < ^hash)
)
end
defp page_transaction(query, %PagingOptions{key: nil}), do: query
defp page_transaction(query, %PagingOptions{key: {block_number, index}}) do
query
|> where(
where(
query,
[transaction],
transaction.block_number < ^block_number or
(transaction.block_number == ^block_number and transaction.index < ^index)
)
end
defp reverse_chronologically(query) do
from(q in query, order_by: [desc: q.inserted_at, desc: q.hash])
defp page_transaction(query, %PagingOptions{key: {index}}) do
where(query, [transaction], transaction.index < ^index)
end
defp run_addresses(multi, ecto_schema_module_to_changes_list, options)
@ -2656,38 +2652,20 @@ defmodule Explorer.Chain do
%{inserted_at: now, updated_at: now}
end
defp transaction_hash_to_logs(
%Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash,
options
)
when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
pagination = Keyword.get(options, :pagination, %{})
query =
from(
log in Log,
join: transaction in assoc(log, :transaction),
where: transaction.hash == ^transaction_hash,
order_by: [asc: :index]
)
defp where_address_fields_match(query, address_hash) do
where_address_fields_match(query, address_hash, nil)
end
query
|> join_associations(necessity_by_association)
|> Repo.paginate(pagination)
defp where_address_fields_match(query, address_hash, :to) do
where(query, [t], field(t, ^:to_address_hash) == ^address_hash)
end
defp where_address_fields_match(query, address_hash, direction \\ nil) do
address_fields =
case direction do
:to -> [:to_address_hash]
:from -> [:from_address_hash]
nil -> [:to_address_hash, :from_address_hash]
end
defp where_address_fields_match(query, address_hash, :from) do
where(query, [t], field(t, ^:from_address_hash) == ^address_hash)
end
Enum.reduce(address_fields, query, fn field, query ->
or_where(query, [t], field(t, ^field) == ^address_hash)
end)
defp where_address_fields_match(query, address_hash, nil) do
where(query, [t], field(t, ^:to_address_hash) == ^address_hash or field(t, ^:from_address_hash) == ^address_hash)
end
defp where_transaction_has_multiple_internal_transactions(query) do

@ -1,6 +1,5 @@
defmodule Explorer.Repo do
use Ecto.Repo, otp_app: :explorer
use Scrivener, page_size: 10
require Logger

@ -52,7 +52,6 @@ defmodule Explorer.Mixfile do
:logger,
:mix,
:runtime_tools,
:scrivener_ecto,
:timex,
:timex_ecto
]
@ -85,8 +84,6 @@ defmodule Explorer.Mixfile do
{:mock, "~> 0.3.0", only: [:test], runtime: false},
{:mox, "~> 0.3.2", only: [:test]},
{:postgrex, ">= 0.0.0"},
{:scrivener_ecto, "~> 1.0"},
{:scrivener_html, "~> 1.7"},
{:sobelow, ">= 0.7.0", only: [:dev, :test], runtime: false},
{:timex, "~> 3.1.24"},
{:timex_ecto, "~> 3.2.1"}

@ -3,8 +3,8 @@ defmodule Explorer.ChainTest do
import Explorer.Factory
alias Explorer.{Chain, Repo, Factory}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction, Wei, SmartContract}
alias Explorer.{Chain, Factory, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, SmartContract, Transaction, Wei}
alias Explorer.Chain.Supply.ProofOfAuthority
doctest Explorer.Chain
@ -30,106 +30,80 @@ defmodule Explorer.ChainTest do
assert Repo.aggregate(Transaction, :count, :hash) == 0
assert %Scrivener.Page{
entries: [],
page_number: 1,
total_entries: 0
} = Chain.address_to_transactions(address)
assert [] == Chain.address_to_transactions(address)
end
test "with from transactions" do
%Transaction{from_address_hash: from_address_hash, hash: transaction_hash} = insert(:transaction)
address = Repo.get!(Address, from_address_hash)
address = insert(:address)
transaction = :transaction |> insert(from_address: address) |> with_block()
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :from)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to transactions" do
%Transaction{to_address_hash: to_address_hash, hash: transaction_hash} = insert(:transaction)
address = Repo.get!(Address, to_address_hash)
address = insert(:address)
transaction = :transaction |> insert(to_address: address) |> with_block()
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :to)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :from" do
%Transaction{from_address: address, hash: from_transaction_hash} =
:transaction
|> insert()
|> Repo.preload(:from_address)
insert(:transaction, to_address: address)
address = insert(:address)
transaction = :transaction |> insert(from_address: address) |> with_block()
:transaction |> insert(to_address: address) |> with_block()
# only contains "from" transaction
assert %Scrivener.Page{
entries: [%Transaction{hash: ^from_transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :from)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :to" do
%Transaction{from_address: address} =
:transaction
|> insert()
|> Repo.preload(:from_address)
%Transaction{hash: to_transaction_hash} = insert(:transaction, to_address: address)
address = insert(:address)
transaction = :transaction |> insert(to_address: address) |> with_block()
:transaction |> insert(from_address: address) |> with_block()
# only contains "to" transaction
assert %Scrivener.Page{
entries: [%Transaction{hash: ^to_transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.address_to_transactions(address, direction: :to)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and no :direction option" do
%Transaction{from_address: address, hash: from_transaction_hash} =
:transaction
|> insert()
|> Repo.preload(:from_address)
%Transaction{hash: to_transaction_hash} = insert(:transaction, to_address: address)
address = insert(:address)
block = insert(:block)
transaction1 = :transaction |> insert(to_address: address) |> with_block(block)
transaction2 = :transaction |> insert(from_address: address) |> with_block(block)
assert %Scrivener.Page{
entries: [
%Transaction{hash: ^to_transaction_hash},
%Transaction{hash: ^from_transaction_hash}
],
page_number: 1,
total_entries: 2
} = Chain.address_to_transactions(address)
assert [transaction2, transaction1] ==
Chain.address_to_transactions(address) |> Repo.preload([:block, :to_address, :from_address])
end
test "with transactions can be paginated" do
address = insert(:address)
transactions = insert_list(2, :transaction, to_address: address)
[%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions
assert %Scrivener.Page{
entries: [%Transaction{hash: ^newest_transaction_hash}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.address_to_transactions(address, pagination: %{page_size: 1})
assert %Scrivener.Page{
entries: [%Transaction{hash: ^oldest_transaction_hash}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.address_to_transactions(address, pagination: %{page: 2, page_size: 1})
second_page_hashes =
50
|> insert_list(:transaction, from_address: address)
|> with_block()
|> Enum.map(& &1.hash)
%Transaction{block_number: block_number, index: index} =
:transaction
|> insert(from_address: address)
|> with_block()
assert second_page_hashes ==
address
|> Chain.address_to_transactions(
paging_options: %PagingOptions{key: {block_number, index}, page_size: 50}
)
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
end
@ -152,17 +126,13 @@ defmodule Explorer.ChainTest do
end
end
describe "block_to_transactions/1" do
describe "block_to_transactions/2" do
test "without transactions" do
block = insert(:block)
assert Repo.aggregate(Transaction, :count, :hash) == 0
assert %Scrivener.Page{
entries: [],
page_number: 1,
total_entries: 0
} = Chain.block_to_transactions(block)
assert [] = Chain.block_to_transactions(block)
end
test "with transactions" do
@ -171,40 +141,28 @@ defmodule Explorer.ChainTest do
|> insert()
|> with_block()
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
page_number: 1,
total_entries: 1
} = Chain.block_to_transactions(block)
assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block)
end
test "with transactions can be paginated" do
block = insert(:block)
transactions =
Enum.map(0..1, fn _ ->
:transaction
|> insert()
|> with_block(block)
end)
[%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions
assert %Scrivener.Page{
entries: [%Transaction{hash: ^second_transaction_hash}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.block_to_transactions(block, pagination: %{page_size: 1})
assert %Scrivener.Page{
entries: [%Transaction{hash: ^first_transaction_hash}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.block_to_transactions(block, pagination: %{page: 2, page_size: 1})
second_page_hashes =
50
|> insert_list(:transaction)
|> with_block(block)
|> Enum.map(& &1.hash)
%Transaction{block_number: block_number, index: index} =
:transaction
|> insert()
|> with_block(block)
assert second_page_hashes ==
block
|> Chain.block_to_transactions(paging_options: %PagingOptions{key: {block_number, index}, page_size: 50})
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
end
@ -341,13 +299,14 @@ defmodule Explorer.ChainTest do
end
test "created_contract_address_hash populated when existing" do
%Transaction{hash: hash_with_block} =
transaction =
%Transaction{hash: hash_with_block} =
:transaction
|> insert()
|> with_block()
%InternalTransaction{created_contract_address_hash: contract_hash} =
insert(:internal_transaction_create, transaction_hash: hash_with_block, index: 0)
insert(:internal_transaction_create, transaction: transaction, index: 0)
assert {:ok, %Transaction{hash: ^hash_with_block, created_contract_address_hash: ^contract_hash}} =
Chain.hash_to_transaction(
@ -359,44 +318,28 @@ defmodule Explorer.ChainTest do
describe "list_blocks/2" do
test "without blocks" do
assert %Scrivener.Page{
entries: [],
page_number: 1,
total_entries: 0,
total_pages: 1
} = Chain.list_blocks()
assert [] = Chain.list_blocks()
end
test "with blocks" do
%Block{hash: hash} = insert(:block)
assert %Scrivener.Page{
entries: [%Block{hash: ^hash}],
page_number: 1,
total_entries: 1
} = Chain.list_blocks()
assert [%Block{hash: ^hash}] = Chain.list_blocks()
end
test "with blocks can be paginated" do
blocks = insert_list(2, :block)
[%Block{number: lesser_block_number}, %Block{number: greater_block_number}] = blocks
assert %Scrivener.Page{
entries: [%Block{number: ^greater_block_number}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.list_blocks(pagination: %{page_size: 1})
assert %Scrivener.Page{
entries: [%Block{number: ^lesser_block_number}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.list_blocks(pagination: %{page: 2, page_size: 1})
second_page_block_ids =
50
|> insert_list(:block)
|> Enum.map(& &1.number)
block = insert(:block)
assert second_page_block_ids ==
[paging_options: %PagingOptions{key: {block.number}, page_size: 50}]
|> Chain.list_blocks()
|> Enum.map(& &1.number)
|> Enum.reverse()
end
end
@ -441,7 +384,7 @@ defmodule Explorer.ChainTest do
transaction: %Transaction{}
}
| _
] = Map.get(Chain.address_to_internal_transactions(address), :entries, [])
] = Chain.address_to_internal_transactions(address)
assert [
%InternalTransaction{
@ -451,17 +394,13 @@ defmodule Explorer.ChainTest do
}
| _
] =
Map.get(
Chain.address_to_internal_transactions(
address,
necessity_by_association: %{
from_address: :optional,
to_address: :optional,
transaction: :optional
}
),
:entries,
[]
Chain.address_to_internal_transactions(
address,
necessity_by_association: %{
from_address: :optional,
to_address: :optional,
transaction: :optional
}
)
end
@ -556,7 +495,6 @@ defmodule Explorer.ChainTest do
result =
address
|> Chain.address_to_internal_transactions()
|> Map.get(:entries, [])
|> Enum.map(& &1.id)
assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result
@ -597,29 +535,48 @@ defmodule Explorer.ChainTest do
end
end
describe "transaction_hash_to_internal_transactions/1" do
test "without transaction" do
{:ok, hash} =
Chain.string_to_transaction_hash("0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b")
describe "pending_transactions/0" do
test "without transactions" do
assert [] = Chain.recent_pending_transactions()
end
test "with transactions" do
%Transaction{hash: hash} = insert(:transaction)
assert Chain.transaction_hash_to_internal_transactions(hash).entries == []
assert [%Transaction{hash: ^hash}] = Chain.recent_pending_transactions()
end
test "with transactions can be paginated" do
second_page_hashes =
50
|> insert_list(:transaction)
|> Enum.map(& &1.hash)
%Transaction{inserted_at: inserted_at, hash: hash} = insert(:transaction)
assert second_page_hashes ==
[paging_options: %PagingOptions{key: {inserted_at, hash}, page_size: 50}]
|> Chain.recent_pending_transactions()
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
end
describe "transaction_to_internal_transactions/1" do
test "with transaction without internal transactions" do
%Transaction{hash: hash} = insert(:transaction)
transaction = insert(:transaction)
assert Chain.transaction_hash_to_internal_transactions(hash).entries == []
assert [] = Chain.transaction_to_internal_transactions(transaction)
end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do
transaction = insert(:transaction)
first = insert(:internal_transaction, transaction_hash: transaction.hash, index: 0)
second = insert(:internal_transaction, transaction_hash: transaction.hash, index: 1)
first = insert(:internal_transaction, transaction: transaction, index: 0)
second = insert(:internal_transaction, transaction: transaction, index: 1)
results =
transaction.hash
|> Chain.transaction_hash_to_internal_transactions()
|> Map.get(:entries, [])
transaction
|> Chain.transaction_to_internal_transactions()
|> Enum.map(& &1.id)
assert 2 == length(results)
@ -628,8 +585,8 @@ defmodule Explorer.ChainTest do
end
test "with transaction with internal transactions loads associations with in necessity_by_association" do
%Transaction{hash: hash} = insert(:transaction)
insert(:internal_transaction_create, transaction_hash: hash, index: 0)
transaction = insert(:transaction)
insert(:internal_transaction_create, transaction: transaction, index: 0)
assert [
%InternalTransaction{
@ -637,7 +594,7 @@ defmodule Explorer.ChainTest do
to_address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_hash_to_internal_transactions(hash).entries
] = Chain.transaction_to_internal_transactions(transaction)
assert [
%InternalTransaction{
@ -646,25 +603,25 @@ defmodule Explorer.ChainTest do
transaction: %Transaction{}
}
] =
Chain.transaction_hash_to_internal_transactions(
hash,
Chain.transaction_to_internal_transactions(
transaction,
necessity_by_association: %{
from_address: :optional,
to_address: :optional,
transaction: :optional
}
).entries
)
end
test "excludes internal transaction of type call with no siblings in the transaction" do
%Transaction{hash: hash} =
transaction =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction, transaction_hash: hash, index: 0)
insert(:internal_transaction, transaction: transaction, index: 0)
result = Chain.transaction_hash_to_internal_transactions(hash)
result = Chain.transaction_to_internal_transactions(transaction)
assert Enum.empty?(result)
end
@ -675,28 +632,28 @@ defmodule Explorer.ChainTest do
|> insert()
|> with_block()
expected = insert(:internal_transaction_create, index: 0, transaction_hash: transaction.hash)
expected = insert(:internal_transaction_create, index: 0, transaction: transaction)
actual = Enum.at(Chain.transaction_hash_to_internal_transactions(transaction.hash), 0)
actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0)
assert actual.id == expected.id
end
test "returns the internal transactions in index order" do
%Transaction{hash: hash} =
test "returns the internal transactions in descending index order" do
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{id: first_id} = insert(:internal_transaction, transaction_hash: hash, index: 0)
%InternalTransaction{id: second_id} = insert(:internal_transaction, transaction_hash: hash, index: 1)
%InternalTransaction{id: first_id} = insert(:internal_transaction, transaction: transaction, index: 0)
%InternalTransaction{id: second_id} = insert(:internal_transaction, transaction: transaction, index: 1)
result =
hash
|> Chain.transaction_hash_to_internal_transactions()
transaction
|> Chain.transaction_to_internal_transactions()
|> Enum.map(& &1.id)
assert [first_id, second_id] == result
assert [second_id, first_id] == result
end
end
@ -704,12 +661,7 @@ defmodule Explorer.ChainTest do
test "without logs" do
transaction = insert(:transaction)
assert %Scrivener.Page{
entries: [],
page_number: 1,
total_entries: 0,
total_pages: 1
} = Chain.transaction_to_logs(transaction)
assert [] = Chain.transaction_to_logs(transaction)
end
test "with logs" do
@ -720,12 +672,7 @@ defmodule Explorer.ChainTest do
%Log{id: id} = insert(:log, transaction: transaction)
assert %Scrivener.Page{
entries: [%Log{id: ^id}],
page_number: 1,
total_entries: 1,
total_pages: 1
} = Chain.transaction_to_logs(transaction)
assert [%Log{id: ^id}] = Chain.transaction_to_logs(transaction)
end
test "with logs can be paginated" do
@ -734,25 +681,17 @@ defmodule Explorer.ChainTest do
|> insert()
|> with_block()
logs = Enum.map(0..1, &insert(:log, index: &1, transaction: transaction))
[%Log{id: first_log_id}, %Log{id: second_log_id}] = logs
log = insert(:log, transaction: transaction, index: 1)
assert %Scrivener.Page{
entries: [%Log{id: ^first_log_id}],
page_number: 1,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.transaction_to_logs(transaction, pagination: %{page_size: 1})
second_page_indexes =
2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end)
|> Enum.map(& &1.index)
assert %Scrivener.Page{
entries: [%Log{id: ^second_log_id}],
page_number: 2,
page_size: 1,
total_entries: 2,
total_pages: 2
} = Chain.transaction_to_logs(transaction, pagination: %{page: 2, page_size: 1})
assert second_page_indexes ==
transaction
|> Chain.transaction_to_logs(paging_options: %PagingOptions{key: {log.index}, page_size: 50})
|> Enum.map(& &1.index)
end
test "with logs necessity_by_association loads associations" do
@ -763,17 +702,7 @@ defmodule Explorer.ChainTest do
insert(:log, transaction: transaction)
assert %Scrivener.Page{
entries: [
%Log{
address: %Address{},
transaction: %Transaction{}
}
],
page_number: 1,
total_entries: 1,
total_pages: 1
} =
assert [%Log{address: %Address{}, transaction: %Transaction{}}] =
Chain.transaction_to_logs(
transaction,
necessity_by_association: %{
@ -782,17 +711,12 @@ defmodule Explorer.ChainTest do
}
)
assert %Scrivener.Page{
entries: [
%Log{
address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
],
page_number: 1,
total_entries: 1,
total_pages: 1
} = Chain.transaction_to_logs(transaction)
assert [
%Log{
address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
] = Chain.transaction_to_logs(transaction)
end
end

@ -41,8 +41,7 @@ defmodule ExplorerWeb do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import ExplorerWeb.{CurrencyHelpers, Gettext, Router.Helpers, WeiHelpers, ErrorHelpers}
import Scrivener.HTML
import ExplorerWeb.{CurrencyHelpers, ErrorHelpers, Gettext, Router.Helpers, WeiHelpers}
end
end

@ -12,7 +12,11 @@ defmodule ExplorerWeb.Chain do
string_to_transaction_hash: 1
]
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction}
alias Explorer.PagingOptions
@page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1}
@spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found}
def from_param(param)
@ -32,6 +36,64 @@ defmodule ExplorerWeb.Chain do
end
end
def next_page_params([], _list), do: nil
def next_page_params(_, list), do: set_page_params(List.last(list))
def paging_options(%{
"block_number" => block_number_string,
"transaction_index" => transaction_index_string,
"index" => index_string
}) do
with {block_number, ""} <- Integer.parse(block_number_string),
{transaction_index, ""} <- Integer.parse(transaction_index_string),
{index, ""} <- Integer.parse(index_string) do
[paging_options: %{@default_paging_options | key: {block_number, transaction_index, index}}]
else
_ ->
[paging_options: @default_paging_options]
end
end
def paging_options(%{"block_number" => block_number_string, "index" => index_string}) do
with {block_number, ""} <- Integer.parse(block_number_string),
{index, ""} <- Integer.parse(index_string) do
[paging_options: %{@default_paging_options | key: {block_number, index}}]
else
_ ->
[paging_options: @default_paging_options]
end
end
def paging_options(%{"block_number" => block_number_string}) do
with {block_number, ""} <- Integer.parse(block_number_string) do
[paging_options: %{@default_paging_options | key: {block_number}}]
else
_ ->
[paging_options: @default_paging_options]
end
end
def paging_options(%{"index" => index_string}) do
with {index, ""} <- Integer.parse(index_string) do
[paging_options: %{@default_paging_options | key: {index}}]
else
_ ->
[paging_options: @default_paging_options]
end
end
def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string}) do
with {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string),
{:ok, hash} <- string_to_transaction_hash(hash_string) do
[paging_options: %{@default_paging_options | key: {inserted_at, hash}}]
else
_ ->
[paging_options: @default_paging_options]
end
end
def paging_options(_params), do: [paging_options: @default_paging_options]
def param_to_block_number(formatted_number) when is_binary(formatted_number) do
case Integer.parse(formatted_number) do
{number, ""} -> {:ok, number}
@ -39,6 +101,8 @@ defmodule ExplorerWeb.Chain do
end
end
def split_list_by_page(list_plus_one), do: Enum.split(list_plus_one, @page_size)
defp address_from_param(param) do
with {:ok, hash} <- string_to_address_hash(param) do
hash_to_address(hash)
@ -47,6 +111,27 @@ defmodule ExplorerWeb.Chain do
end
end
defp set_page_params(%Block{number: number}) do
%{block_number: number}
end
defp set_page_params(%InternalTransaction{index: index, transaction_hash: transaction_hash}) do
{:ok, %Transaction{block_number: block_number, index: transaction_index}} = hash_to_transaction(transaction_hash)
%{block_number: block_number, transaction_index: transaction_index, index: index}
end
defp set_page_params(%Log{index: index}) do
%{index: index}
end
defp set_page_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do
%{inserted_at: DateTime.to_iso8601(inserted_at), hash: hash}
end
defp set_page_params(%Transaction{block_number: block_number, index: index}) do
%{block_number: block_number, index: index}
end
defp transaction_from_param(param) do
with {:ok, hash} <- string_to_transaction_hash(param) do
hash_to_transaction(hash)

@ -6,6 +6,7 @@ defmodule ExplorerWeb.AddressInternalTransactionController do
use ExplorerWeb, :controller
import ExplorerWeb.AddressController, only: [transaction_count: 1]
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -13,27 +14,28 @@ defmodule ExplorerWeb.AddressInternalTransactionController do
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
options = [
necessity_by_association: %{
from_address: :optional,
to_address: :optional
},
pagination: params
]
page =
Chain.address_to_internal_transactions(
address,
Keyword.merge(options, current_filter(params))
)
full_options =
[
necessity_by_association: %{
from_address: :optional,
to_address: :optional
}
]
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
internal_transactions_plus_one = Chain.address_to_internal_transactions(address, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
render(
conn,
"index.html",
address: address,
next_page_params: next_page_params(next_page, internal_transactions),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
page: page,
internal_transactions: internal_transactions,
transaction_count: transaction_count(address)
)
else
@ -45,6 +47,16 @@ defmodule ExplorerWeb.AddressInternalTransactionController do
end
end
defp current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to, paging_options: paging_options]
"from" -> [direction: :from, paging_options: paging_options]
_ -> [paging_options: paging_options]
end
end
defp current_filter(params) do
params
|> Map.get("filter")

@ -6,6 +6,7 @@ defmodule ExplorerWeb.AddressTransactionController do
use ExplorerWeb, :controller
import ExplorerWeb.AddressController, only: [transaction_count: 1]
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -13,39 +14,50 @@ defmodule ExplorerWeb.AddressTransactionController do
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
options = [
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional
},
pagination: params
]
page =
Chain.address_to_transactions(
address,
Keyword.merge(options, current_filter(params))
)
full_options =
[
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional
}
]
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
transactions_plus_one = Chain.address_to_transactions(address, full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one)
render(
conn,
"index.html",
address: address,
next_page_params: next_page_params(next_page, transactions),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
page: page,
transactions: transactions,
transaction_count: transaction_count(address)
)
else
:error ->
not_found(conn)
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
defp current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to, paging_options: paging_options]
"from" -> [direction: :from, paging_options: paging_options]
_ -> [paging_options: paging_options]
end
end
defp current_filter(params) do
params
|> Map.get("filter")

@ -1,12 +1,26 @@
defmodule ExplorerWeb.BlockController do
use ExplorerWeb, :controller
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.Chain
def index(conn, params) do
blocks = Chain.list_blocks(necessity_by_association: %{transactions: :optional}, pagination: params)
full_options =
Keyword.merge(
[
necessity_by_association: %{
transactions: :optional
}
],
paging_options(params)
)
blocks_plus_one = Chain.list_blocks(full_options)
{blocks, next_page} = split_list_by_page(blocks_plus_one)
render(conn, "index.html", blocks: blocks)
render(conn, "index.html", blocks: blocks, next_page_params: next_page_params(next_page, blocks))
end
def show(conn, %{"id" => number, "locale" => locale}) do

@ -1,7 +1,8 @@
defmodule ExplorerWeb.BlockTransactionController do
use ExplorerWeb, :controller
import ExplorerWeb.Chain, only: [param_to_block_number: 1]
import ExplorerWeb.Chain,
only: [paging_options: 1, param_to_block_number: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.Chain
@ -9,18 +10,30 @@ defmodule ExplorerWeb.BlockTransactionController do
with {:ok, block_number} <- param_to_block_number(formatted_block_number),
{:ok, block} <- Chain.number_to_block(block_number, necessity_by_association: %{miner: :required}),
block_transaction_count <- Chain.block_to_transaction_count(block) do
page =
Chain.block_to_transactions(
block,
necessity_by_association: %{
block: :required,
from_address: :required,
to_address: :optional
},
pagination: params
full_options =
Keyword.merge(
[
necessity_by_association: %{
block: :required,
from_address: :required,
to_address: :optional
}
],
paging_options(params)
)
render(conn, "index.html", block: block, block_transaction_count: block_transaction_count, page: page)
transactions_plus_one = Chain.block_to_transactions(block, full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one)
render(
conn,
"index.html",
block: block,
block_transaction_count: block_transaction_count,
next_page_params: next_page_params(next_page, transactions),
transactions: transactions
)
else
{:error, :invalid} ->
not_found(conn)

@ -1,35 +1,34 @@
defmodule ExplorerWeb.PendingTransactionController do
use ExplorerWeb, :controller
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.Chain
def index(conn, params) do
with %{"last_seen_pending_inserted_at" => last_seen_pending_inserted_at_string} <- params,
{:ok, last_seen_pending_inserted_at} = Timex.parse(last_seen_pending_inserted_at_string, "{ISO:Extended:Z}") do
do_index(conn, inserted_after: last_seen_pending_inserted_at)
else
_ -> do_index(conn)
end
end
full_options =
Keyword.merge(
[
necessity_by_association: %{
from_address: :optional,
to_address: :optional
}
],
paging_options(params)
)
transactions_plus_one = Chain.recent_pending_transactions(full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one)
defp do_index(conn, options \\ []) when is_list(options) do
full_options = Keyword.merge([necessity_by_association: %{from_address: :optional, to_address: :optional}], options)
transactions = Chain.recent_pending_transactions(full_options)
last_seen_pending_inserted_at = last_seen_pending_inserted_at(transactions.entries)
pending_transaction_count = Chain.pending_transaction_count()
render(
conn,
"index.html",
last_seen_pending_inserted_at: last_seen_pending_inserted_at,
next_page_params: next_page_params(next_page, transactions),
pending_transaction_count: pending_transaction_count,
transactions: transactions
)
end
defp last_seen_pending_inserted_at([]), do: nil
defp last_seen_pending_inserted_at(transactions) do
List.last(transactions).inserted_at
end
end

@ -1,29 +1,11 @@
defmodule ExplorerWeb.TransactionController do
use ExplorerWeb, :controller
alias Explorer.{Chain, PagingOptions}
@default_paging_options %PagingOptions{page_size: 50}
def index(conn, %{"block_number" => block_number_string, "index" => index_string}) do
with {block_number, ""} <- Integer.parse(block_number_string),
{index, ""} <- Integer.parse(index_string) do
do_index(conn, paging_options: %{@default_paging_options | key: {block_number, index}})
else
_ ->
unprocessable_entity(conn)
end
end
def index(conn, _params) do
do_index(conn)
end
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
def show(conn, %{"id" => id, "locale" => locale}) do
redirect(conn, to: transaction_internal_transaction_path(conn, :index, locale, id))
end
alias Explorer.Chain
defp do_index(conn, options \\ []) when is_list(options) do
def index(conn, params) do
full_options =
Keyword.merge(
[
@ -31,28 +13,27 @@ defmodule ExplorerWeb.TransactionController do
block: :required,
from_address: :optional,
to_address: :optional
},
paging_options: @default_paging_options
}
],
options
paging_options(params)
)
transactions = Chain.recent_collated_transactions(full_options)
transactions_plus_one = Chain.recent_collated_transactions(full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one)
transaction_estimated_count = Chain.transaction_estimated_count()
render(
conn,
"index.html",
earliest: earliest(transactions),
next_page_params: next_page_params(next_page, transactions),
transaction_estimated_count: transaction_estimated_count,
transactions: transactions
)
end
defp earliest([]), do: nil
defp earliest(transactions) do
last = List.last(transactions)
%{block_number: last.block_number, index: last.index}
def show(conn, %{"id" => id, "locale" => locale}) do
redirect(conn, to: transaction_internal_transaction_path(conn, :index, locale, id))
end
end

@ -1,6 +1,8 @@
defmodule ExplorerWeb.TransactionInternalTransactionController do
use ExplorerWeb, :controller
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -15,24 +17,30 @@ defmodule ExplorerWeb.TransactionInternalTransactionController do
to_address: :optional
}
) do
page =
Chain.transaction_hash_to_internal_transactions(
transaction.hash,
necessity_by_association: %{
from_address: :required,
to_address: :optional
},
pagination: params
full_options =
Keyword.merge(
[
necessity_by_association: %{
from_address: :required,
to_address: :optional
}
],
paging_options(params)
)
internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
max_block_number = max_block_number()
render(
conn,
"index.html",
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
internal_transactions: internal_transactions,
max_block_number: max_block_number,
page: page,
next_page_params: next_page_params(next_page, internal_transactions),
transaction: transaction
)
else

@ -1,6 +1,8 @@
defmodule ExplorerWeb.TransactionLogController do
use ExplorerWeb, :controller
import ExplorerWeb.Chain, only: [paging_options: 1, next_page_params: 2, split_list_by_page: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -15,18 +17,26 @@ defmodule ExplorerWeb.TransactionLogController do
to_address: :required
}
) do
logs =
Chain.transaction_to_logs(
transaction,
necessity_by_association: %{address: :optional},
pagination: params
full_options =
Keyword.merge(
[
necessity_by_association: %{
address: :optional
}
],
paging_options(params)
)
logs_plus_one = Chain.transaction_to_logs(transaction, full_options)
{logs, next_page} = split_list_by_page(logs_plus_one)
render(
conn,
"index.html",
logs: logs,
max_block_number: max_block_number(),
next_page_params: next_page_params(next_page, logs),
transaction: transaction,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
)

@ -38,7 +38,7 @@
</div>
<div class="card-body">
<%= if Enum.count(@page) > 0 do %>
<%= if Enum.count(@internal_transactions) > 0 do %>
<div class="dropdown u-float-right u-push-sm">
<button data-test="filter_dropdown" class="button button--secondary button--xsmall dropdown-toggle" type="button"
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -87,7 +87,7 @@
<th><%= gettext "To" %></th>
<th><%= gettext "Value" %> (<%= gettext "Ether" %>)</th>
</thead>
<%= for internal_transaction <- @page do %>
<%= for internal_transaction <- @internal_transactions do %>
<tgroup>
<tr data-test="internal_transaction">
<td>
@ -115,22 +115,18 @@
<% end %>
</div>
</div>
<%= if Enum.count(@page) > 0 do %>
<div class="address__pagination">
<%= pagination_links(
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: address_internal_transaction_path(
@conn,
@page,
["en", @conn.params["address_id"]],
distance: 1,
filter: @conn.params["filter"],
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &address_internal_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
:index,
@conn.assigns.locale,
@address,
@next_page_params
)
) %>
<% end %>
</section>
</section>

@ -93,7 +93,7 @@
</tr>
</thead>
<tbody>
<%= for transaction <- @page do %>
<%= for transaction <- @transactions do %>
<tr>
<td><div class="transaction__dot transaction__dot--<%= status(transaction) %>"></div></td>
<td>
@ -134,20 +134,18 @@
</table>
</div>
</div>
<div class="address__pagination">
<%= pagination_links(
@conn,
@page,
["en", @conn.params["address_id"]],
distance: 1,
filter: @conn.params["filter"],
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &address_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: address_transaction_path(
@conn,
:index,
@conn.assigns.locale,
@address,
@next_page_params
)
) %>
<% end %>
</section>
</section>

@ -4,8 +4,8 @@
<p>
<%= gettext(
"Showing #%{start_block} to #%{end_block}",
start_block: List.first(@blocks.entries).number,
end_block: List.last(@blocks.entries).number
start_block: List.first(@blocks).number,
end_block: List.last(@blocks).number
) %>
</p>
</div>
@ -55,17 +55,16 @@
</table>
</div>
</div>
<div class="blocks__pagination">
<%= pagination_links(
@conn,
@blocks,
["en"],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &block_path/4,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: block_path(
@conn,
:index,
@conn.assigns.locale,
@next_page_params
)
) %>
<% end %>
</section>

@ -129,7 +129,7 @@
</ul>
</div>
<div class="card-body">
<%= if Enum.count(@page) > 0 do %>
<%= if Enum.count(@transactions) > 0 do %>
<table class="table table-responsive-sm table-font">
<thead>
<tr>
@ -146,7 +146,7 @@
</tr>
</thead>
<tbody>
<%= for transaction <- @page do %>
<%= for transaction <- @transactions do %>
<tr>
<td>
<div class="transaction__dot transaction__dot--<%= status(transaction) %>"></div>
@ -195,21 +195,17 @@
<% end %>
</div>
</div>
<%= if Enum.count(@page) > 0 do %>
<div class="blocks__pagination">
<%= pagination_links(
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: transaction_path(
@conn,
@page,
["en", @conn.params["block_id"]],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &block_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
:index,
@conn.assigns.locale,
@next_page_params
)
) %>
<% end %>
</section>
</section>

@ -64,19 +64,16 @@
</table>
</div>
</div>
<%= if @last_seen_pending_inserted_at do %>
<%= if @next_page_params do %>
<%= link(
gettext("Next Page"),
class: "button button--secondary button--sm u-float-right mt-3",
to: pending_transaction_path(
@conn,
:index,
@conn.assigns.locale,
%{
"last_seen_inserted_at" =>
Timex.format!(@last_seen_pending_inserted_at, "{ISO:Extended:Z}")
}
)
) %>
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: pending_transaction_path(
@conn,
:index,
@conn.assigns.locale,
@next_page_params
)
) %>
<% end %>
</section>

@ -85,7 +85,7 @@
</table>
</div>
</div>
<%= if @earliest do %>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
@ -93,7 +93,7 @@
@conn,
:index,
@conn.assigns.locale,
%{"block_number" => @earliest.block_number, "index" => @earliest.index}
@next_page_params
)
) %>
<% end %>

@ -24,7 +24,7 @@
</ul>
</div>
<div class="card-body">
<%= if Enum.count(@page) > 0 do %>
<%= if Enum.count(@internal_transactions) > 0 do %>
<table class="table table-responsive-sm table-font">
<thead>
<th><%= gettext "Type" %></th>
@ -33,10 +33,10 @@
<th><%= gettext "Value" %> (<%= gettext "Ether" %>)</th>
<th><%= gettext "Gas Limit" %> (<%= gettext "Gas" %>)</th>
</thead>
<%= for internal_transaction <- @page do %>
<%= for internal_transaction <- @internal_transactions do %>
<tgroup>
<tr>
<td><%= internal_transaction.call_type %></td>
<td><%= internal_transaction.type %></td>
<td>
<%= render ExplorerWeb.AddressView, "_link.html", conn: @conn, address: internal_transaction.from_address %>
</td>
@ -55,20 +55,18 @@
</div>
</div>
<%= if Enum.count(@page) > 0 do %>
<div class="address__pagination">
<%= pagination_links(
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: transaction_internal_transaction_path(
@conn,
@page,
["en", @conn.params["transaction_id"]],
distance: 1,
first: true,
next: Phoenix.HTML.raw("&rsaquo;"),
path: &transaction_internal_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</div>
:index,
@conn.assigns.locale,
@transaction,
@next_page_params
)
) %>
<% end %>
</section>
</section>

@ -68,5 +68,18 @@
<% end %>
</div>
</div>
<%= if @next_page_params do %>
<%= link(
gettext("Newer"),
class: "button button--secondary button--sm u-float-right mt-3",
to: transaction_log_path(
@conn,
:index,
@conn.assigns.locale,
@transaction,
@next_page_params
)
) %>
<% end %>
</section>
</section>

@ -48,7 +48,6 @@ defmodule ExplorerWeb.Mixfile do
defp extra_applications,
do: [
:scrivener_html,
:ex_cldr,
:timex,
:timex_ecto,
@ -88,7 +87,6 @@ defmodule ExplorerWeb.Mixfile do
{:phoenix_live_reload, "~> 1.0", only: [:dev]},
{:phoenix_pubsub, "~> 1.0"},
{:postgrex, ">= 0.0.0"},
{:scrivener_html, "~> 1.7"},
# Waiting on https://github.com/smeevil/set_locale/pull/9
{:set_locale, github: "minifast/set_locale", branch: "master"},
{:sobelow, ">= 0.7.0", only: [:dev, :test], runtime: false},

@ -504,7 +504,13 @@ msgstr ""
msgid "There are no Transactions"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:120
#: lib/explorer_web/templates/address_transaction/index.html.eex:139
#: lib/explorer_web/templates/block/index.html.eex:60
#: lib/explorer_web/templates/block_transaction/index.html.eex:200
#: lib/explorer_web/templates/pending_transaction/index.html.eex:69
#: lib/explorer_web/templates/transaction/index.html.eex:90
#: lib/explorer_web/templates/transaction_internal_transaction/index.html.eex:60
msgid "Older"
msgstr ""
@ -533,3 +539,8 @@ msgstr ""
#: lib/explorer_web/templates/address/overview.html.eex:48
msgid "Last Updated In Block"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/transaction_log/index.html.eex:73
msgid "Newer"
msgstr ""

@ -516,7 +516,13 @@ msgstr ""
msgid "There are no Transactions"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:120
#: lib/explorer_web/templates/address_transaction/index.html.eex:139
#: lib/explorer_web/templates/block/index.html.eex:60
#: lib/explorer_web/templates/block_transaction/index.html.eex:200
#: lib/explorer_web/templates/pending_transaction/index.html.eex:69
#: lib/explorer_web/templates/transaction/index.html.eex:90
#: lib/explorer_web/templates/transaction_internal_transaction/index.html.eex:60
msgid "Older"
msgstr ""
@ -545,3 +551,8 @@ msgstr "Yes"
#: lib/explorer_web/templates/address/overview.html.eex:48
msgid "Last Updated In Block"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/transaction_log/index.html.eex:73
msgid "Newer"
msgstr ""

@ -3,6 +3,7 @@ defmodule ExplorerWeb.AddressInternalTransactionControllerTest do
import ExplorerWeb.Router.Helpers, only: [address_internal_transaction_path: 4]
alias Explorer.Chain.{Block, InternalTransaction, Transaction}
alias Explorer.ExchangeRates.Token
describe "GET index/3" do
@ -38,7 +39,7 @@ defmodule ExplorerWeb.AddressInternalTransactionControllerTest do
conn = get(conn, path)
actual_transaction_ids =
conn.assigns.page
conn.assigns.internal_transactions
|> Enum.map(fn internal_transaction -> internal_transaction.id end)
assert Enum.member?(actual_transaction_ids, from_internal_transaction.id)
@ -52,5 +53,131 @@ defmodule ExplorerWeb.AddressInternalTransactionControllerTest do
assert %Token{} = conn.assigns.exchange_rate
end
test "returns next page of results based on last seen internal transaction", %{conn: conn} do
address = insert(:address)
a_block = insert(:block, number: 1000)
b_block = insert(:block, number: 2000)
transaction_1 =
:transaction
|> insert()
|> with_block(a_block)
transaction_2 =
:transaction
|> insert()
|> with_block(a_block)
transaction_3 =
:transaction
|> insert()
|> with_block(b_block)
transaction_1_hashes =
1..20
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction_1,
from_address: address,
index: index
)
end)
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
transaction_2_hashes =
1..20
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction_2,
from_address: address,
index: index
)
end)
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
transaction_3_hashes =
1..10
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction_3,
from_address: address,
index: index
)
end)
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
second_page_hashes = transaction_1_hashes ++ transaction_2_hashes ++ transaction_3_hashes
%InternalTransaction{index: index} =
:internal_transaction
|> insert(transaction: transaction_3, from_address: address, index: 11)
conn =
get(conn, address_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, address.hash), %{
"block_number" => Integer.to_string(b_block.number),
"transaction_index" => Integer.to_string(transaction_3.index),
"index" => Integer.to_string(index)
})
actual_hashes =
conn.assigns.internal_transactions
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
|> Enum.reverse()
assert second_page_hashes == actual_hashes
end
test "next_page_params exist if not on last page", %{conn: conn} do
address = insert(:address)
block = %Block{number: number} = insert(:block)
transaction =
%Transaction{index: transaction_index} =
:transaction
|> insert()
|> with_block(block)
1..60
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction,
from_address: address,
index: index
)
end)
conn = get(conn, address_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert %{block_number: ^number, index: 11, transaction_index: ^transaction_index} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
1..2
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction,
from_address: address,
index: index
)
end)
conn = get(conn, address_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
refute conn.assigns.next_page_params
end
end
end

@ -3,13 +3,14 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
import ExplorerWeb.Router.Helpers, only: [address_transaction_path: 4]
alias Explorer.Chain.{Block, Transaction}
alias Explorer.ExchangeRates.Token
describe "GET index/2" do
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_transaction_path(conn, :index, :en, "invalid_address"))
assert html_response(conn, 404)
assert html_response(conn, 422)
end
test "with valid address hash without address", %{conn: conn} do
@ -36,8 +37,8 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
conn = get(conn, address_transaction_path(conn, :index, :en, address))
actual_transaction_hashes =
conn.assigns.page
|> Enum.map(fn transaction -> transaction.hash end)
conn.assigns.transactions
|> Enum.map(& &1.hash)
assert html_response(conn, 200)
assert Enum.member?(actual_transaction_hashes, from_transaction.hash)
@ -53,9 +54,9 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
assert html_response(conn, 200)
assert conn.status == 200
assert Enum.empty?(conn.assigns.page)
assert Enum.empty?(conn.assigns.transactions)
assert conn.status == 200
assert Enum.empty?(conn.assigns.page)
assert Enum.empty?(conn.assigns.transactions)
end
test "includes USD exchange rate value for address in assigns", %{conn: conn} do
@ -65,5 +66,58 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
assert %Token{} = conn.assigns.exchange_rate
end
test "returns next page of results based on last seen transaction", %{conn: conn} do
address = insert(:address)
second_page_hashes =
50
|> insert_list(:transaction, from_address: address)
|> with_block()
|> Enum.map(& &1.hash)
%Transaction{block_number: block_number, index: index} =
:transaction
|> insert(from_address: address)
|> with_block()
conn =
get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address.hash), %{
"block_number" => Integer.to_string(block_number),
"index" => Integer.to_string(index)
})
actual_hashes =
conn.assigns.transactions
|> Enum.map(& &1.hash)
|> Enum.reverse()
assert second_page_hashes == actual_hashes
end
test "next_page_params exist if not on last page", %{conn: conn} do
address = insert(:address)
block = %Block{number: number} = insert(:block)
60
|> insert_list(:transaction, from_address: address)
|> with_block(block)
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
assert %{block_number: ^number, index: 10} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
address = insert(:address)
:transaction
|> insert(from_address: address)
|> with_block()
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
refute conn.assigns.next_page_params
end
end
end

@ -1,5 +1,6 @@
defmodule ExplorerWeb.BlockControllerTest do
use ExplorerWeb.ConnCase
alias Explorer.Chain.Block
@locale "en"
@ -33,7 +34,47 @@ defmodule ExplorerWeb.BlockControllerTest do
conn = get(conn, block_path(conn, :index, @locale))
assert conn.assigns.blocks.entries |> Enum.count() == 1
assert conn.assigns.blocks |> Enum.count() == 1
end
test "returns next page of results based on last seen block", %{conn: conn} do
second_page_block_ids =
50
|> insert_list(:block)
|> Enum.map(& &1.number)
block = insert(:block)
conn =
get(conn, block_path(conn, :index, @locale), %{
"block_number" => Integer.to_string(block.number)
})
actual_block_ids =
conn.assigns.blocks
|> Enum.map(& &1.number)
|> Enum.reverse()
assert second_page_block_ids == actual_block_ids
end
test "next_page_params exist if not on last page", %{conn: conn} do
%Block{number: number} =
60
|> insert_list(:block)
|> Enum.fetch!(10)
conn = get(conn, block_path(conn, :index, @locale))
assert %{block_number: ^number} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
insert(:block)
conn = get(conn, block_path(conn, :index, @locale))
refute conn.assigns.next_page_params
end
end
end

@ -1,6 +1,7 @@
defmodule ExplorerWeb.BlockTransactionControllerTest do
use ExplorerWeb.ConnCase
alias Explorer.Chain.Block
import ExplorerWeb.Router.Helpers, only: [block_transaction_path: 4]
describe "GET index/2" do
@ -20,12 +21,12 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
block = insert(:block)
:transaction |> insert() |> with_block(block)
:transaction |> insert(to_address_hash: nil) |> with_block(block)
:transaction |> insert(to_address: nil) |> with_block(block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
assert html_response(conn, 200)
assert 2 == Enum.count(conn.assigns.page)
assert 2 == Enum.count(conn.assigns.transactions)
end
test "does not return unrelated transactions", %{conn: conn} do
@ -35,7 +36,7 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block))
assert html_response(conn, 200)
assert Enum.empty?(conn.assigns.page)
assert Enum.empty?(conn.assigns.transactions)
end
test "does not return related transactions without a block", %{conn: conn} do
@ -45,7 +46,31 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block))
assert html_response(conn, 200)
assert Enum.empty?(conn.assigns.page)
assert Enum.empty?(conn.assigns.transactions)
end
test "next_page_params exist if not on last page", %{conn: conn} do
block = %Block{number: number} = insert(:block)
60
|> insert_list(:transaction)
|> with_block(block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block))
assert %{block_number: ^number, index: 10} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
block = insert(:block)
:transaction
|> insert()
|> with_block(block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block))
refute conn.assigns.next_page_params
end
end
end

@ -1,5 +1,6 @@
defmodule ExplorerWeb.PendingTransactionControllerTest do
use ExplorerWeb.ConnCase
alias Explorer.Chain.{Hash, Transaction}
import ExplorerWeb.Router.Helpers, only: [pending_transaction_path: 3]
@ -48,27 +49,53 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
assert 1 == conn.assigns.pending_transaction_count
end
test "paginates transactions using the last seen transaction", %{conn: conn} do
{:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
insert(:transaction, inserted_at: first_inserted_at)
{:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z")
insert(:transaction, inserted_at: second_inserted_at)
test "works when there are no transactions", %{conn: conn} do
conn = get(conn, pending_transaction_path(conn, :index, :en))
assert html_response(conn, 200)
end
test "returns next page of results based on last seen pending transaction", %{conn: conn} do
second_page_hashes =
50
|> insert_list(:transaction)
|> Enum.map(& &1.hash)
%Transaction{inserted_at: inserted_at, hash: hash} = insert(:transaction)
conn =
get(
conn,
pending_transaction_path(ExplorerWeb.Endpoint, :index, :en),
last_seen_pending_inserted_at: Timex.format!(first_inserted_at, "{ISO:Extended:Z}")
)
get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en), %{
"inserted_at" => DateTime.to_iso8601(inserted_at),
"hash" => Hash.to_string(hash)
})
assert html_response(conn, 200)
assert 1 == Enum.count(conn.assigns.transactions)
actual_hashes =
conn.assigns.transactions
|> Enum.map(& &1.hash)
|> Enum.reverse()
assert second_page_hashes == actual_hashes
end
test "works when there are no transactions", %{conn: conn} do
conn = get(conn, pending_transaction_path(conn, :index, :en))
test "next_page_params exist if not on last page", %{conn: conn} do
%Transaction{inserted_at: inserted_at, hash: hash} =
60
|> insert_list(:transaction)
|> Enum.fetch!(10)
assert html_response(conn, 200)
converted_date = DateTime.to_iso8601(inserted_at)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
assert %{inserted_at: ^converted_date, hash: ^hash} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
insert(:transaction)
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
refute conn.assigns.next_page_params
end
end
end

@ -1,6 +1,6 @@
defmodule ExplorerWeb.TransactionControllerTest do
use ExplorerWeb.ConnCase
alias Explorer.Chain.Transaction
alias Explorer.Chain.{Block, Transaction}
import ExplorerWeb.Router.Helpers, only: [transaction_path: 4, transaction_internal_transaction_path: 4]
@ -65,29 +65,34 @@ defmodule ExplorerWeb.TransactionControllerTest do
assert second_page_hashes == actual_hashes
end
test "guards against bad block_number input", %{conn: conn} do
conn = get(conn, "/en/transactions", %{"block_number" => "foo", "index" => "2"})
assert html_response(conn, 422)
end
test "next_page_params exist if not on last page", %{conn: conn} do
address = insert(:address)
block = %Block{number: number} = insert(:block)
60
|> insert_list(:transaction, from_address: address)
|> with_block(block)
conn = get(conn, "/en/transactions")
test "guards against bad index input", %{conn: conn} do
conn = get(conn, "/en/transactions", %{"block_number" => "2", "index" => "bar"})
assert html_response(conn, 422)
assert %{block_number: ^number, index: 10} = conn.assigns.next_page_params
end
test "sends back the number of transactions", %{conn: conn} do
insert(:transaction)
test "next_page_params are empty if on last page", %{conn: conn} do
address = insert(:address)
:transaction
|> insert(from_address: address)
|> with_block()
conn = get(conn, "/en/transactions")
refute conn.assigns.transaction_estimated_count == nil
refute conn.assigns.next_page_params
end
test "works when there are no transactions", %{conn: conn} do
conn = get(conn, "/en/transactions")
assert conn.assigns.transaction_estimated_count == 0
assert conn.assigns.transactions == []
end
end

@ -3,6 +3,7 @@ defmodule ExplorerWeb.TransactionInternalTransactionControllerTest do
import ExplorerWeb.Router.Helpers, only: [transaction_internal_transaction_path: 4]
alias Explorer.Chain.{Block, InternalTransaction, Transaction}
alias Explorer.ExchangeRates.Token
describe "GET index/3" do
@ -35,15 +36,15 @@ defmodule ExplorerWeb.TransactionInternalTransactionControllerTest do
test "includes internal transactions for the transaction", %{conn: conn} do
transaction = insert(:transaction)
expected_internal_transaction = insert(:internal_transaction, transaction_hash: transaction.hash, index: 0)
insert(:internal_transaction, transaction_hash: transaction.hash, index: 1)
expected_internal_transaction = insert(:internal_transaction, transaction: transaction, index: 0)
insert(:internal_transaction, transaction: transaction, index: 1)
path = transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash)
conn = get(conn, path)
actual_internal_transaction_ids =
conn.assigns.page
conn.assigns.internal_transactions
|> Enum.map(fn it -> it.id end)
assert html_response(conn, 200)
@ -80,5 +81,74 @@ defmodule ExplorerWeb.TransactionInternalTransactionControllerTest do
refute is_nil(conn.assigns.transaction.created_contract_address_hash)
end
test "returns next page of results based on last seen internal transaction", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
second_page_indexes =
1..50
|> Enum.map(fn index -> insert(:internal_transaction, transaction: transaction, index: index) end)
|> Enum.map(& &1.index)
%InternalTransaction{index: index} = insert(:internal_transaction, transaction: transaction, index: 51)
conn =
get(conn, transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash), %{
"index" => Integer.to_string(index)
})
actual_indexes =
conn.assigns.internal_transactions
|> Enum.map(& &1.index)
|> Enum.reverse()
assert second_page_indexes == actual_indexes
end
test "next_page_params exist if not on last page", %{conn: conn} do
block = %Block{number: number} = insert(:block)
transaction =
%Transaction{index: transaction_index} =
:transaction
|> insert()
|> with_block(block)
1..60
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction,
index: index
)
end)
conn = get(conn, transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash))
assert %{block_number: ^number, index: 11, transaction_index: ^transaction_index} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
1..2
|> Enum.map(fn index ->
insert(
:internal_transaction,
transaction: transaction,
index: index
)
end)
conn = get(conn, transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash))
refute conn.assigns.next_page_params
end
end
end

@ -33,7 +33,7 @@ defmodule ExplorerWeb.TransactionLogControllerTest do
conn = get(conn, transaction_log_path(conn, :index, :en, transaction))
first_log = List.first(conn.assigns.logs.entries)
first_log = List.first(conn.assigns.logs)
assert first_log.transaction_hash == transaction.hash
end
@ -44,7 +44,55 @@ defmodule ExplorerWeb.TransactionLogControllerTest do
conn = get(conn, path)
assert Enum.count(conn.assigns.logs.entries) == 0
assert Enum.count(conn.assigns.logs) == 0
end
test "returns next page of results based on last seen transaction log", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
log = insert(:log, transaction: transaction, index: 1)
second_page_indexes =
2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end)
|> Enum.map(& &1.index)
conn =
get(conn, transaction_log_path(conn, :index, :en, transaction), %{
"index" => Integer.to_string(log.index)
})
actual_indexes = Enum.map(conn.assigns.logs, & &1.index)
assert second_page_indexes == actual_indexes
end
test "next_page_params exist if not on last page", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
1..60
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end)
conn = get(conn, transaction_log_path(conn, :index, :en, transaction))
assert %{index: 50} = conn.assigns.next_page_params
end
test "next_page_params are empty if on last page", %{conn: conn} do
transaction =
:transaction
|> insert()
|> with_block()
conn = get(conn, transaction_log_path(conn, :index, :en, transaction))
refute conn.assigns.next_page_params
end
end

@ -48,10 +48,10 @@ defmodule ExplorerWeb.ViewingBlocksTest do
transaction =
:transaction
|> insert(to_address: nil, to_address_hash: nil)
|> insert(to_address: nil, to_address: nil)
|> with_block(block)
internal_transaction = insert(:internal_transaction_create, transaction_hash: transaction.hash, index: 0)
internal_transaction = insert(:internal_transaction_create, transaction: transaction, index: 0)
session
|> BlockPage.visit_page(block)

@ -63,11 +63,6 @@
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"qrcode": {:hex, :qrcode, "0.1.1", "9ca0d512a3c69a07d28e7660b046f8f3d835ef5462063814a7639a27302f7213", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"redix": {:hex, :redix, "0.6.1", "20986b0e02f02b13e6f53c79a1ae70aa83147488c408f40275ec261f5bb0a6d0", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"redix_pubsub": {:hex, :redix_pubsub, "0.4.1", "26e6a69129072ac2226be49139019bdf951bb1e9e210a773c1372acf88100936", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:redix, "~> 0.6.0", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm"},
"scrivener": {:hex, :scrivener, "2.5.0", "e1f78c62b6806d91cc9c4778deef1ea4e80aa9fadfce2c16831afe0468cc8a2c", [:mix], [], "hexpm"},
"scrivener_ecto": {:hex, :scrivener_ecto, "1.3.0", "69698428e22810ac8a47abc12d1df5b2f5d8f6b36dc5d5bfe6dd93fde857c576", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.11.0 or ~> 0.12.0 or ~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm"},
"scrivener_html": {:hex, :scrivener_html, "1.7.1", "afa35128fb36184bc469e4531bb1ef61b2d91bb29e373157068c62332291485f", [:mix], [{:phoenix, "~> 1.0-pre and < 1.4.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.1", [hex: :plug, repo: "hexpm", optional: false]}, {:scrivener, "~> 1.2 or ~> 2.0", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm"},
"set_locale": {:git, "https://github.com/minifast/set_locale.git", "da9ae029642bc0fbd9212c2aaf86c0adca70c084", [branch: "master"]},
"sobelow": {:hex, :sobelow, "0.7.0", "68ac7cb55040e8d33fb0e5df0008b4f612d2b97ea2bc99a013967a9a200ffc57", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
@ -75,6 +70,5 @@
"timex_ecto": {:hex, :timex_ecto, "3.2.1", "461140751026e1ca03298fab628f78ab189e78784175f5e301eefa034ee530aa", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"wallaby": {:hex, :wallaby, "0.20.0", "cc6663555ff7b05afbebb2a8b461d18a5b321658b9017f7bc77d494b7063266a", [:mix], [{:httpoison, "~> 0.12", [hex: :httpoison, optional: false]}, {:poison, ">= 1.4.0", [hex: :poison, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}]},
"wallaby": {:hex, :wallaby, "0.20.0", "cc6663555ff7b05afbebb2a8b461d18a5b321658b9017f7bc77d494b7063266a", [:mix], [{:httpoison, "~> 0.12", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 1.4.0", [hex: :poison, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm"},
}

Loading…
Cancel
Save