Build query to fetch transactions with token transfers

The query is scoped by the given address hash and the token hash. So, it
will considers transactions that have token transfers so that the given
address hash is the `to` or the `from` from a token transfer.
pull/764/head
Felipe Renan 6 years ago
parent 2b883c21c9
commit e4d4154fc7
  1. 21
      apps/explorer/lib/explorer/chain.ex
  2. 25
      apps/explorer/lib/explorer/chain/transaction.ex
  3. 140
      apps/explorer/test/explorer/chain/transaction_test.exs
  4. 76
      apps/explorer/test/explorer/chain_test.exs

@ -222,6 +222,27 @@ defmodule Explorer.Chain do
|> Enum.slice(0..paging_options.page_size)
end
@doc """
Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract
address hash.
## Options
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (in the form of `%{"inserted_at" => inserted_at}`). Results will be the transactions
older than the `index` that are passed.
"""
@spec address_to_transactions_with_token_tranfers(Hash.t(), Hash.t(), [paging_options]) :: [Transaction.t()]
def address_to_transactions_with_token_tranfers(address_hash, token_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hash
|> Transaction.transactions_with_token_transfers(token_hash)
|> Transaction.preload_token_transfers(address_hash)
|> handle_paging_options(paging_options)
|> Repo.all()
end
@doc """
The average time it took to mine/validate the last <= 100 `t:Explorer.Chain.Block.t/0`
"""

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Transaction do
use Explorer.Schema
import Ecto.Query, only: [from: 2, preload: 3, where: 3]
import Ecto.Query, only: [from: 2, preload: 3, where: 3, subquery: 1]
alias Ecto.Changeset
@ -16,6 +16,7 @@ defmodule Explorer.Chain.Transaction do
InternalTransaction,
Log,
TokenTransfer,
Transaction,
Wei
}
@ -503,4 +504,26 @@ defmodule Explorer.Chain.Transaction do
changeset
end
end
@doc """
Builds an `Ecto.Query` to fetch transactions with token transfers from the give address hash.
The results will be ordered by block number and index DESC.
"""
def transactions_with_token_transfers(address_hash, token_hash) do
query = transactions_with_token_transfers_query(address_hash, token_hash)
from(t in subquery(query), order_by: [desc: t.block_number, desc: t.index])
end
defp transactions_with_token_transfers_query(address_hash, token_hash) do
from(
t in Transaction,
inner_join: tt in TokenTransfer,
on: t.hash == tt.transaction_hash,
where: tt.token_contract_address_hash == ^token_hash,
where: tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash,
distinct: :hash
)
end
end

@ -37,4 +37,144 @@ defmodule Explorer.Chain.TransactionTest do
assert %Changeset{valid?: true} = Transaction.changeset(%Transaction{}, changeset_params)
end
end
describe "transactions_with_token_transfers/2" do
test "returns the transaction when there is token transfer from the given address" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
from_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
test "returns the transaction when there is token transfer to the given address" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
test "returns only transactions that have token transfers from the given token hash" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: insert(:token).contract_address
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
test "order the results DESC by block_number" do
address = insert(:address)
token = insert(:token)
transaction_a =
:transaction
|> insert()
|> with_block(insert(:block, number: 1000))
transaction_b =
:transaction
|> insert()
|> with_block(insert(:block, number: 1002))
transaction_c =
:transaction
|> insert()
|> with_block(insert(:block, number: 1003))
insert(
:token_transfer,
amount: 2,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction_a
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction_b
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction_c
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.block_number)
assert result == [transaction_c.block_number, transaction_b.block_number, transaction_a.block_number]
end
end
end

@ -2716,4 +2716,80 @@ defmodule Explorer.ChainTest do
assert Chain.count_token_holders_from_token_hash(contract_address_hash) == 0
end
end
describe "address_to_transactions_with_token_tranfers/2" do
test "paginates transactions by the block number" do
address = insert(:address)
token = insert(:token)
first_page =
:transaction
|> insert()
|> with_block(insert(:block, number: 1000))
second_page =
:transaction
|> insert()
|> with_block(insert(:block, number: 999))
insert(
:token_transfer,
to_address: address,
transaction: first_page,
token_contract_address: token.contract_address
)
insert(
:token_transfer,
to_address: address,
transaction: second_page,
token_contract_address: token.contract_address
)
paging_options = %PagingOptions{
key: {first_page.block_number, first_page.index},
page_size: 2
}
result =
address.hash
|> Chain.address_to_transactions_with_token_tranfers(token.contract_address_hash, paging_options: paging_options)
|> Enum.map(& &1.hash)
assert result == [second_page.hash]
end
test "doesn't duplicate the transaction when there are multiple transfers for it" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
amount: 2,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction
)
result =
address.hash
|> Chain.address_to_transactions_with_token_tranfers(token.contract_address_hash)
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
end
end

Loading…
Cancel
Save