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. 101
      apps/explorer/test/explorer/chain_test.exs
  3. 20
      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,7 +625,9 @@ defmodule Explorer.ChainTest do
to_address: %Address{}, to_address: %Address{},
transaction: %Transaction{} transaction: %Transaction{}
} }
| _
] = ] =
Map.get(
Chain.address_to_internal_transactions( Chain.address_to_internal_transactions(
address, address,
necessity_by_association: %{ necessity_by_association: %{
@ -627,6 +635,9 @@ defmodule Explorer.ChainTest do
to_address: :optional, to_address: :optional,
transaction: :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)
second_transaction = :transaction |> insert(transaction_index: "20") |> with_block(first_block)
second_block = insert(:block, number: 4000)
third_transaction = :transaction |> insert(transaction_index: "5") |> with_block(second_block)
%InternalTransaction{id: pending_id} =
insert(:internal_transaction, transaction: pending_transaction, to_address_id: address.id, index: 0) insert(:internal_transaction, transaction: pending_transaction, to_address_id: address.id, index: 0)
%InternalTransaction{id: first_id} = %InternalTransaction{id: second_pending} =
insert(:internal_transaction, transaction: first_transaction, to_address_id: address.id, index: 0) insert(:internal_transaction, transaction: pending_transaction, to_address_id: address.id, index: 1)
%InternalTransaction{id: second_id} = a_block = insert(:block, number: 2000)
insert(:internal_transaction, transaction: second_transaction, to_address_id: address.id, index: 0) first_a_transaction = :transaction |> insert(transaction_index: "10") |> with_block(a_block)
%InternalTransaction{id: third_id} = %InternalTransaction{id: first} =
insert(:internal_transaction, transaction: third_transaction, to_address_id: address.id, index: 0) insert(:internal_transaction, transaction: first_a_transaction, to_address_id: address.id, index: 0)
%InternalTransaction{id: fourth_id} = %InternalTransaction{id: second} =
insert(:internal_transaction, transaction: third_transaction, to_address_id: address.id, index: 1) insert(:internal_transaction, transaction: first_a_transaction, to_address_id: address.id, index: 1)
result = second_a_transaction = :transaction |> insert(transaction_index: "20") |> with_block(a_block)
address
|> Chain.address_to_internal_transactions()
|> Enum.map(fn internal_transaction -> internal_transaction.id end)
assert [pending_id, fourth_id, third_id, second_id, first_id] == result %InternalTransaction{id: third} =
end insert(:internal_transaction, transaction: second_a_transaction, to_address_id: address.id, index: 0)
test "Filters out internal transaction (with lowest id if multiple) that represents the parent transaction" do %InternalTransaction{id: fourth} =
address = insert(:address) insert(:internal_transaction, transaction: second_a_transaction, to_address_id: address.id, index: 1)
transaction = :transaction |> insert(to_address_id: address.id) |> with_block()
%InternalTransaction{id: first_id} = b_block = insert(:block, number: 6000)
insert(:internal_transaction, transaction: transaction, to_address_id: address.id, index: 0) first_b_transaction = :transaction |> insert(transaction_index: "20") |> with_block(b_block)
%InternalTransaction{id: excluded_id} = %InternalTransaction{id: fifth} =
insert( insert(:internal_transaction, transaction: first_b_transaction, to_address_id: address.id, index: 0)
: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} = %InternalTransaction{id: sixth} =
insert( insert(:internal_transaction, transaction: first_b_transaction, to_address_id: address.id, index: 1)
:internal_transaction,
transaction: transaction,
to_address_id: address.id,
index: 2,
value: transaction.value,
from_address_id: transaction.from_address_id
)
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 Enum.member?(result, first_id) assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result
refute Enum.member?(result, excluded_id) end
assert Enum.member?(result, third_id)
test "Excludes internal transactions where they are alone in the parent transaction" do
address = insert(:address)
transaction = :transaction |> insert(to_address_id: address.id) |> with_block()
insert(:internal_transaction, transaction: transaction, to_address_id: address.id)
assert %{entries: []} = Chain.address_to_internal_transactions(address)
end end
end end

@ -18,9 +18,8 @@
) %> ) %>
</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 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>
@ -58,7 +57,7 @@
</div> </div>
</div> </div>
<div class="internal-transaction__container"> <div class="internal-transaction__container">
<%= if length(@page) > 0 do %> <%= if length(@page.entries) > 0 do %>
<table class="internal-transaction__table"> <table class="internal-transaction__table">
<thead> <thead>
<th class="internal-transaction__column-header"><%= gettext "Parent Tx Hash" %></th> <th class="internal-transaction__column-header"><%= gettext "Parent Tx Hash" %></th>
@ -101,4 +100,19 @@
<p><%= gettext "There are no Internal Transactions" %></p> <p><%= gettext "There are no Internal Transactions" %></p>
<% end %> <% end %>
</div> </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_internal_transaction_path/5,
previous: Phoenix.HTML.raw("&lsaquo;"),
view_style: :bulma
) %>
</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