Exclude internal transactions with no siblings inside transaction

Add pagination


Co-authored-by: jkahne <jkahne@gmail.com>
Co-authored-by: faultyserver <jonegeland@gmail.com>
pull/156/head
Tim Mecklem 7 years ago
parent 82572acbe4
commit 7ca6138762
  1. 23
      apps/explorer/lib/explorer/chain.ex
  2. 115
      apps/explorer/test/explorer/chain_test.exs
  3. 174
      apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex
  4. 2
      apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex

@ -51,9 +51,8 @@ defmodule Explorer.Chain do
@doc """ @doc """
`t:Explorer.Chain.InternalTransaction/0`s from `address`. `t:Explorer.Chain.InternalTransaction/0`s from `address`.
This function excludes any "representative" internal transactions, where the internal transaction is a mirror of the This function excludes any internal transactions in the results where the internal transaction has no siblings within
parent transaction's value, to and from addresses. In the case of multiple internal transactions that have the same the parent transaction.
value, to, and from address, it excludes the internal transaction with the lowest id.
## Options ## Options
@ -70,31 +69,23 @@ defmodule Explorer.Chain do
def address_to_internal_transactions(%Address{id: id}, options \\ []) do def address_to_internal_transactions(%Address{id: id}, options \\ []) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
direction = Keyword.get(options, :direction) direction = Keyword.get(options, :direction)
pagination = Keyword.get(options, :pagination, %{})
InternalTransaction InternalTransaction
|> join(:inner, [internal_transaction], transaction in assoc(internal_transaction, :transaction)) |> join(:inner, [internal_transaction], transaction in assoc(internal_transaction, :transaction))
|> join(:left, [internal_transaction, transaction], block in assoc(transaction, :block)) |> join(:left, [internal_transaction, transaction], block in assoc(transaction, :block))
|> where_address_fields_match(direction, id) |> where_address_fields_match(direction, id)
|> where( |> where(
[it], [_it, t],
fragment( fragment(
""" "(SELECT COUNT(sibling.id) FROM internal_transactions as sibling WHERE sibling.transaction_id = ?) > 1",
? NOT IN ( t.id
SELECT min(it.id) FROM internal_transactions AS it
INNER JOIN transactions AS t ON t.id = it.transaction_id
WHERE it.value = t.value
AND it.from_address_id = t.from_address_id
AND it.to_address_id = t.to_address_id
GROUP BY t.id
)
""",
it.id
) )
) )
|> order_by([it, transaction, block], desc: block.number, desc: transaction.transaction_index, desc: it.index) |> order_by([it, transaction, block], desc: block.number, desc: transaction.transaction_index, desc: it.index)
|> preload(transaction: :block) |> preload(transaction: :block)
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)
|> Repo.all() |> Repo.paginate(pagination)
end end
@doc """ @doc """

@ -589,21 +589,26 @@ defmodule Explorer.ChainTest do
end end
describe "address_to_internal_transactions/1" do describe "address_to_internal_transactions/1" do
test "with single transaction containing an internal transaction" do test "with single transaction containing two internal transactions" do
address = insert(:address) address = insert(:address)
transaction = insert(:transaction) transaction = insert(:transaction)
%InternalTransaction{id: expected_id} = %InternalTransaction{id: first_id} =
insert(:internal_transaction, transaction_id: transaction.id, to_address_id: address.id)
%InternalTransaction{id: second_id} =
insert(:internal_transaction, transaction_id: transaction.id, to_address_id: address.id) insert(:internal_transaction, transaction_id: transaction.id, to_address_id: address.id)
result = address |> Chain.address_to_internal_transactions() |> List.first() result = address |> Chain.address_to_internal_transactions() |> Enum.map(fn it -> it.id end)
assert result.id == expected_id assert Enum.member?(result, first_id)
assert Enum.member?(result, second_id)
end end
test "loads associations in necessity_by_association" do test "loads associations in necessity_by_association" do
address = insert(:address) address = insert(:address)
transaction = insert(:transaction, to_address_id: address.id) transaction = insert(:transaction, to_address_id: address.id)
insert(:internal_transaction, transaction_id: transaction.id, to_address_id: address.id) insert(:internal_transaction, transaction_id: transaction.id, to_address_id: address.id, index: 0)
insert(:internal_transaction, transaction_id: transaction.id, to_address_id: address.id, index: 1)
assert [ assert [
%InternalTransaction{ %InternalTransaction{
@ -611,7 +616,8 @@ defmodule Explorer.ChainTest do
to_address: %Ecto.Association.NotLoaded{}, to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{} transaction: %Transaction{}
} }
] = Chain.address_to_internal_transactions(address) | _
] = Map.get(Chain.address_to_internal_transactions(address), :entries, [])
assert [ assert [
%InternalTransaction{ %InternalTransaction{
@ -619,14 +625,19 @@ defmodule Explorer.ChainTest do
to_address: %Address{}, to_address: %Address{},
transaction: %Transaction{} transaction: %Transaction{}
} }
| _
] = ] =
Chain.address_to_internal_transactions( Map.get(
address, Chain.address_to_internal_transactions(
necessity_by_association: %{ address,
from_address: :optional, necessity_by_association: %{
to_address: :optional, from_address: :optional,
transaction: :optional to_address: :optional,
} transaction: :optional
}
),
:entries,
[]
) )
end end
@ -635,71 +646,53 @@ defmodule Explorer.ChainTest do
pending_transaction = :transaction |> insert(transaction_index: "3") pending_transaction = :transaction |> insert(transaction_index: "3")
first_block = insert(:block, number: 2000) %InternalTransaction{id: first_pending} =
first_transaction = :transaction |> insert(transaction_index: "10") |> with_block(first_block) insert(:internal_transaction, transaction: pending_transaction, to_address_id: address.id, index: 0)
second_transaction = :transaction |> insert(transaction_index: "20") |> with_block(first_block)
second_block = insert(:block, number: 4000) %InternalTransaction{id: second_pending} =
third_transaction = :transaction |> insert(transaction_index: "5") |> with_block(second_block) insert(:internal_transaction, transaction: pending_transaction, to_address_id: address.id, index: 1)
%InternalTransaction{id: pending_id} = a_block = insert(:block, number: 2000)
insert(:internal_transaction, transaction: pending_transaction, to_address_id: address.id, index: 0) first_a_transaction = :transaction |> insert(transaction_index: "10") |> with_block(a_block)
%InternalTransaction{id: first_id} = %InternalTransaction{id: first} =
insert(:internal_transaction, transaction: first_transaction, to_address_id: address.id, index: 0) insert(:internal_transaction, transaction: first_a_transaction, to_address_id: address.id, index: 0)
%InternalTransaction{id: second_id} = %InternalTransaction{id: second} =
insert(:internal_transaction, transaction: second_transaction, to_address_id: address.id, index: 0) insert(:internal_transaction, transaction: first_a_transaction, to_address_id: address.id, index: 1)
second_a_transaction = :transaction |> insert(transaction_index: "20") |> with_block(a_block)
%InternalTransaction{id: third} =
insert(:internal_transaction, transaction: second_a_transaction, to_address_id: address.id, index: 0)
%InternalTransaction{id: third_id} = %InternalTransaction{id: fourth} =
insert(:internal_transaction, transaction: third_transaction, to_address_id: address.id, index: 0) insert(:internal_transaction, transaction: second_a_transaction, to_address_id: address.id, index: 1)
%InternalTransaction{id: fourth_id} = b_block = insert(:block, number: 6000)
insert(:internal_transaction, transaction: third_transaction, to_address_id: address.id, index: 1) first_b_transaction = :transaction |> insert(transaction_index: "20") |> with_block(b_block)
%InternalTransaction{id: fifth} =
insert(:internal_transaction, transaction: first_b_transaction, to_address_id: address.id, index: 0)
%InternalTransaction{id: sixth} =
insert(:internal_transaction, transaction: first_b_transaction, to_address_id: address.id, index: 1)
result = result =
address address
|> Chain.address_to_internal_transactions() |> Chain.address_to_internal_transactions()
|> Map.get(:entries, [])
|> Enum.map(fn internal_transaction -> internal_transaction.id end) |> Enum.map(fn internal_transaction -> internal_transaction.id end)
assert [pending_id, fourth_id, third_id, second_id, first_id] == result assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result
end end
test "Filters out internal transaction (with lowest id if multiple) that represents the parent transaction" do test "Excludes internal transactions where they are alone in the parent transaction" do
address = insert(:address) address = insert(:address)
transaction = :transaction |> insert(to_address_id: address.id) |> with_block() transaction = :transaction |> insert(to_address_id: address.id) |> with_block()
insert(:internal_transaction, transaction: transaction, to_address_id: address.id)
%InternalTransaction{id: first_id} = assert %{entries: []} = Chain.address_to_internal_transactions(address)
insert(:internal_transaction, transaction: transaction, to_address_id: address.id, index: 0)
%InternalTransaction{id: excluded_id} =
insert(
:internal_transaction,
transaction: transaction,
to_address_id: address.id,
index: 1,
value: transaction.value,
from_address_id: transaction.from_address_id
)
%InternalTransaction{id: third_id} =
insert(
:internal_transaction,
transaction: transaction,
to_address_id: address.id,
index: 2,
value: transaction.value,
from_address_id: transaction.from_address_id
)
result =
address
|> Chain.address_to_internal_transactions()
|> Enum.map(fn internal_transaction -> internal_transaction.id end)
assert Enum.member?(result, first_id)
refute Enum.member?(result, excluded_id)
assert Enum.member?(result, third_id)
end end
end end

@ -18,87 +18,101 @@
) %> ) %>
</h2> </h2>
</div> </div>
</div> <div class="dropdown u-float-right u-push-sm-right u-push-sm-bottom">
<div class="dropdown u-float-right u-push-sm-right u-push-sm-bottom"> <button data-test="filter_dropdown" class="button button--secondary button--xsmall dropdown-toggle" type="button"
<button data-test="filter_dropdown" class="button button--secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Filter: <%= format_current_filter(@filter) %>
Filter: <%= format_current_filter(@filter) %> </button>
</button> <div class="dropdown-menu dropdown-menu-right filter" aria-labelledby="dropdownMenu2">
<div class="dropdown-menu dropdown-menu-right filter" aria-labelledby="dropdownMenu2"> <%= link(
<%= link( gettext("All"),
gettext("All"), to: address_internal_transaction_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"]),
to: address_internal_transaction_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"]), class: "address__link address__link--active dropdown-item",
class: "address__link address__link--active dropdown-item", "data-test": "filter_option"
"data-test": "filter_option" ) %>
) %> <%= link(
<%= link( gettext("To"),
gettext("To"), to: address_internal_transaction_path(
to: address_internal_transaction_path( @conn,
@conn, :index,
:index, @conn.assigns.locale,
@conn.assigns.locale, @conn.params["address_id"],
@conn.params["address_id"], filter: "to"
filter: "to" ),
), class: "address__link address__link--active dropdown-item",
class: "address__link address__link--active dropdown-item", "data-test": "filter_option"
"data-test": "filter_option" ) %>
) %> <%= link(
<%= link( gettext("From"),
gettext("From"), to: address_internal_transaction_path(
to: address_internal_transaction_path( @conn,
@conn, :index,
:index, @conn.assigns.locale,
@conn.assigns.locale, @conn.params["address_id"],
@conn.params["address_id"], filter: "from"
filter: "from" ),
), class: "address__link address__link--active dropdown-item",
class: "address__link address__link--active dropdown-item", "data-test": "filter_option"
"data-test": "filter_option" ) %>
) %> </div>
</div>
<div class="internal-transaction__container">
<%= if length(@page.entries) > 0 do %>
<table class="internal-transaction__table">
<thead>
<th class="internal-transaction__column-header"><%= gettext "Parent Tx Hash" %></th>
<th class="internal-transaction__column-header"><%= gettext "Block" %></th>
<th class="internal-transaction__column-header"><%= gettext "Age" %></th>
<th class="internal-transaction__column-header"><%= gettext "From" %></th>
<th class="internal-transaction__column-header"><%= gettext "To" %></th>
<th class="internal-transaction__column-header"><%= gettext "Value" %></th>
</thead>
<%= for internal_transaction <- @page do %>
<tgroup>
<tr data-test="internal_transaction">
<td>
<%= link(internal_transaction.transaction.hash,
to: transaction_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.hash),
class: "transaction-log__link") %>
</td>
<td>
<%= link(internal_transaction.transaction.block.number,
to: block_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.block.number),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__to-address"><%= ExplorerWeb.BlockView.age(internal_transaction.transaction.block) %></td>
<td class="internal-transaction__from-address">
<%= link(internal_transaction.from_address.hash,
to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.from_address.hash),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__to-address">
<%= link(internal_transaction.to_address.hash,
to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.to_address.hash),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__to-address"><%= ExplorerWeb.TransactionView.value(internal_transaction) %></td>
</tr>
</tgroup>
<% end %>
</table>
<% else %>
<p><%= gettext "There are no Internal Transactions" %></p>
<% end %>
</div> </div>
</div> </div>
<div class="internal-transaction__container"> <div class="address__pagination">
<%= if length(@page) > 0 do %> <%= pagination_links(
<table class="internal-transaction__table"> @conn,
<thead> @page,
<th class="internal-transaction__column-header"><%= gettext "Parent Tx Hash" %></th> ["en", @conn.params["address_id"]],
<th class="internal-transaction__column-header"><%= gettext "Block" %></th> distance: 1,
<th class="internal-transaction__column-header"><%= gettext "Age" %></th> filter: @conn.params["filter"],
<th class="internal-transaction__column-header"><%= gettext "From" %></th> first: true,
<th class="internal-transaction__column-header"><%= gettext "To" %></th> next: Phoenix.HTML.raw("&rsaquo;"),
<th class="internal-transaction__column-header"><%= gettext "Value" %></th> path: &address_internal_transaction_path/5,
</thead> previous: Phoenix.HTML.raw("&lsaquo;"),
<%= for internal_transaction <- @page do %> view_style: :bulma
<tgroup> ) %>
<tr data-test="internal_transaction">
<td>
<%= link(internal_transaction.transaction.hash,
to: transaction_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.hash),
class: "transaction-log__link") %>
</td>
<td>
<%= link(internal_transaction.transaction.block.number,
to: block_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.block.number),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__to-address"><%= ExplorerWeb.BlockView.age(internal_transaction.transaction.block) %></td>
<td class="internal-transaction__from-address">
<%= link(internal_transaction.from_address.hash,
to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.from_address.hash),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__to-address">
<%= link(internal_transaction.to_address.hash,
to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.to_address.hash),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__to-address"><%= ExplorerWeb.TransactionView.value(internal_transaction) %></td>
</tr>
</tgroup>
<% end %>
</table>
<% else %>
<p><%= gettext "There are no Internal Transactions" %></p>
<% end %>
</div> </div>
</section> </section>

@ -20,7 +20,7 @@
</h2> </h2>
</div> </div>
<div class="dropdown u-float-right u-push-sm-right u-push-sm-bottom"> <div class="dropdown u-float-right u-push-sm-right u-push-sm-bottom">
<button data-test="filter_dropdown" class="button button--secondary dropdown-toggle" type="button" <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"> id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filter: <%= format_current_filter(@filter) %> Filter: <%= format_current_filter(@filter) %>
</button> </button>

Loading…
Cancel
Save