Merge branch 'master' into ams-top-accounts

pull/1140/head
Andrew Cravenho 6 years ago committed by GitHub
commit 53177eea9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      apps/block_scout_web/lib/block_scout_web/chain.ex
  2. 39
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  3. 46
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  4. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  5. 9
      apps/block_scout_web/priv/gettext/default.pot
  6. 9
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  7. 149
      apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs
  8. 18
      apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs
  9. 85
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  10. 30
      apps/explorer/lib/explorer/chain/token_transfer.ex
  11. 18
      apps/explorer/priv/repo/migrations/20181121170616_add_block_number_to_token_transfers.exs
  12. 39
      apps/explorer/priv/repo/migrations/scripts/20181121170616_token_transfers_update_block_number_in_batches.sql
  13. 4
      apps/explorer/test/explorer/chain/import_test.exs
  14. 43
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  15. 10
      apps/explorer/test/explorer/chain_test.exs
  16. 1
      apps/explorer/test/support/factory.ex

@ -130,9 +130,6 @@ defmodule BlockScoutWeb.Chain do
end
end
def paging_options(%{"inserted_at" => inserted_at}),
do: [paging_options: %{@default_paging_options | key: inserted_at}]
def paging_options(%{"token_name" => name, "token_type" => type, "token_inserted_at" => inserted_at}),
do: [paging_options: %{@default_paging_options | key: {name, type, inserted_at}}]
@ -180,13 +177,8 @@ defmodule BlockScoutWeb.Chain do
%{"block_number" => block_number, "index" => index}
end
defp paging_params(%TokenTransfer{inserted_at: inserted_at}) do
inserted_at_datetime =
inserted_at
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_iso8601()
%{"inserted_at" => inserted_at_datetime}
defp paging_params(%TokenTransfer{block_number: block_number, log_index: index}) do
%{"block_number" => block_number, "index" => index}
end
defp paging_params(%Address.Token{name: name, type: type, inserted_at: inserted_at}) do

@ -8,10 +8,12 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.InternalTransactionView
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Phoenix.View
def index(conn, %{"address_id" => address_hash_string} = params) do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
full_options =
@ -28,14 +30,45 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
internal_transactions_plus_one = Chain.address_to_internal_transactions(address, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
next_page_path =
case next_page_params(next_page, internal_transactions, params) do
nil ->
nil
next_page_params ->
address_internal_transaction_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end
internal_transactions_json =
Enum.map(internal_transactions, fn internal_transaction ->
View.render_to_string(
InternalTransactionView,
"_tile.html",
current_address: address,
internal_transaction: internal_transaction
)
end)
json(conn, %{items: internal_transactions_json, next_page_path: next_page_path})
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
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
render(
conn,
"index.html",
address: address,
next_page_params: next_page_params(next_page, internal_transactions, params),
current_path: current_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
internal_transactions: internal_transactions,
transaction_count: transaction_count(address),
validation_count: validation_count(address)
)

@ -7,7 +7,7 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
</div>
<div class="card-body">
<div class="card-body" data-async-listing="<%= @current_path %>">
<div data-selector="channel-batching-message" style="display: none;">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More internal transactions have come in" %></a>
@ -54,31 +54,33 @@
) %>
</div>
</div>
<h2 class="card-title"><%= gettext "Internal Transactions" %></h2>
<%= if Enum.count(@internal_transactions) > 0 do %>
<span data-selector="internal-transactions-list">
<%= for internal_transaction <- @internal_transactions do %>
<%= render BlockScoutWeb.InternalTransactionView, "_tile.html", current_address: @address, internal_transaction: internal_transaction %>
<% end %>
</span>
<% else %>
<button data-error-message class="alert alert-danger col-12 text-left" style="display: none;">
<span href="#" class="alert-link"><%= gettext("Something went wrong, click to reload.") %></span>
</button>
<div data-empty-response-message style="display: none;">
<div class="tile tile-muted text-center">
<span data-selector="empty-internal-transactions-list"><%= gettext "There are no internal transactions for this address." %></span>
</div>
<% end %>
<div>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button-secondary button-sm float-right mt-3",
to: address_internal_transaction_path(
@conn,
:index,
@address,
@next_page_params
)
) %>
<% end %>
</div>
<div data-loading-message class="tile tile-muted text-center mt-3">
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading") %>...
</div>
<div data-items></div>
<a href="#" class="button button-secondary button-small float-right mt-4" data-next-page-button style="display: none;">
<%= gettext("Older") %>
</a>
<div class="button button-secondary button-small float-right mt-4" data-loading-button style="display: none;">
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading") %>...
</div>
</div>
</div>

@ -43,7 +43,7 @@
<%= link(
gettext(
"Block #%{number}",
number: @transfer.transaction.block_number
number: @transfer.block_number
),
class: "mr-2 mr-sm-0 text-muted",
to: block_path(BlockScoutWeb.Endpoint, :show, @transfer.transaction.block_number)

@ -505,7 +505,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:21
#: lib/block_scout_web/templates/address/_tabs.html.eex:81
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:57
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:58
#: lib/block_scout_web/templates/address_validation/index.html.eex:24
#: lib/block_scout_web/templates/address_validation/index.html.eex:75
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
@ -638,7 +638,7 @@ msgid "OUT"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76
#: lib/block_scout_web/templates/address_validation/index.html.eex:117
#: lib/block_scout_web/templates/block/index.html.eex:30
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -815,7 +815,7 @@ msgid "There are no holders for this Token."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:66
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:64
msgid "There are no internal transactions for this address."
msgstr ""
@ -1214,6 +1214,8 @@ msgid "GraphQL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
#: lib/block_scout_web/templates/address_transaction/index.html.eex:61
#: lib/block_scout_web/templates/block/index.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:33
@ -1409,6 +1411,7 @@ msgid "Log Data"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
msgid "Something went wrong, click to reload."
msgstr ""

@ -505,7 +505,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:21
#: lib/block_scout_web/templates/address/_tabs.html.eex:81
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:57
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:58
#: lib/block_scout_web/templates/address_validation/index.html.eex:24
#: lib/block_scout_web/templates/address_validation/index.html.eex:75
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
@ -638,7 +638,7 @@ msgid "OUT"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76
#: lib/block_scout_web/templates/address_validation/index.html.eex:117
#: lib/block_scout_web/templates/block/index.html.eex:30
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -815,7 +815,7 @@ msgid "There are no holders for this Token."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:66
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:64
msgid "There are no internal transactions for this address."
msgstr ""
@ -1214,6 +1214,8 @@ msgid "GraphQL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
#: lib/block_scout_web/templates/address_transaction/index.html.eex:61
#: lib/block_scout_web/templates/block/index.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:33
@ -1409,6 +1411,7 @@ msgid "Log Data"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
msgid "Something went wrong, click to reload."
msgstr ""

@ -1,7 +1,8 @@
defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
use BlockScoutWeb.ConnCase
import BlockScoutWeb.Router.Helpers, only: [address_internal_transaction_path: 3]
import BlockScoutWeb.Router.Helpers,
only: [address_internal_transaction_path: 3, address_internal_transaction_path: 4]
alias Explorer.Chain.{Block, InternalTransaction, Transaction}
alias Explorer.ExchangeRates.Token
@ -21,6 +22,14 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
assert html_response(conn, 404)
end
test "includes USD exchange rate value for address in assigns", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash))
assert %Token{} = conn.assigns.exchange_rate
end
test "returns internal transactions for the address", %{conn: conn} do
address = insert(:address)
@ -47,29 +56,101 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
transaction_index: transaction.index
)
path = address_internal_transaction_path(conn, :index, address)
path = address_internal_transaction_path(conn, :index, address, %{"type" => "JSON"})
conn = get(conn, path)
actual_internal_transaction_primary_keys =
Enum.map(conn.assigns.internal_transactions, &{&1.transaction_hash, &1.index})
internal_transaction_tiles = json_response(conn, 200)["items"]
assert Enum.all?([from_internal_transaction, to_internal_transaction], fn internal_transaction ->
Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"")
end)
end)
end
test "returns internal transactions coming from the address", %{conn: conn} do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
from_internal_transaction =
insert(:internal_transaction,
transaction: transaction,
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
to_internal_transaction =
insert(:internal_transaction,
transaction: transaction,
to_address: address,
index: 2,
block_number: transaction.block_number,
transaction_index: transaction.index
)
path = address_internal_transaction_path(conn, :index, address, %{"filter" => "from", "type" => "JSON"})
conn = get(conn, path)
assert Enum.member?(
actual_internal_transaction_primary_keys,
{from_internal_transaction.transaction_hash, from_internal_transaction.index}
)
internal_transaction_tiles = json_response(conn, 200)["items"]
assert Enum.member?(
actual_internal_transaction_primary_keys,
{to_internal_transaction.transaction_hash, to_internal_transaction.index}
)
assert Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"")
end)
refute Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"")
end)
end
test "includes USD exchange rate value for address in assigns", %{conn: conn} do
test "returns internal transactions going to the address", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash))
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
assert %Token{} = conn.assigns.exchange_rate
from_internal_transaction =
insert(:internal_transaction,
transaction: transaction,
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
to_internal_transaction =
insert(:internal_transaction,
transaction: transaction,
to_address: address,
index: 2,
block_number: transaction.block_number,
transaction_index: transaction.index
)
path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"})
conn = get(conn, path)
internal_transaction_tiles = json_response(conn, 200)["items"]
assert Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"")
end)
refute Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"")
end)
end
test "returns next page of results based on last seen internal transaction", %{conn: conn} do
@ -105,7 +186,6 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
transaction_index: transaction_1.index
)
end)
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
transaction_2_hashes =
1..20
@ -119,7 +199,6 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
transaction_index: transaction_2.index
)
end)
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
transaction_3_hashes =
1..10
@ -133,9 +212,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
transaction_index: transaction_3.index
)
end)
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
second_page_hashes = transaction_1_hashes ++ transaction_2_hashes ++ transaction_3_hashes
second_page = transaction_1_hashes ++ transaction_2_hashes ++ transaction_3_hashes
%InternalTransaction{index: index} =
insert(
@ -151,15 +229,18 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash), %{
"block_number" => Integer.to_string(b_block.number),
"transaction_index" => Integer.to_string(transaction_3.index),
"index" => Integer.to_string(index)
"index" => Integer.to_string(index),
"type" => "JSON"
})
actual_hashes =
conn.assigns.internal_transactions
|> Enum.map(&"#{&1.transaction_hash}.#{&1.index}")
|> Enum.reverse()
internal_transaction_tiles = json_response(conn, 200)["items"]
assert second_page_hashes == actual_hashes
assert Enum.all?(second_page, fn internal_transaction ->
Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"")
end)
end)
end
test "next_page_params exist if not on last page", %{conn: conn} do
@ -184,10 +265,17 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
)
end)
conn = get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash))
conn =
get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash, %{"type" => "JSON"}))
assert %{"block_number" => ^number, "index" => 11, "transaction_index" => ^transaction_index} =
conn.assigns.next_page_params
expected_response =
address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash, %{
"block_number" => number,
"index" => 11,
"transaction_index" => transaction_index
})
assert expected_response == json_response(conn, 200)["next_page_path"]
end
test "next_page_params are empty if on last page", %{conn: conn} do
@ -208,9 +296,10 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
)
end)
conn = get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash))
conn =
get(conn, address_internal_transaction_path(BlockScoutWeb.Endpoint, :index, address.hash, %{"type" => "JSON"}))
refute conn.assigns.next_page_params
assert %{"next_page_path" => nil} = json_response(conn, 200)
end
end
end

@ -82,25 +82,21 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
|> insert()
|> with_block()
{:ok, first_transfer_time} = NaiveDateTime.new(2000, 1, 1, 0, 0, 5)
{:ok, remaining_transfers_time} = NaiveDateTime.new(1999, 1, 1, 0, 0, 0)
insert(:token_transfer, transaction: transaction, inserted_at: first_transfer_time)
token_transfer = insert(:token_transfer, transaction: transaction, block_number: 1000, log_index: 1)
1..5
|> Enum.each(fn log_index ->
insert(:token_transfer, transaction: transaction, inserted_at: remaining_transfers_time, log_index: log_index)
Enum.each(2..5, fn item ->
insert(:token_transfer, transaction: transaction, block_number: item + 1001, log_index: item + 1)
end)
conn =
get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash), %{
"inserted_at" => first_transfer_time |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601()
"block_number" => "1000",
"index" => "1"
})
actual_times =
conn.assigns.token_transfers
|> Enum.map(& &1.inserted_at)
actual_log_indexes = Enum.map(conn.assigns.token_transfers, & &1.log_index)
refute Enum.any?(actual_times, fn time -> first_transfer_time == time end)
refute Enum.any?(actual_log_indexes, fn log_index -> log_index == token_transfer.log_index end)
end
test "next_page_params exist if not on last page", %{conn: conn} do

@ -147,35 +147,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> assert_has(AddressPage.transaction(transactions.from_taft))
end
test "contract creation is shown for to_address on list page", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
contract_address = insert(:contract_address)
from_lincoln =
:transaction
|> insert(from_address: lincoln, to_address: nil)
|> with_contract_creation(contract_address)
|> with_block(block)
internal_transaction =
:internal_transaction_create
|> insert(
transaction: from_lincoln,
from_address: lincoln,
index: 1
)
|> with_contract_creation(contract_address)
session
|> AddressPage.visit_page(addresses.lincoln)
|> assert_has(AddressPage.contract_creation(internal_transaction))
end
test "only addresses not matching the page are links", %{
addresses: addresses,
session: session,
@ -213,29 +184,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
{:ok, %{internal_transaction_lincoln_to_address: internal_transaction_lincoln_to_address}}
end
test "can see internal transactions for an address", %{addresses: addresses, session: session} do
session
|> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions()
|> assert_has(AddressPage.internal_transactions(count: 2))
end
test "can filter to only see internal transactions from an address", %{addresses: addresses, session: session} do
session
|> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions()
|> AddressPage.apply_filter("From")
|> assert_has(AddressPage.internal_transactions(count: 1))
end
test "can filter to only see internal transactions to an address", %{addresses: addresses, session: session} do
session
|> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions()
|> AddressPage.apply_filter("To")
|> assert_has(AddressPage.internal_transactions(count: 1))
end
@tag :skip
test "only addresses not matching the page are links", %{
addresses: addresses,
internal_transaction_lincoln_to_address: internal_transaction,
@ -248,6 +197,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> refute_has(AddressPage.internal_transaction_address_link(internal_transaction, :to))
end
@tag :skip
test "viewing new internal transactions via live update", %{addresses: addresses, session: session} do
transaction =
:transaction
@ -276,37 +226,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
end
end
test "contract creation is shown for to_address on list page", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
contract_address = insert(:contract_address)
from_lincoln =
:transaction
|> insert(from_address: lincoln, to_address: nil)
|> with_block(block)
|> with_contract_creation(contract_address)
internal_transaction =
:internal_transaction_create
|> insert(
transaction: from_lincoln,
from_address: lincoln,
index: 1,
block_number: from_lincoln.block_number,
transaction_index: from_lincoln.index
)
|> with_contract_creation(contract_address)
session
|> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions()
|> assert_has(AddressPage.contract_creation(internal_transaction))
end
describe "viewing token transfers" do
test "contributor can see all token transfers that he sent", %{
addresses: addresses,

@ -24,15 +24,17 @@ defmodule Explorer.Chain.TokenTransfer do
use Ecto.Schema
import Ecto.{Changeset, Query}
import Ecto.Changeset
import Ecto.Query, only: [from: 2, dynamic: 2, limit: 2, where: 3]
alias Explorer.Chain.{Address, Block, Hash, Token, TokenTransfer, Transaction}
alias Explorer.Chain.{Address, Hash, Token, TokenTransfer, Transaction}
alias Explorer.{PagingOptions, Repo}
@default_paging_options %PagingOptions{page_size: 50}
@typedoc """
* `:amount` - The token transferred amount
* `:block_number` - The block number that the transfer took place.
* `:from_address` - The `t:Explorer.Chain.Address.t/0` that sent the tokens
* `:from_address_hash` - Address hash foreign key
* `:to_address` - The `t:Explorer.Chain.Address.t/0` that received the tokens
@ -46,6 +48,7 @@ defmodule Explorer.Chain.TokenTransfer do
"""
@type t :: %TokenTransfer{
amount: Decimal.t(),
block_number: non_neg_integer() | nil,
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_hash: Hash.Address.t(),
to_address: %Ecto.Association.NotLoaded{} | Address.t(),
@ -65,6 +68,7 @@ defmodule Explorer.Chain.TokenTransfer do
@primary_key false
schema "token_transfers" do
field(:amount, :decimal)
field(:block_number, :integer)
field(:log_index, :integer, primary_key: true)
field(:token_id, :decimal)
@ -91,7 +95,7 @@ defmodule Explorer.Chain.TokenTransfer do
timestamps()
end
@required_attrs ~w(log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash)a
@required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash)a
@optional_attrs ~w(amount token_id)a
@doc false
@ -118,13 +122,9 @@ defmodule Explorer.Chain.TokenTransfer do
query =
from(
tt in TokenTransfer,
join: t in Transaction,
on: tt.transaction_hash == t.hash,
join: b in Block,
on: t.block_hash == b.hash,
where: tt.token_contract_address_hash == ^token_address_hash,
preload: [{:transaction, :block}, :token, :from_address, :to_address],
order_by: [desc: b.timestamp]
order_by: [desc: tt.block_number, desc: tt.log_index]
)
query
@ -135,19 +135,11 @@ defmodule Explorer.Chain.TokenTransfer do
def page_token_transfer(query, %PagingOptions{key: nil}), do: query
def page_token_transfer(query, %PagingOptions{key: {token_id}}) do
def page_token_transfer(query, %PagingOptions{key: {block_number, log_index}}) do
where(
query,
[token_transfer],
token_transfer.token_id > ^token_id
)
end
def page_token_transfer(query, %PagingOptions{key: inserted_at}) do
where(
query,
[token_transfer],
token_transfer.inserted_at < ^inserted_at
[tt],
tt.block_number < ^block_number or (tt.block_number == ^block_number and tt.log_index < ^log_index)
)
end

@ -0,0 +1,18 @@
defmodule Explorer.Repo.Migrations.AddBlockNumberToTokenTransfers do
@moduledoc """
Use `priv/repo/migrations/scripts/20181121170616_token_transfers_update_block_number_in_batches.sql` to migrate data.
```sh
mix ecto.migrate
psql -d $DATABASE -a -f priv/repo/migrations/scripts/20181121170616_token_transfers_update_block_number_in_batches.sql
```
"""
use Ecto.Migration
def change do
alter table(:token_transfers) do
add(:block_number, :integer)
end
end
end

@ -0,0 +1,39 @@
DO $$
DECLARE
row_count integer;
batch_size integer := 100000; -- HOW MANY ITEMS WILL BE UPDATED AT TIME
affected integer;
BEGIN
RAISE NOTICE 'Counting items to be updated';
row_count := (SELECT COUNT(*) FROM token_transfers WHERE block_number IS NULL);
RAISE NOTICE '% items', row_count;
WHILE row_count > 0 LOOP
WITH cte AS (
SELECT
t.hash,
t.block_number
FROM token_transfers AS tt
INNER JOIN transactions AS t ON t.hash = tt.transaction_hash
WHERE tt.block_number IS NULL
LIMIT batch_size
)
UPDATE token_transfers
SET
block_number = cte.block_number
FROM cte
WHERE token_transfers.transaction_hash = cte.hash;
GET DIAGNOSTICS affected = ROW_COUNT;
RAISE NOTICE '-> % token transfers updated!', affected;
-- UPDATES THE COUNTER SO IT DOESN'T TURN INTO AN INFINITE LOOP
row_count := row_count - batch_size;
RAISE NOTICE '-> % items missing to update', row_count;
CHECKPOINT; -- COMMITS THE BATCH UPDATES
END LOOP;
END $$;

@ -1517,7 +1517,9 @@ defmodule Explorer.Chain.ImportTest do
},
token_transfers: %{
params: [
params_for(:token_transfer,
params_for(
:token_transfer,
block_number: 35,
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
token_contract_address_hash: token_contract_address_hash,

@ -8,7 +8,7 @@ defmodule Explorer.Chain.TokenTransferTest do
doctest Explorer.Chain.TokenTransfer
describe "fetch_token_transfers/2" do
describe "fetch_token_transfers_from_token_hash/2" do
test "returns token transfers for the given address" do
token_contract_address = insert(:contract_address)
@ -87,6 +87,7 @@ defmodule Explorer.Chain.TokenTransferTest do
second_page =
insert(
:token_transfer,
block_number: 999,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
@ -96,23 +97,53 @@ defmodule Explorer.Chain.TokenTransferTest do
first_page =
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
paging_options = %PagingOptions{key: first_page.inserted_at, page_size: 1}
paging_options = %PagingOptions{key: {first_page.block_number, first_page.log_index}, page_size: 1}
token_transfers_primary_keys_paginated =
TokenTransfer.fetch_token_transfers_from_token_hash(
token_contract_address.hash,
paging_options: paging_options
)
token_contract_address.hash
|> TokenTransfer.fetch_token_transfers_from_token_hash(paging_options: paging_options)
|> Enum.map(&{&1.transaction_hash, &1.log_index})
assert token_transfers_primary_keys_paginated == [{second_page.transaction_hash, second_page.log_index}]
end
test "paginates considering the log_index when there are repeated block numbers" do
token_contract_address = insert(:contract_address)
transaction =
:transaction
|> insert()
|> with_block()
token = insert(:token)
token_transfer =
insert(
:token_transfer,
block_number: 1000,
log_index: 0,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
paging_options = %PagingOptions{key: {token_transfer.block_number, token_transfer.log_index + 1}, page_size: 1}
token_transfers_primary_keys_paginated =
token_contract_address.hash
|> TokenTransfer.fetch_token_transfers_from_token_hash(paging_options: paging_options)
|> Enum.map(&{&1.transaction_hash, &1.log_index})
assert token_transfers_primary_keys_paginated == [{token_transfer.transaction_hash, token_transfer.log_index}]
end
end
describe "count_token_transfers/0" do

@ -3112,6 +3112,7 @@ defmodule Explorer.ChainTest do
first_page =
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
@ -3122,6 +3123,7 @@ defmodule Explorer.ChainTest do
second_page =
insert(
:token_transfer,
block_number: 999,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
@ -3129,13 +3131,11 @@ defmodule Explorer.ChainTest do
token_id: 29
)
paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1}
paging_options = %PagingOptions{key: {first_page.block_number, first_page.log_index}, page_size: 1}
unique_tokens_ids_paginated =
Chain.address_to_unique_tokens(
token_contract_address.hash,
paging_options: paging_options
)
token_contract_address.hash
|> Chain.address_to_unique_tokens(paging_options: paging_options)
|> Enum.map(& &1.token_id)
assert unique_tokens_ids_paginated == [second_page.token_id]

@ -387,6 +387,7 @@ defmodule Explorer.Factory do
%TokenTransfer{
amount: Decimal.new(1),
block_number: block_number(),
from_address: from_address,
to_address: to_address,
token_contract_address: token_address,

Loading…
Cancel
Save