diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index d57a03ac69..5118f60950 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -91,13 +91,17 @@ 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, %PagingOptions{page_size: 50}) InternalTransaction |> join( @@ -108,6 +112,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, @@ -116,7 +122,7 @@ defmodule Explorer.Chain do ) |> preload(transaction: :block) |> join_associations(necessity_by_association) - |> Repo.paginate(pagination) + |> Repo.all() end @doc """ @@ -2525,6 +2531,19 @@ defmodule Explorer.Chain do ) end + defp page_internal_transaction(query, %PagingOptions{key: nil}), do: query + + defp page_internal_transaction(query, %PagingOptions{key: {block_number, transaction_index, index}}) do + query + |> where( + [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_transaction(query, %PagingOptions{key: nil}), do: query defp page_transaction(query, %PagingOptions{key: {block_number, index}}) do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index d19e8a41fb..43b9710b0b 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -405,7 +405,7 @@ defmodule Explorer.ChainTest do transaction: %Transaction{} } | _ - ] = Map.get(Chain.address_to_internal_transactions(address), :entries, []) + ] = Chain.address_to_internal_transactions(address) assert [ %InternalTransaction{ @@ -415,17 +415,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 @@ -520,7 +516,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 diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_internal_transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_internal_transaction_controller.ex index bf87e4d486..29de532635 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/address_internal_transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/address_internal_transaction_controller.ex @@ -7,33 +7,49 @@ defmodule ExplorerWeb.AddressInternalTransactionController do import ExplorerWeb.AddressController, only: [transaction_count: 1] - alias Explorer.{Chain, Market} + alias Explorer.{Chain, Market, PagingOptions} alias Explorer.ExchangeRates.Token - def index(conn, %{"address_id" => address_hash_string} = params) do + @default_paging_options %PagingOptions{page_size: 50} + + def index(conn, %{"block_number" => block_number_string, "transaction_index" => transaction_index_string, "index" => index_string} = params) do + with {block_number, ""} <- Integer.parse(block_number_string), + {transaction_index, ""} <- Integer.parse(transaction_index_string), + {index, ""} <- Integer.parse(index_string) do + do_index(conn, Map.put(params, :paging_options, %{@default_paging_options | key: {block_number, transaction_index, index}})) + else + _ -> + unprocessable_entity(conn) + end + end + + def index(conn, params), do: do_index(conn, params) + + def do_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 = + Keyword.merge( + [ + necessity_by_association: %{ + from_address: :optional, + to_address: :optional + }, + paging_options: @default_paging_options + ], + current_filter(params) ) + internal_transactions = Chain.address_to_internal_transactions(address, full_options) + render( conn, "index.html", address: address, + earliest: earliest(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 +61,24 @@ defmodule ExplorerWeb.AddressInternalTransactionController do end end + defp earliest([]), do: nil + + defp earliest(internal_transactions) do + last = List.last(internal_transactions) + {:ok, last_transaction} = Chain.hash_to_transaction(last.transaction_hash) + %{block_number: last_transaction.block_number, transaction_index: last_transaction.index, index: last.index} + 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") diff --git a/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex index baf46761b7..242773d0e3 100644 --- a/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex @@ -38,7 +38,7 @@