Fold receipts into transactions

`Explorer.Chain.Receipt`s are never shown in isolation from
`Explorer.Chain.Transaction`s and `Explorer.Chain.Transaction`s are used
for pending and collated transactions, so move all collated-only fields
from `Explorer.Chain.Receipt`s to `Explorer.Chain.Transaction`s. Remove
`receipts` table completely: its migration is removed.  **THIS IS A DB
RESET AND REINDEX CHANGE.**
pull/222/head
Luke Imhoff 7 years ago
parent b7aadf26e2
commit f9c026202d
  1. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
  2. BIN
      apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.benchee
  3. 6
      apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs
  4. 160
      apps/explorer/lib/explorer/chain.ex
  5. 17
      apps/explorer/lib/explorer/chain/log.ex
  6. 53
      apps/explorer/lib/explorer/chain/receipt.ex
  7. 122
      apps/explorer/lib/explorer/chain/transaction.ex
  8. 36
      apps/explorer/lib/explorer/chain/transaction/status.ex
  9. 23
      apps/explorer/lib/explorer/indexer/block_fetcher.ex
  10. 73
      apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs
  11. 24
      apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs
  12. 2
      apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs
  13. 5
      apps/explorer/test/explorer/chain/receipt/status_test.exs
  14. 26
      apps/explorer/test/explorer/chain/receipt_test.exs
  15. 20
      apps/explorer/test/explorer/chain/statistics_test.exs
  16. 5
      apps/explorer/test/explorer/chain/transaction/status_test.exs
  17. 265
      apps/explorer/test/explorer/chain_test.exs
  18. 24
      apps/explorer/test/explorer/indexer/block_fetcher_test.exs
  19. 106
      apps/explorer/test/support/factory.ex
  20. 3
      apps/explorer_web/lib/explorer_web/controllers/address_transaction_controller.ex
  21. 3
      apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
  22. 3
      apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex
  23. 3
      apps/explorer_web/lib/explorer_web/controllers/transaction_internal_transaction_controller.ex
  24. 1
      apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex
  25. 6
      apps/explorer_web/lib/explorer_web/views/transaction_view.ex
  26. 7
      apps/explorer_web/test/explorer_web/controllers/address_internal_transaction_controller_test.exs
  27. 19
      apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
  28. 10
      apps/explorer_web/test/explorer_web/controllers/api/rpc/block_controller_test.exs
  29. 8
      apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs
  30. 16
      apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs
  31. 20
      apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs
  32. 13
      apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs
  33. 15
      apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs
  34. 11
      apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs
  35. 5
      apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs
  36. 30
      apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs
  37. 43
      apps/explorer_web/test/explorer_web/views/transaction_view_test.exs
  38. 2
      coveralls.json

@ -6,7 +6,7 @@ defmodule EthereumJSONRPC.Receipt do
import EthereumJSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
alias Explorer.Chain.Receipt.Status alias Explorer.Chain.Transaction.Status
alias EthereumJSONRPC alias EthereumJSONRPC
alias EthereumJSONRPC.Logs alias EthereumJSONRPC.Logs

@ -39,11 +39,7 @@ Benchee.run(
|> Enum.each(fn block -> |> Enum.each(fn block ->
transaction_count_per_block transaction_count_per_block
|> insert_list(:transaction) |> insert_list(:transaction)
|> Enum.each(fn transaction ->
transaction
|> with_block(block) |> with_block(block)
|> with_receipt()
end)
end) end)
input input
@ -54,7 +50,7 @@ Benchee.run(
load: path, load: path,
save: [ save: [
path: path, path: path,
tag: "separate-receipts" tag: "fold-receipts-into-transactions"
], ],
time: 10 time: 10
) )

@ -24,7 +24,6 @@ defmodule Explorer.Chain do
Hash, Hash,
InternalTransaction, InternalTransaction,
Log, Log,
Receipt,
Transaction, Transaction,
Wei Wei
} }
@ -160,7 +159,6 @@ defmodule Explorer.Chain do
@insert_blocks_timeout 60_000 @insert_blocks_timeout 60_000
@insert_internal_transactions_timeout 60_000 @insert_internal_transactions_timeout 60_000
@insert_logs_timeout 60_000 @insert_logs_timeout 60_000
@insert_receipts_timeout 60_000
@insert_transactions_timeout 60_000 @insert_transactions_timeout 60_000
@doc """ @doc """
@ -266,14 +264,13 @@ defmodule Explorer.Chain do
from( from(
block in Block, block in Block,
left_join: transaction in assoc(block, :transactions), left_join: transaction in assoc(block, :transactions),
left_join: receipt in assoc(transaction, :receipt),
inner_join: block_reward in Reward, inner_join: block_reward in Reward,
on: fragment("? <@ ?", block.number, block_reward.block_range), on: fragment("? <@ ?", block.number, block_reward.block_range),
where: block.number == ^block_number, where: block.number == ^block_number,
group_by: block_reward.reward, group_by: block_reward.reward,
select: %{ select: %{
transaction_reward: %Wei{ transaction_reward: %Wei{
value: default_if_empty(sum_of_products(receipt.gas_used, transaction.gas_price), 0) value: default_if_empty(sum_of_products(transaction.gas_used, transaction.gas_price), 0)
}, },
static_reward: block_reward.reward static_reward: block_reward.reward
} }
@ -406,28 +403,28 @@ defmodule Explorer.Chain do
...> %Explorer.Chain.Transaction{ ...> %Explorer.Chain.Transaction{
...> gas: Decimal.new(3), ...> gas: Decimal.new(3),
...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)}, ...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)},
...> receipt: nil ...> gas_used: nil
...> }, ...> },
...> :wei ...> :wei
...> ) ...> )
{:maximum, Decimal.new(6)} {:maximum, Decimal.new(6)}
If the transaction has been confirmed in block, then the fee will be the actual fee paid in `unit` for the `gas_used` If the transaction has been confirmed in block, then the fee will be the actual fee paid in `unit` for the `gas_used`
in the `receipt`. in the `transaction`.
iex> Explorer.Chain.fee( iex> Explorer.Chain.fee(
...> %Explorer.Chain.Transaction{ ...> %Explorer.Chain.Transaction{
...> gas: Decimal.new(3), ...> gas: Decimal.new(3),
...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)}, ...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)},
...> receipt: %Explorer.Chain.Receipt{gas_used: Decimal.new(2)} ...> gas_used: Decimal.new(2)
...> }, ...> },
...> :wei ...> :wei
...> ) ...> )
{:actual, Decimal.new(4)} {:actual, Decimal.new(4)}
""" """
@spec fee(%Transaction{receipt: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()} @spec fee(%Transaction{gas_used: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()}
def fee(%Transaction{gas: gas, gas_price: gas_price, receipt: nil}, unit) do def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do
fee = fee =
gas_price gas_price
|> Wei.to(unit) |> Wei.to(unit)
@ -436,8 +433,8 @@ defmodule Explorer.Chain do
{:maximum, fee} {:maximum, fee}
end end
@spec fee(%Transaction{receipt: Receipt.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()} @spec fee(%Transaction{gas_used: Decimal.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()}
def fee(%Transaction{gas_price: gas_price, receipt: %Receipt{gas_used: gas_used}}, unit) do def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do
fee = fee =
gas_price gas_price
|> Wei.to(unit) |> Wei.to(unit)
@ -587,7 +584,6 @@ defmodule Explorer.Chain do
| `:blocks` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Block.t/0` `hash` | | `:blocks` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Block.t/0` `hash` |
| `:internal_transactions` | `[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]` | List of maps of the `t:Explorer.Chain.InternalTransaction.t/0` `index` and `transaction_hash` | | `:internal_transactions` | `[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]` | List of maps of the `t:Explorer.Chain.InternalTransaction.t/0` `index` and `transaction_hash` |
| `:logs` | `[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]` | List of maps of the `t:Explorer.Chain.Log.t/0` `index` and `transaction_hash` | | `:logs` | `[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]` | List of maps of the `t:Explorer.Chain.Log.t/0` `index` and `transaction_hash` |
| `:receipts` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Receipt.t/0` `transaction_hash` |
| `:transactions` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Transaction.t/0` `hash` | | `:transactions` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Transaction.t/0` `hash` |
iex> Explorer.Chain.import_blocks( iex> Explorer.Chain.import_blocks(
@ -635,21 +631,14 @@ defmodule Explorer.Chain do
...> type: "mined" ...> type: "mined"
...> } ...> }
...> ], ...> ],
...> receipts: [
...> %{
...> cumulative_gas_used: 50450,
...> gas_used: 50450,
...> status: :ok,
...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> transaction_index: 0
...> }
...> ],
...> transactions: [ ...> transactions: [
...> %{ ...> %{
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> cumulative_gas_used: 50450,
...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> gas: 4700000, ...> gas: 4700000,
...> gas_price: 100000000000, ...> gas_price: 100000000000,
...> gas_used: 50450,
...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> index: 0, ...> index: 0,
...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", ...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
@ -658,6 +647,7 @@ defmodule Explorer.Chain do
...> r: 0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01, ...> r: 0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01,
...> s: 0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f, ...> s: 0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f,
...> standard_v: 1, ...> standard_v: 1,
...> status: :ok,
...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> v: 0xbe, ...> v: 0xbe,
...> value: 0 ...> value: 0
@ -713,14 +703,6 @@ defmodule Explorer.Chain do
} }
} }
], ],
receipts: [
%Explorer.Chain.Hash{
byte_count: 32,
bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136,
27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140,
57, 254, 153, 47, 255, 212, 51, 229>>
}
],
transactions: [ transactions: [
%Explorer.Chain.Hash{ %Explorer.Chain.Hash{
byte_count: 32, byte_count: 32,
@ -738,7 +720,6 @@ defmodule Explorer.Chain do
...> blocks: [], ...> blocks: [],
...> logs: [], ...> logs: [],
...> internal_transactions: [], ...> internal_transactions: [],
...> receipts: [],
...> transactions: [], ...> transactions: [],
...> addresses: [] ...> addresses: []
...> } ...> }
@ -749,7 +730,6 @@ defmodule Explorer.Chain do
blocks: [], blocks: [],
internal_transactions: [], internal_transactions: [],
logs: [], logs: [],
receipts: [],
transactions: [] transactions: []
}} }}
@ -757,7 +737,7 @@ defmodule Explorer.Chain do
are errors, they are returned in `Ecto.Changeset.t`s, so that the original, invalid value can be reconstructed for any are errors, they are returned in `Ecto.Changeset.t`s, so that the original, invalid value can be reconstructed for any
error messages. error messages.
iex> {:error, [internal_transaction_changeset, receipt_changeset]} = Explorer.Chain.import_blocks( iex> {:error, [internal_transaction_changeset, transaction_changeset]} = Explorer.Chain.import_blocks(
...> %{ ...> %{
...> blocks: [ ...> blocks: [
...> %{ ...> %{
@ -815,20 +795,14 @@ defmodule Explorer.Chain do
...> type: "mined" ...> type: "mined"
...> } ...> }
...> ], ...> ],
...> receipts: [
...> %{
...> cumulative_gas_used: 50450,
...> gas_used: 50450,
...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> transaction_index: 0
...> }
...> ],
...> transactions: [ ...> transactions: [
...> %{ ...> %{
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> cumulative_gas_used: 50450,
...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> gas: 4700000, ...> gas: 4700000,
...> gas_price: 100000000000, ...> gas_price: 100000000000,
...> gas_used: 50450,
...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> index: 0, ...> index: 0,
...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", ...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
@ -851,15 +825,16 @@ defmodule Explorer.Chain do
...> ) ...> )
iex> internal_transaction_changeset.errors iex> internal_transaction_changeset.errors
[call_type: {"can't be blank", [validation: :required]}] [call_type: {"can't be blank", [validation: :required]}]
iex> receipt_changeset.errors iex> transaction_changeset.errors
[status: {"can't be blank", [validation: :required]}] [
status: {"can't be blank when the transaction is collated into a block", []}
]
## Tree ## Tree
* `t:Explorer.Chain.Block.t/0`s * `t:Explorer.Chain.Block.t/0`s
* `t:Explorer.Chain.Transaction.t/0` * `t:Explorer.Chain.Transaction.t/0`
* `t.Explorer.Chain.InternalTransaction.t/0` * `t.Explorer.Chain.InternalTransaction.t/0`
* `t.Explorer.Chain.Receipt.t/0`
* `t.Explorer.Chain.Log.t/0` * `t.Explorer.Chain.Log.t/0`
## Options ## Options
@ -873,8 +848,6 @@ defmodule Explorer.Chain do
* `:insert_internal_transactions_timeout` - the timeout for inserting all internal transactions. Defaults to * `:insert_internal_transactions_timeout` - the timeout for inserting all internal transactions. Defaults to
`#{@insert_internal_transactions_timeout}` milliseconds. `#{@insert_internal_transactions_timeout}` milliseconds.
* `:insert_logs_timeout` - the timeout for inserting all logs. Defaults to `#{@insert_logs_timeout}` milliseconds. * `:insert_logs_timeout` - the timeout for inserting all logs. Defaults to `#{@insert_logs_timeout}` milliseconds.
* `:insert_receipts_timeout` - the timeout for inserting all receipts. Defaults to `#{@insert_receipts_timeout}`
milliseconds.
* `:insert_transactions_timeout` - the timeout for inserting all transactions found in the params lists across all * `:insert_transactions_timeout` - the timeout for inserting all transactions found in the params lists across all
types. Defaults to `#{@insert_transactions_timeout}` milliseconds. types. Defaults to `#{@insert_transactions_timeout}` milliseconds.
""" """
@ -883,21 +856,18 @@ defmodule Explorer.Chain do
blocks: blocks_params, blocks: blocks_params,
logs: logs_params, logs: logs_params,
internal_transactions: internal_transactions_params, internal_transactions: internal_transactions_params,
receipts: receipts_params,
transactions: transactions_params, transactions: transactions_params,
addresses: addresses_params addresses: addresses_params
}, },
options \\ [] options \\ []
) )
when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(logs_params) and when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(logs_params) and
is_list(receipts_params) and is_list(transactions_params) and is_list(addresses_params) and is_list(transactions_params) and is_list(addresses_params) and is_list(options) do
is_list(options) do
with {:ok, ecto_schema_module_to_changes_list} <- with {:ok, ecto_schema_module_to_changes_list} <-
ecto_schema_module_to_params_list_to_ecto_schema_module_to_changes_list(%{ ecto_schema_module_to_params_list_to_ecto_schema_module_to_changes_list(%{
Block => blocks_params, Block => blocks_params,
Log => logs_params, Log => logs_params,
InternalTransaction => internal_transactions_params, InternalTransaction => internal_transactions_params,
Receipt => receipts_params,
Transaction => transactions_params, Transaction => transactions_params,
Address => addresses_params Address => addresses_params
}) do }) do
@ -1030,10 +1000,8 @@ defmodule Explorer.Chain do
@doc """ @doc """
The number of `t:Explorer.Chain.Log.t/0`. The number of `t:Explorer.Chain.Log.t/0`.
iex> block = insert(:block) iex> transaction = :transaction |> insert() |> with_block()
iex> transaction = insert(:transaction, block_hash: block.hash, index: 0) iex> insert(:log, transaction_hash: transaction.hash, index: 0)
iex> receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
iex> insert(:log, transaction_hash: receipt.transaction_hash, index: 0)
iex> Explorer.Chain.log_count() iex> Explorer.Chain.log_count()
1 1
@ -1143,31 +1111,12 @@ defmodule Explorer.Chain do
end end
end end
@doc """
The number of `t:Explorer.Chain.Receipt.t/0`.
iex> block = insert(:block)
iex> transaction = insert(:transaction, block_hash: block.hash, index: 0)
iex> insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
iex> Explorer.Chain.receipt_count()
1
When there are no `t:Explorer.Chain.Receipt.t/0`.
iex> Explorer.Chain.receipt_count()
0
"""
def receipt_count do
Repo.aggregate(Receipt, :count, :transaction_hash)
end
@doc """ @doc """
Returns the list of collated transactions that occurred recently (10). Returns the list of collated transactions that occurred recently (10).
iex> 2 |> insert_list(:transaction) |> validate() iex> 2 |> insert_list(:transaction) |> with_block()
iex> insert(:transaction) # unvalidated transaction iex> insert(:transaction) # unvalidated transaction
iex> 8 |> insert_list(:transaction) |> validate() iex> 8 |> insert_list(:transaction) |> with_block()
iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions() iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions()
iex> length(recent_collated_transactions) iex> length(recent_collated_transactions)
10 10
@ -1181,10 +1130,10 @@ defmodule Explorer.Chain do
returned. This can be used to generate paging for collated transaction. returned. This can be used to generate paging for collated transaction.
iex> first_block = insert(:block, number: 1) iex> first_block = insert(:block, number: 1)
iex> first_transaction_in_first_block = insert(:transaction, block_hash: first_block.hash, index: 0) iex> first_transaction_in_first_block = :transaction |> insert() |> with_block(first_block)
iex> second_transaction_in_first_block = insert(:transaction, block_hash: first_block.hash, index: 1) iex> second_transaction_in_first_block = :transaction |> insert() |> with_block(first_block)
iex> second_block = insert(:block, number: 2) iex> second_block = insert(:block, number: 2)
iex> first_transaction_in_second_block = insert(:transaction, block_hash: second_block.hash, index: 0) iex> first_transaction_in_second_block = :transaction |> insert() |> with_block(second_block)
iex> after_first_transaciton_in_first_block = Explorer.Chain.recent_collated_transactions( iex> after_first_transaciton_in_first_block = Explorer.Chain.recent_collated_transactions(
...> after_hash: first_transaction_in_first_block.hash ...> after_hash: first_transaction_in_first_block.hash
...> ) ...> )
@ -1245,7 +1194,7 @@ defmodule Explorer.Chain do
Return the list of pending transactions that occurred recently (10). Return the list of pending transactions that occurred recently (10).
iex> 2 |> insert_list(:transaction) iex> 2 |> insert_list(:transaction)
iex> :transaction |> insert() |> validate() iex> :transaction |> insert() |> with_block()
iex> 8 |> insert_list(:transaction) iex> 8 |> insert_list(:transaction)
iex> %Scrivener.Page{entries: recent_pending_transactions} = Explorer.Chain.recent_pending_transactions() iex> %Scrivener.Page{entries: recent_pending_transactions} = Explorer.Chain.recent_pending_transactions()
iex> length(recent_pending_transactions) iex> length(recent_pending_transactions)
@ -1277,9 +1226,9 @@ defmodule Explorer.Chain do
When there are no pending transaction and a collated transaction's inserted_at is used, an empty list is returned When there are no pending transaction and a collated transaction's inserted_at is used, an empty list is returned
iex> {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z") iex> {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
iex> :transaction |> insert(inserted_at: first_inserted_at) |> validate() iex> :transaction |> insert(inserted_at: first_inserted_at) |> with_block()
iex> {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z") iex> {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z")
iex> :transaction |> insert(inserted_at: second_inserted_at) |> validate() iex> :transaction |> insert(inserted_at: second_inserted_at) |> with_block()
iex> %Scrivener.Page{entries: entries} = Explorer.Chain.recent_pending_transactions( iex> %Scrivener.Page{entries: entries} = Explorer.Chain.recent_pending_transactions(
...> after_inserted_at: first_inserted_at ...> after_inserted_at: first_inserted_at
...> ) ...> )
@ -1399,7 +1348,7 @@ defmodule Explorer.Chain do
With no options or an explicit `pending: nil`, both collated and pending transactions will be counted. With no options or an explicit `pending: nil`, both collated and pending transactions will be counted.
iex> insert(:transaction) iex> insert(:transaction)
iex> :transaction |> insert() |> validate() iex> :transaction |> insert() |> with_block()
iex> Explorer.Chain.transaction_count() iex> Explorer.Chain.transaction_count()
2 2
iex> Explorer.Chain.transaction_count(pending: nil) iex> Explorer.Chain.transaction_count(pending: nil)
@ -1408,14 +1357,14 @@ defmodule Explorer.Chain do
To count only collated transactions, pass `pending: false`. To count only collated transactions, pass `pending: false`.
iex> 2 |> insert_list(:transaction) iex> 2 |> insert_list(:transaction)
iex> 3 |> insert_list(:transaction) |> validate() iex> 3 |> insert_list(:transaction) |> with_block()
iex> Explorer.Chain.transaction_count(pending: false) iex> Explorer.Chain.transaction_count(pending: false)
3 3
To count only pending transactions, pass `pending: true`. To count only pending transactions, pass `pending: true`.
iex> 2 |> insert_list(:transaction) iex> 2 |> insert_list(:transaction)
iex> 3 |> insert_list(:transaction) |> validate() iex> 3 |> insert_list(:transaction) |> with_block()
iex> Explorer.Chain.transaction_count(pending: true) iex> Explorer.Chain.transaction_count(pending: true)
2 2
@ -1486,7 +1435,7 @@ defmodule Explorer.Chain do
end end
@doc """ @doc """
Converts `transaction` with its `receipt` loaded to the status of the `t:Explorer.Chain.Transaction.t/0`. Converts `transaction` to the status of the `t:Explorer.Chain.Transaction.t/0` whether pending or collated.
## Returns ## Returns
@ -1497,18 +1446,14 @@ defmodule Explorer.Chain do
""" """
@spec transaction_to_status(Transaction.t()) :: :failed | :pending | :out_of_gas | :success @spec transaction_to_status(Transaction.t()) :: :failed | :pending | :out_of_gas | :success
def transaction_to_status(%Transaction{receipt: nil}), do: :pending def transaction_to_status(%Transaction{status: nil}), do: :pending
def transaction_to_status(%Transaction{receipt: %Receipt{status: :ok}}), do: :success def transaction_to_status(%Transaction{status: :ok}), do: :success
def transaction_to_status(%Transaction{ def transaction_to_status(%Transaction{gas: gas, gas_used: gas_used, status: :error}) when gas_used >= gas do
gas: gas,
receipt: %Receipt{gas_used: gas_used, status: :error}
})
when gas_used >= gas do
:out_of_gas :out_of_gas
end end
def transaction_to_status(%Transaction{receipt: %Receipt{status: :error}}), do: :failed def transaction_to_status(%Transaction{status: :error}), do: :failed
@doc """ @doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
@ -1666,7 +1611,6 @@ defmodule Explorer.Chain do
Block => blocks_changes, Block => blocks_changes,
Log => logs_changes, Log => logs_changes,
InternalTransaction => internal_transactions_changes, InternalTransaction => internal_transactions_changes,
Receipt => receipts_changes,
Transaction => transactions_changes Transaction => transactions_changes
}, },
options options
@ -1707,13 +1651,6 @@ defmodule Explorer.Chain do
timestamps: timestamps timestamps: timestamps
) )
end) end)
|> Multi.run(:receipts, fn _ ->
insert_receipts(
receipts_changes,
timeout: Keyword.get(options, :insert_receipts_timeout, @insert_receipts_timeout),
timestamps: timestamps
)
end)
|> Multi.run(:logs, fn _ -> |> Multi.run(:logs, fn _ ->
insert_logs( insert_logs(
logs_changes, logs_changes,
@ -1774,29 +1711,6 @@ defmodule Explorer.Chain do
{:ok, for(log <- logs, do: Map.take(log, [:index, :transaction_hash]))} {:ok, for(log <- logs, do: Map.take(log, [:index, :transaction_hash]))}
end end
@spec insert_receipts([map()], [timeout_option | timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]}
defp insert_receipts(changes_list, named_arguments)
when is_list(changes_list) and is_list(named_arguments) do
timestamps = Keyword.fetch!(named_arguments, :timestamps)
timeout = Keyword.fetch!(named_arguments, :timeout)
# order so that row ShareLocks are grabbed in a consistent order
ordered_changes_list = Enum.sort_by(changes_list, & &1.transaction_hash)
{:ok, receipts} =
insert_changes_list(
ordered_changes_list,
conflict_target: :transaction_hash,
on_conflict: :replace_all,
for: Receipt,
returning: [:transaction_hash],
timeout: timeout,
timestamps: timestamps
)
{:ok, for(receipt <- receipts, do: receipt.transaction_hash)}
end
defp insert_changes_list(changes_list, options) when is_list(changes_list) do defp insert_changes_list(changes_list, options) when is_list(changes_list) do
ecto_schema_module = Keyword.fetch!(options, :for) ecto_schema_module = Keyword.fetch!(options, :for)

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Log do
use Explorer.Schema use Explorer.Schema
alias Explorer.Chain.{Address, Data, Hash, Receipt, Transaction} alias Explorer.Chain.{Address, Data, Hash, Transaction}
@required_attrs ~w(address_hash data index transaction_hash type)a @required_attrs ~w(address_hash data index transaction_hash type)a
@optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a @optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a
@ -14,13 +14,10 @@ defmodule Explorer.Chain.Log do
* `data` - non-indexed log parameters. * `data` - non-indexed log parameters.
* `first_topic` - `topics[0]` * `first_topic` - `topics[0]`
* `fourth_topic` - `topics[3]` * `fourth_topic` - `topics[3]`
* `index` - index of the log entry in all logs for the `receipt` / `transaction` * `index` - index of the log entry in all logs for the `transaction`
* `receipt` - receipt for the `transaction` being mined in a block
* `second_topic` - `topics[1]` * `second_topic` - `topics[1]`
* `transaction` - transaction for which `receipt` is * `transaction` - transaction for which `log` is
* `transaction_hash` - foreign key for `receipt`. **ALWAYS join through `receipts` and not directly to * `transaction_hash` - foreign key for `transaction`.
`transaction` to ensure that any `t:Explorer.Chain.Transaction.t/0` has a receipt before it has logs in that
receipt.**
* `third_topic` - `topics[2]` * `third_topic` - `topics[2]`
* `type` - type of event * `type` - type of event
""" """
@ -31,7 +28,6 @@ defmodule Explorer.Chain.Log do
first_topic: String.t(), first_topic: String.t(),
fourth_topic: String.t(), fourth_topic: String.t(),
index: non_neg_integer(), index: non_neg_integer(),
receipt: %Ecto.Association.NotLoaded{} | Receipt.t(),
second_topic: String.t(), second_topic: String.t(),
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.Full.t(), transaction_hash: Hash.Full.t(),
@ -51,8 +47,7 @@ defmodule Explorer.Chain.Log do
timestamps() timestamps()
belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated)
belongs_to(:receipt, Receipt, foreign_key: :transaction_hash, references: :transaction_hash, type: Hash.Full) belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full)
has_one(:transaction, through: [:receipt, :transaction])
end end
@doc """ @doc """
@ -94,8 +89,6 @@ defmodule Explorer.Chain.Log do
log log
|> cast(attrs, @required_attrs) |> cast(attrs, @required_attrs)
|> cast(attrs, @optional_attrs) |> cast(attrs, @optional_attrs)
|> cast_assoc(:address)
|> cast_assoc(:receipt)
|> validate_required(@required_attrs) |> validate_required(@required_attrs)
end end
end end

@ -1,53 +0,0 @@
defmodule Explorer.Chain.Receipt do
@moduledoc "Captures a Web3 Transaction Receipt."
use Explorer.Schema
alias Explorer.Chain.{Gas, Hash, Log, Transaction}
alias Explorer.Chain.Receipt.Status
@optional_attrs ~w()a
@required_attrs ~w(cumulative_gas_used gas_used status transaction_hash transaction_index)a
@allowed_attrs @optional_attrs ++ @required_attrs
@typedoc """
* `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before
`transaction`'s `index`
* `gas_used` - the gas used for just `transaction`
* `logs` - events that occurred while mining the `transaction`
* `status` - whether the transaction was successfully mined or failed
* `transaction` - the transaction for which this receipt is for
* `transaction_hash` - foreign key for `transaction`
* `transaction_index` - index of `transaction` in its `t:Explorer.Chain.Block.t/0`.
"""
@type t :: %__MODULE__{
cumulative_gas_used: Gas.t(),
gas_used: Gas.t(),
logs: %Ecto.Association.NotLoaded{} | [Log.t()],
status: Status.t(),
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.Full.t(),
transaction_index: non_neg_integer()
}
@primary_key false
schema "receipts" do
field(:cumulative_gas_used, :decimal)
field(:gas_used, :decimal)
field(:status, Status)
field(:transaction_index, :integer)
belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full)
has_many(:logs, Log, foreign_key: :transaction_hash, references: :transaction_hash)
timestamps()
end
def changeset(%__MODULE__{} = transaction_receipt, attrs \\ %{}) do
transaction_receipt
|> cast(attrs, @allowed_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:transaction_hash)
|> unique_constraint(:transaction_hash)
end
end

@ -4,9 +4,10 @@ defmodule Explorer.Chain.Transaction do
use Explorer.Schema use Explorer.Schema
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.Chain.{Address, Block, Data, Gas, Hash, InternalTransaction, Receipt, Wei} alias Explorer.Chain.{Address, Block, Data, Gas, Hash, InternalTransaction, Log, Wei}
alias Explorer.Chain.Transaction.Status
@optional_attrs ~w(block_hash from_address_hash index to_address_hash)a @optional_attrs ~w(block_hash cumulative_gas_used from_address_hash gas_used index status to_address_hash)a
@required_attrs ~w(gas gas_price hash input nonce public_key r s standard_v v value)a @required_attrs ~w(gas gas_price hash input nonce public_key r s standard_v v value)a
@typedoc """ @typedoc """
@ -73,15 +74,19 @@ defmodule Explorer.Chain.Transaction do
@typedoc """ @typedoc """
* `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending. * `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending.
* `block_hash` - `block` foreign key. `nil` when transaction is pending. * `block_hash` - `block` foreign key. `nil` when transaction is pending.
* `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before
`transaction`'s `index`. `nil` when transaction is pending.
* `from_address` - the source of `value` * `from_address` - the source of `value`
* `from_address_hash` - foreign key of `from_address` * `from_address_hash` - foreign key of `from_address`
* `gas` - Gas provided by the sender * `gas` - Gas provided by the sender
* `gas_price` - How much the sender is willing to pay for `gas` * `gas_price` - How much the sender is willing to pay for `gas`
* `gas_used` - the gas used for just `transaction`. `nil` when transaction is pending.
* `hash` - hash of contents of this transaction * `hash` - hash of contents of this transaction
* `index` - index of this transaction in `block`. `nil` when transaction is pending. * `index` - index of this transaction in `block`. `nil` when transaction is pending.
* `input`- data sent along with the transaction * `input`- data sent along with the transaction
* `internal_transactions` - transactions (value transfers) created while executing contract used for this * `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction transaction
* `logs` - events that occurred while mining the `transaction`.
* `nonce` - the number of transaction made by the sender prior to this one * `nonce` - the number of transaction made by the sender prior to this one
* `public_key` - public key of the signer of the transaction * `public_key` - public key of the signer of the transaction
* `r` - the R field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as * `r` - the R field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as
@ -89,6 +94,7 @@ defmodule Explorer.Chain.Transaction do
* `s` - The S field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as * `s` - The S field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as
the X coordinate of a point R, modulo the curve order n. the X coordinate of a point R, modulo the curve order n.
* `standard_v` - The standardized V field of the signature * `standard_v` - The standardized V field of the signature
* `status` - whether the transaction was successfully mined or failed. `nil` when transaction is pending.
* `to_address` - sink of `value` * `to_address` - sink of `value`
* `to_address_hash` - `to_address` foreign key * `to_address_hash` - `to_address` foreign key
* `v` - The V field of the signature. * `v` - The V field of the signature.
@ -97,20 +103,23 @@ defmodule Explorer.Chain.Transaction do
@type t :: %__MODULE__{ @type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil, block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
block_hash: Hash.t() | nil, block_hash: Hash.t() | nil,
cumulative_gas_used: Gas.t() | nil,
from_address: %Ecto.Association.NotLoaded{} | Address.t(), from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_hash: Hash.Truncated.t(), from_address_hash: Hash.Truncated.t(),
gas: Gas.t(), gas: Gas.t(),
gas_price: wei_per_gas, gas_price: wei_per_gas,
gas_used: Gas.t() | nil,
hash: Hash.t(), hash: Hash.t(),
index: non_neg_integer() | nil, index: non_neg_integer() | nil,
input: Data.t(), input: Data.t(),
internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()], internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
logs: %Ecto.Association.NotLoaded{} | [Log.t()],
nonce: non_neg_integer(), nonce: non_neg_integer(),
public_key: public_key(), public_key: public_key(),
r: r(), r: r(),
receipt: %Ecto.Association.NotLoaded{} | Receipt.t(),
s: s(), s: s(),
standard_v: standard_v(), standard_v: standard_v(),
status: Status.t() | nil,
to_address: %Ecto.Association.NotLoaded{} | Address.t(), to_address: %Ecto.Association.NotLoaded{} | Address.t(),
to_address_hash: Hash.Truncated.t(), to_address_hash: Hash.Truncated.t(),
v: v(), v: v(),
@ -119,8 +128,10 @@ defmodule Explorer.Chain.Transaction do
@primary_key {:hash, Hash.Full, autogenerate: false} @primary_key {:hash, Hash.Full, autogenerate: false}
schema "transactions" do schema "transactions" do
field(:cumulative_gas_used, :decimal)
field(:gas, :decimal) field(:gas, :decimal)
field(:gas_price, Wei) field(:gas_price, Wei)
field(:gas_used, :decimal)
field(:index, :integer) field(:index, :integer)
field(:input, Data) field(:input, Data)
field(:nonce, :integer) field(:nonce, :integer)
@ -128,6 +139,7 @@ defmodule Explorer.Chain.Transaction do
field(:r, :decimal) field(:r, :decimal)
field(:s, :decimal) field(:s, :decimal)
field(:standard_v, :integer) field(:standard_v, :integer)
field(:status, Status)
field(:v, :integer) field(:v, :integer)
field(:value, Wei) field(:value, Wei)
@ -144,7 +156,7 @@ defmodule Explorer.Chain.Transaction do
) )
has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash) has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash)
has_one(:receipt, Receipt, foreign_key: :transaction_hash) has_many(:logs, Log, foreign_key: :transaction_hash)
belongs_to( belongs_to(
:to_address, :to_address,
@ -177,13 +189,16 @@ defmodule Explorer.Chain.Transaction do
iex> changeset.valid? iex> changeset.valid?
true true
A pending transaction can't have an `index` A pending transaction (which is indicated by not having a `block_hash`) can't have `cumulative_gas_used`, `gas_used`,
or `index`.
iex> changeset = Explorer.Chain.Transaction.changeset( iex> changeset = Explorer.Chain.Transaction.changeset(
...> %Transaction{}, ...> %Transaction{},
...> %{ ...> %{
...> cumulative_gas_used: 0,
...> gas: 4700000, ...> gas: 4700000,
...> gas_price: 100000000000, ...> gas_price: 100000000000,
...> gas_used: 4600000,
...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0, ...> index: 0,
...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
@ -192,14 +207,21 @@ defmodule Explorer.Chain.Transaction do
...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75, ...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75,
...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3, ...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3,
...> standard_v: 0x0, ...> standard_v: 0x0,
...> status: :ok,
...> v: 0x8d, ...> v: 0x8d,
...> value: 0 ...> value: 0
...> } ...> }
...> ) ...> )
iex> changeset.valid? iex> changeset.valid?
false false
iex> changeset.errors iex> Keyword.get_values(changeset.errors, :cumulative_gas_used)
[index: {"can't be set when the transaction is pending", []}] [{"can't be set when the transaction is pending", []}]
iex> Keyword.get_values(changeset.errors, :gas_used)
[{"can't be set when the transaction is pending", []}]
iex> Keyword.get_values(changeset.errors, :index)
[{"can't be set when the transaction is pending", []}]
iex> Keyword.get_values(changeset.errors, :status)
[{"can't be set when the transaction is pending", []}]
A collated transaction has a `block_hash` for the block in which it was collated. A collated transaction has a `block_hash` for the block in which it was collated.
@ -207,8 +229,10 @@ defmodule Explorer.Chain.Transaction do
...> %Transaction{}, ...> %Transaction{},
...> %{ ...> %{
...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
...> cumulative_gas_used: 0,
...> gas: 4700000, ...> gas: 4700000,
...> gas_price: 100000000000, ...> gas_price: 100000000000,
...> gas_used: 4600000,
...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0, ...> index: 0,
...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
@ -217,6 +241,7 @@ defmodule Explorer.Chain.Transaction do
...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75, ...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75,
...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3, ...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3,
...> standard_v: 0x0, ...> standard_v: 0x0,
...> status: :ok,
...> v: 0x8d, ...> v: 0x8d,
...> value: 0 ...> value: 0
...> } ...> }
@ -224,7 +249,8 @@ defmodule Explorer.Chain.Transaction do
iex> changeset.valid? iex> changeset.valid?
true true
A collated transaction MUST have an `index` so its position in the `block` is known. A collated transaction MUST have an `index` so its position in the `block` is known, a `status` to know whether it
succeeded and the `cumulative_gas_used` ane `gas_used` to know its fees.
iex> changeset = Explorer.Chain.Transaction.changeset( iex> changeset = Explorer.Chain.Transaction.changeset(
...> %Transaction{}, ...> %Transaction{},
@ -245,38 +271,82 @@ defmodule Explorer.Chain.Transaction do
...> ) ...> )
iex> changeset.valid? iex> changeset.valid?
false false
iex> changeset.errors iex> Keyword.get_values(changeset.errors, :cumulative_gas_used)
[index: {"can't be blank when transaction is collated into a block", []}] [{"can't be blank when the transaction is collated into a block", []}]
iex> Keyword.get_values(changeset.errors, :gas_used)
[{"can't be blank when the transaction is collated into a block", []}]
iex> Keyword.get_values(changeset.errors, :index)
[{"can't be blank when the transaction is collated into a block", []}]
iex> Keyword.get_values(changeset.errors, :status)
[{"can't be blank when the transaction is collated into a block", []}]
""" """
def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do
transaction transaction
|> cast(attrs, @required_attrs ++ @optional_attrs) |> cast(attrs, @required_attrs ++ @optional_attrs)
|> validate_required(@required_attrs) |> validate_required(@required_attrs)
|> validate_collated() |> validate_collated_or_pending()
|> validate_number(:standard_v, greater_than_or_equal_to: 0, less_than_or_equal_to: 3) |> validate_number(:standard_v, greater_than_or_equal_to: 0, less_than_or_equal_to: 3)
|> check_constraint( |> check_pending()
:index, |> check_collated()
message: "cannot be set when block_hash is nil and must be set when block_hash is not nil",
name: :indexed
)
|> foreign_key_constraint(:block_hash) |> foreign_key_constraint(:block_hash)
|> unique_constraint(:hash) |> unique_constraint(:hash)
end end
defp validate_collated(%Changeset{} = changeset) do @collated_fields ~w(cumulative_gas_used gas_used index status)a
case {Changeset.get_field(changeset, :block_hash), Changeset.get_field(changeset, :index)} do
{nil, nil} -> @collated_message "can't be blank when the transaction is collated into a block"
changeset @collated_field_to_check Enum.into(@collated_fields, %{}, fn collated_field ->
{collated_field, :"collated_#{collated_field}}"}
end)
@pending_message "can't be set when the transaction is pending"
@pending_field_to_check Enum.into(@collated_fields, %{}, fn collated_field ->
{collated_field, :"pending_#{collated_field}}"}
end)
defp check_collated(%Changeset{} = changeset) do
check_constraints(changeset, @collated_field_to_check, @collated_message)
end
defp check_pending(%Changeset{} = changeset) do
check_constraints(changeset, @pending_field_to_check, @pending_message)
end
defp check_constraints(%Changeset{} = changeset, field_to_name, message)
when is_map(field_to_name) and is_binary(message) do
Enum.reduce(field_to_name, changeset, fn {field, name}, acc_changeset ->
check_constraint(
acc_changeset,
field,
message: message,
name: name
)
end)
end
defp validate_collated_or_pending(%Changeset{} = changeset) do
case Changeset.get_field(changeset, :block_hash) do
nil -> validate_collated_or_pending(changeset, &validate_pending/2)
%Hash{} -> validate_collated_or_pending(changeset, &validate_collated/2)
end
end
{_block_hash, nil} -> defp validate_collated_or_pending(%Changeset{} = changeset, field_validator) when is_function(field_validator, 2) do
Changeset.add_error(changeset, :index, "can't be blank when transaction is collated into a block") Enum.reduce(@collated_fields, changeset, field_validator)
end
{nil, _index} -> defp validate_pending(field, %Changeset{} = changeset) when is_atom(field) do
Changeset.add_error(changeset, :index, "can't be set when the transaction is pending") case Changeset.get_field(changeset, field) do
nil -> changeset
_ -> Changeset.add_error(changeset, field, @pending_message)
end
end
_ -> defp validate_collated(field, %Changeset{} = changeset) when is_atom(field) do
changeset case Changeset.get_field(changeset, field) do
nil -> Changeset.add_error(changeset, field, @collated_message)
_ -> changeset
end end
end end
end end

@ -1,4 +1,4 @@
defmodule Explorer.Chain.Receipt.Status do defmodule Explorer.Chain.Transaction.Status do
@moduledoc """ @moduledoc """
Whether a transaction succeeded (`:ok`) or failed (`:error`). Whether a transaction succeeded (`:ok`) or failed (`:error`).
@ -21,27 +21,27 @@ defmodule Explorer.Chain.Receipt.Status do
If the `term` is already in `t:t/0`, then it is returned If the `term` is already in `t:t/0`, then it is returned
iex> Explorer.Chain.Receipt.Status.cast(:ok) iex> Explorer.Chain.Transaction.Status.cast(:ok)
{:ok, :ok} {:ok, :ok}
iex> Explorer.Chain.Receipt.Status.cast(:error) iex> Explorer.Chain.Transaction.Status.cast(:error)
{:ok, :error} {:ok, :error}
If the `term` is an `non_neg_integer`, then it is converted only if it is `0` or `1`. If the `term` is an `non_neg_integer`, then it is converted only if it is `0` or `1`.
iex> Explorer.Chain.Receipt.Status.cast(0) iex> Explorer.Chain.Transaction.Status.cast(0)
{:ok, :error} {:ok, :error}
iex> Explorer.Chain.Receipt.Status.cast(1) iex> Explorer.Chain.Transaction.Status.cast(1)
{:ok, :ok} {:ok, :ok}
iex> Explorer.Chain.Receipt.Status.cast(2) iex> Explorer.Chain.Transaction.Status.cast(2)
:error :error
If the `term` is in the quantity format used by `Explorer.JSONRPC`, it is converted only if `0x0` or `0x1` If the `term` is in the quantity format used by `Explorer.JSONRPC`, it is converted only if `0x0` or `0x1`
iex> Explorer.Chain.Receipt.Status.cast("0x0") iex> Explorer.Chain.Transaction.Status.cast("0x0")
{:ok, :error} {:ok, :error}
iex> Explorer.Chain.Receipt.Status.cast("0x1") iex> Explorer.Chain.Transaction.Status.cast("0x1")
{:ok, :ok} {:ok, :ok}
iex> Explorer.Chain.Receipt.Status.cast("0x2") iex> Explorer.Chain.Transaction.Status.cast("0x2")
:error :error
""" """
@ -58,20 +58,20 @@ defmodule Explorer.Chain.Receipt.Status do
@doc """ @doc """
Dumps the `atom` format to `integer` format used in database. Dumps the `atom` format to `integer` format used in database.
iex> Explorer.Chain.Receipt.Status.dump(:ok) iex> Explorer.Chain.Transaction.Status.dump(:ok)
{:ok, 1} {:ok, 1}
iex> Explorer.Chain.Receipt.Status.dump(:error) iex> Explorer.Chain.Transaction.Status.dump(:error)
{:ok, 0} {:ok, 0}
If the value hasn't been cast first, it can't be dumped. If the value hasn't been cast first, it can't be dumped.
iex> Explorer.Chain.Receipt.Status.dump(0) iex> Explorer.Chain.Transaction.Status.dump(0)
:error :error
iex> Explorer.Chain.Receipt.Status.dump(1) iex> Explorer.Chain.Transaction.Status.dump(1)
:error :error
iex> Explorer.Chain.Receipt.Status.dump("0x0") iex> Explorer.Chain.Transaction.Status.dump("0x0")
:error :error
iex> Explorer.Chain.Receipt.Status.dump("0x1") iex> Explorer.Chain.Transaction.Status.dump("0x1")
:error :error
""" """
@ -86,11 +86,11 @@ defmodule Explorer.Chain.Receipt.Status do
Only loads integers `0` and `1`. Only loads integers `0` and `1`.
iex> Explorer.Chain.Receipt.Status.load(0) iex> Explorer.Chain.Transaction.Status.load(0)
{:ok, :error} {:ok, :error}
iex> Explorer.Chain.Receipt.Status.load(1) iex> Explorer.Chain.Transaction.Status.load(1)
{:ok, :ok} {:ok, :ok}
iex> Explorer.Chain.Receipt.Status.load(2) iex> Explorer.Chain.Transaction.Status.load(2)
:error :error
""" """

@ -137,7 +137,6 @@ defmodule Explorer.Indexer.BlockFetcher do
================================ ================================
blocks: #{Chain.block_count()} blocks: #{Chain.block_count()}
internal transactions: #{Chain.internal_transaction_count()} internal transactions: #{Chain.internal_transaction_count()}
receipts: #{Chain.receipt_count()}
logs: #{Chain.log_count()} logs: #{Chain.log_count()}
addresses: #{Chain.address_count()} addresses: #{Chain.address_count()}
""" """
@ -259,11 +258,12 @@ defmodule Explorer.Indexer.BlockFetcher do
@doc false @doc false
def import_range({block_start, block_end} = range, %{} = state, seq) do def import_range({block_start, block_end} = range, %{} = state, seq) do
with {:blocks, {:ok, next, result}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(block_start, block_end)}, with {:blocks, {:ok, next, result}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(block_start, block_end)},
%{blocks: blocks, transactions: transactions} = result, %{blocks: blocks, transactions: transactions_without_receipts} = result,
cap_seq(seq, next, range, state), cap_seq(seq, next, range, state),
transaction_hashes = Transactions.params_to_hashes(transactions), transaction_hashes = Transactions.params_to_hashes(transactions_without_receipts),
{:receipts, {:ok, receipt_params}} <- {:receipts, fetch_transaction_receipts(state, transaction_hashes)}, {:receipts, {:ok, receipt_params}} <- {:receipts, fetch_transaction_receipts(state, transaction_hashes)},
%{logs: logs, receipts: receipts} = receipt_params, %{logs: logs, receipts: receipts} = receipt_params,
transactions_with_receipts = put_receipts(transactions_without_receipts, receipts),
{:internal_transactions, {:ok, internal_transactions}} <- {:internal_transactions, {:ok, internal_transactions}} <-
{:internal_transactions, fetch_internal_transactions(state, transaction_hashes)} do {:internal_transactions, fetch_internal_transactions(state, transaction_hashes)} do
addresses = addresses =
@ -271,7 +271,7 @@ defmodule Explorer.Indexer.BlockFetcher do
blocks: blocks, blocks: blocks,
internal_transactions: internal_transactions, internal_transactions: internal_transactions,
logs: logs, logs: logs,
transactions: transactions transactions: transactions_with_receipts
}) })
insert(state, seq, range, %{ insert(state, seq, range, %{
@ -279,8 +279,7 @@ defmodule Explorer.Indexer.BlockFetcher do
blocks: blocks, blocks: blocks,
internal_transactions: internal_transactions, internal_transactions: internal_transactions,
logs: logs, logs: logs,
receipts: receipts, transactions: transactions_with_receipts
transactions: transactions
}) })
else else
{step, {:error, reason}} -> {step, {:error, reason}} ->
@ -294,6 +293,18 @@ defmodule Explorer.Indexer.BlockFetcher do
end end
end end
defp put_receipts(transactions_params, receipts_params)
when is_list(transactions_params) and is_list(receipts_params) do
transaction_hash_to_receipt_params =
Enum.into(receipts_params, %{}, fn %{transaction_hash: transaction_hash} = receipt_params ->
{transaction_hash, receipt_params}
end)
Enum.map(transactions_params, fn %{hash: transaction_hash} = transaction_params ->
Map.merge(transaction_params, Map.fetch!(transaction_hash_to_receipt_params, transaction_hash))
end)
end
defp schedule_next_catchup_index(state) do defp schedule_next_catchup_index(state) do
send(self(), :catchup_index) send(self(), :catchup_index)
state state

@ -3,8 +3,15 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
def change do def change do
create table(:transactions, primary_key: false) do create table(:transactions, primary_key: false) do
# `null` when a pending transaction
add(:cumulative_gas_used, :numeric, precision: 100, null: true)
add(:gas, :numeric, precision: 100, null: false) add(:gas, :numeric, precision: 100, null: false)
add(:gas_price, :numeric, precision: 100, null: false) add(:gas_price, :numeric, precision: 100, null: false)
# `null` when a pending transaction
add(:gas_used, :numeric, precision: 100, null: true)
add(:hash, :bytea, null: false, primary_key: true) add(:hash, :bytea, null: false, primary_key: true)
# `null` when a pending transaction # `null` when a pending transaction
@ -16,6 +23,10 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
add(:r, :numeric, precision: 100, null: false) add(:r, :numeric, precision: 100, null: false)
add(:s, :numeric, precision: 100, null: false) add(:s, :numeric, precision: 100, null: false)
add(:standard_v, :smallint, null: false) add(:standard_v, :smallint, null: false)
# `null` when a pending transaction
add(:status, :integer, null: true)
add(:v, :integer, null: false) add(:v, :integer, null: false)
add(:value, :numeric, precision: 100, null: false) add(:value, :numeric, precision: 100, null: false)
@ -32,8 +43,64 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
create( create(
constraint( constraint(
:transactions, :transactions,
:indexed, :collated_cumalative_gas_used,
check: "(block_hash IS NULL AND index IS NULL) OR (block_hash IS NOT NULL AND index IS NOT NULL)" check: "block_hash IS NULL OR cumulative_gas_used IS NOT NULL"
)
)
create(
constraint(
:transactions,
:collated_gas_used,
check: "block_hash IS NULL OR gas_used IS NOT NULL"
)
)
create(
constraint(
:transactions,
:collated_index,
check: "block_hash IS NULL OR index IS NOT NULL"
)
)
create(
constraint(
:transactions,
:collated_status,
check: "block_hash IS NULL OR status IS NOT NULL"
)
)
create(
constraint(
:transactions,
:pending_cumalative_gas_used,
check: "block_hash IS NOT NULL OR cumulative_gas_used IS NULL"
)
)
create(
constraint(
:transactions,
:pending_gas_used,
check: "block_hash IS NOT NULL OR gas_used IS NULL"
)
)
create(
constraint(
:transactions,
:pending_index,
check: "block_hash IS NOT NULL OR index IS NULL"
)
)
create(
constraint(
:transactions,
:pending_status,
check: "block_hash IS NOT NULL OR status IS NULL"
) )
) )
@ -46,6 +113,8 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
create(index(:transactions, :inserted_at)) create(index(:transactions, :inserted_at))
create(index(:transactions, :updated_at)) create(index(:transactions, :updated_at))
create(index(:transactions, :status))
create(unique_index(:transactions, [:block_hash, :index])) create(unique_index(:transactions, [:block_hash, :index]))
end end
end end

@ -1,24 +0,0 @@
defmodule Explorer.Repo.Migrations.CreateReceipts do
use Ecto.Migration
def change do
create table(:receipts, primary_key: false) do
add(:cumulative_gas_used, :numeric, precision: 100, null: false)
add(:gas_used, :numeric, precision: 100, null: false)
add(:status, :integer, null: false)
add(:transaction_index, :integer, null: false)
timestamps(null: false)
add(
:transaction_hash,
references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea),
null: false
)
end
create(index(:receipts, :status))
create(index(:receipts, :transaction_index))
create(unique_index(:receipts, :transaction_hash))
end
end

@ -17,7 +17,7 @@ defmodule Explorer.Repo.Migrations.CreateLogs do
add( add(
:transaction_hash, :transaction_hash,
references(:receipts, column: :transaction_hash, on_delete: :delete_all, type: :bytea), references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea),
null: false null: false
) )
end end

@ -1,5 +0,0 @@
defmodule Explorer.Chain.Receipt.StatusTest do
use ExUnit.Case, async: true
doctest Explorer.Chain.Receipt.Status
end

@ -1,26 +0,0 @@
defmodule Explorer.Chain.ReceiptTest do
use Explorer.DataCase
alias Explorer.Chain.Receipt
describe "changeset/2" do
test "accepts valid attributes" do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
params = params_for(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
changeset = Receipt.changeset(%Receipt{}, params)
assert changeset.valid?
end
test "rejects missing attributes" do
transaction = insert(:transaction)
params = params_for(:receipt, transaction: transaction, cumulative_gas_used: nil)
changeset = Receipt.changeset(%Receipt{}, params)
refute changeset.valid?
end
end
end

@ -44,11 +44,18 @@ defmodule Explorer.Chain.StatisticsTest do
test "returns the count of transactions from blocks in the last day" do test "returns the count of transactions from blocks in the last day" do
time = DateTime.utc_now() time = DateTime.utc_now()
last_week = Timex.shift(time, days: -8)
block = insert(:block, timestamp: time) block = insert(:block, timestamp: time)
:transaction
|> insert()
|> with_block(block)
last_week = Timex.shift(time, days: -8)
old_block = insert(:block, timestamp: last_week) old_block = insert(:block, timestamp: last_week)
insert(:transaction, block_hash: block.hash, index: 0)
insert(:transaction, block_hash: old_block.hash, index: 0) :transaction
|> insert()
|> with_block(old_block)
assert %Statistics{transaction_count: 1} = Statistics.fetch() assert %Statistics{transaction_count: 1} = Statistics.fetch()
end end
@ -97,8 +104,11 @@ defmodule Explorer.Chain.StatisticsTest do
end end
test "returns the last five transactions with blocks" do test "returns the last five transactions with blocks" do
block = insert(:block) Enum.map(0..5, fn _ ->
Enum.map(0..5, &insert(:transaction, block_hash: block.hash, index: &1)) :transaction
|> insert()
|> with_block()
end)
statistics = Statistics.fetch() statistics = Statistics.fetch()

@ -0,0 +1,5 @@
defmodule Explorer.Chain.Transaction.StatusTest do
use ExUnit.Case, async: true
doctest Explorer.Chain.Transaction.Status
end

@ -4,7 +4,7 @@ defmodule Explorer.ChainTest do
import Explorer.Factory import Explorer.Factory
alias Explorer.{Chain, Repo, Factory} alias Explorer.{Chain, Repo, Factory}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction, Wei} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction, Wei}
doctest Explorer.Chain doctest Explorer.Chain
@ -100,53 +100,6 @@ defmodule Explorer.ChainTest do
} = Chain.address_to_transactions(address) } = Chain.address_to_transactions(address)
end end
test "with transactions with receipt required without receipt does not return transaction" do
address = %Address{hash: to_address_hash} = insert(:address)
block = insert(:block)
%Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} =
insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: to_address_hash)
insert(
:receipt,
transaction_hash: transaction_hash_with_receipt,
transaction_index: transaction_index_with_receipt
)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}],
page_number: 1,
total_entries: 1
} =
Chain.address_to_transactions(
address,
necessity_by_association: %{receipt: :required}
)
assert %Scrivener.Page{
entries: transactions,
page_number: 1,
total_entries: 2
} =
Chain.address_to_transactions(
address,
necessity_by_association: %{receipt: :optional}
)
assert length(transactions) == 2
transaction_by_hash =
Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} ->
{hash, transaction}
end)
assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt]
assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt]
end
test "with transactions can be paginated" do test "with transactions can be paginated" do
adddress = %Address{hash: to_address_hash} = insert(:address) adddress = %Address{hash: to_address_hash} = insert(:address)
transactions = insert_list(2, :transaction, to_address_hash: to_address_hash) transactions = insert_list(2, :transaction, to_address_hash: to_address_hash)
@ -204,8 +157,10 @@ defmodule Explorer.ChainTest do
end end
test "with transactions" do test "with transactions" do
block = insert(:block) %Transaction{block: block, hash: transaction_hash} =
%Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0) :transaction
|> insert()
|> with_block()
assert %Scrivener.Page{ assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}], entries: [%Transaction{hash: ^transaction_hash}],
@ -214,55 +169,15 @@ defmodule Explorer.ChainTest do
} = Chain.block_to_transactions(block) } = Chain.block_to_transactions(block)
end end
test "with transaction with receipt required without receipt does not return transaction" do
block = %Block{hash: block_hash} = insert(:block)
%Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} =
insert(:transaction, block_hash: block_hash, index: 0)
insert(
:receipt,
transaction_hash: transaction_hash_with_receipt,
transaction_index: transaction_index_with_receipt
)
%Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash, index: 1)
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}],
page_number: 1,
total_entries: 1
} =
Chain.block_to_transactions(
block,
necessity_by_association: %{receipt: :required}
)
assert %Scrivener.Page{
entries: transactions,
page_number: 1,
total_entries: 2
} =
Chain.block_to_transactions(
block,
necessity_by_association: %{receipt: :optional}
)
assert length(transactions) == 2
transaction_by_hash =
Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} ->
{hash, transaction}
end)
assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt]
assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt]
end
test "with transactions can be paginated" do test "with transactions can be paginated" do
block = insert(:block) block = insert(:block)
transactions = Enum.map(0..1, &insert(:transaction, block_hash: block.hash, index: &1)) transactions =
Enum.map(0..1, fn _ ->
:transaction
|> insert()
|> with_block(block)
end)
[%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions [%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions
@ -292,8 +207,10 @@ defmodule Explorer.ChainTest do
end end
test "with transactions" do test "with transactions" do
block = insert(:block) %Transaction{block: block} =
insert(:transaction, block_hash: block.hash, index: 0) :transaction
|> insert()
|> with_block()
assert Chain.block_to_transaction_count(block) == 1 assert Chain.block_to_transaction_count(block) == 1
end end
@ -320,17 +237,17 @@ defmodule Explorer.ChainTest do
describe "fee/2" do describe "fee/2" do
test "without receipt with :wei unit" do test "without receipt with :wei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, receipt: nil}, :wei) == assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :wei) ==
{:maximum, Decimal.new(6)} {:maximum, Decimal.new(6)}
end end
test "without receipt with :gwei unit" do test "without receipt with :gwei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, receipt: nil}, :gwei) == assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :gwei) ==
{:maximum, Decimal.new("6e-9")} {:maximum, Decimal.new("6e-9")}
end end
test "without receipt with :ether unit" do test "without receipt with :ether unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, receipt: nil}, :ether) == assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, gas_used: nil}, :ether) ==
{:maximum, Decimal.new("6e-18")} {:maximum, Decimal.new("6e-18")}
end end
@ -339,7 +256,7 @@ defmodule Explorer.ChainTest do
%Transaction{ %Transaction{
gas: Decimal.new(3), gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)}, gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)} gas_used: Decimal.new(2)
}, },
:wei :wei
) == {:actual, Decimal.new(4)} ) == {:actual, Decimal.new(4)}
@ -350,7 +267,7 @@ defmodule Explorer.ChainTest do
%Transaction{ %Transaction{
gas: Decimal.new(3), gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)}, gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)} gas_used: Decimal.new(2)
}, },
:gwei :gwei
) == {:actual, Decimal.new("4e-9")} ) == {:actual, Decimal.new("4e-9")}
@ -361,7 +278,7 @@ defmodule Explorer.ChainTest do
%Transaction{ %Transaction{
gas: Decimal.new(3), gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)}, gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)} gas_used: Decimal.new(2)
}, },
:ether :ether
) == {:actual, Decimal.new("4e-18")} ) == {:actual, Decimal.new("4e-18")}
@ -387,32 +304,30 @@ defmodule Explorer.ChainTest do
end end
describe "hash_to_transaction/2" do describe "hash_to_transaction/2" do
test "with transaction with receipt required without receipt returns {:error, :not_found}" do test "with transaction with block required without block returns {:error, :not_found}" do
block = insert(:block) %Transaction{hash: hash_with_block} =
:transaction
%Transaction{hash: hash_with_receipt, index: index_with_receipt} = |> insert()
insert(:transaction, block_hash: block.hash, index: 0) |> with_block()
insert(:receipt, transaction_hash: hash_with_receipt, transaction_index: index_with_receipt) %Transaction{hash: hash_without_index} = insert(:transaction)
%Transaction{hash: hash_without_receipt} = insert(:transaction) assert {:ok, %Transaction{hash: ^hash_with_block}} =
assert {:ok, %Transaction{hash: ^hash_with_receipt}} =
Chain.hash_to_transaction( Chain.hash_to_transaction(
hash_with_receipt, hash_with_block,
necessity_by_association: %{receipt: :required} necessity_by_association: %{block: :required}
) )
assert {:error, :not_found} = assert {:error, :not_found} =
Chain.hash_to_transaction( Chain.hash_to_transaction(
hash_without_receipt, hash_without_index,
necessity_by_association: %{receipt: :required} necessity_by_association: %{block: :required}
) )
assert {:ok, %Transaction{hash: ^hash_without_receipt}} = assert {:ok, %Transaction{hash: ^hash_without_index}} =
Chain.hash_to_transaction( Chain.hash_to_transaction(
hash_without_receipt, hash_without_index,
necessity_by_association: %{receipt: :optional} necessity_by_association: %{block: :optional}
) )
end end
end end
@ -525,7 +440,7 @@ defmodule Explorer.ChainTest do
) )
end end
test "Returns results in reverse chronological order by block number, transaction index, internal transaction index" do test "returns results in reverse chronological order by block number, transaction index, internal transaction index" do
address = insert(:address) address = insert(:address)
pending_transaction = insert(:transaction) pending_transaction = insert(:transaction)
@ -547,7 +462,11 @@ defmodule Explorer.ChainTest do
) )
a_block = insert(:block, number: 2000) a_block = insert(:block, number: 2000)
first_a_transaction = insert(:transaction, block_hash: a_block.hash, index: 10)
first_a_transaction =
:transaction
|> insert()
|> with_block(a_block)
%InternalTransaction{id: first} = %InternalTransaction{id: first} =
insert( insert(
@ -565,7 +484,10 @@ defmodule Explorer.ChainTest do
index: 1 index: 1
) )
second_a_transaction = insert(:transaction, block_hash: a_block.hash, index: 20) second_a_transaction =
:transaction
|> insert()
|> with_block(a_block)
%InternalTransaction{id: third} = %InternalTransaction{id: third} =
insert( insert(
@ -584,7 +506,11 @@ defmodule Explorer.ChainTest do
) )
b_block = insert(:block, number: 6000) b_block = insert(:block, number: 6000)
first_b_transaction = insert(:transaction, block_hash: b_block.hash, index: 20)
first_b_transaction =
:transaction
|> insert()
|> with_block(b_block)
%InternalTransaction{id: fifth} = %InternalTransaction{id: fifth} =
insert( insert(
@ -611,19 +537,26 @@ defmodule Explorer.ChainTest do
assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result
end end
test "Excludes internal transactions of type `call` when they are alone in the parent transaction" do test "excludes internal transactions of type `call` when they are alone in the parent transaction" do
address = insert(:address) address = insert(:address)
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: address.hash) transaction =
:transaction
|> insert(to_address_hash: address.hash)
|> with_block()
insert(:internal_transaction_call, index: 0, to_address_hash: address.hash, transaction_hash: transaction.hash) insert(:internal_transaction_call, index: 0, to_address_hash: address.hash, transaction_hash: transaction.hash)
assert Enum.empty?(Chain.address_to_internal_transactions(address)) assert Enum.empty?(Chain.address_to_internal_transactions(address))
end end
test "Includes internal transactions of type `create` even when they are alone in the parent transaction" do test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
address = insert(:address) address = insert(:address)
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: address.hash) transaction =
:transaction
|> insert(to_address_hash: address.hash)
|> with_block()
expected = expected =
insert( insert(
@ -698,9 +631,12 @@ defmodule Explorer.ChainTest do
).entries ).entries
end end
test "Excludes internal transactions of type call with no siblings in the transaction" do test "excludes internal transaction of type call with no siblings in the transaction" do
block = insert(:block) %Transaction{hash: hash} =
%Transaction{hash: hash} = insert(:transaction, block_hash: block.hash, index: 0) :transaction
|> insert()
|> with_block()
insert(:internal_transaction_call, transaction_hash: hash, index: 0) insert(:internal_transaction_call, transaction_hash: hash, index: 0)
result = Chain.transaction_hash_to_internal_transactions(hash) result = Chain.transaction_hash_to_internal_transactions(hash)
@ -708,9 +644,12 @@ defmodule Explorer.ChainTest do
assert Enum.empty?(result) assert Enum.empty?(result)
end end
test "Includes internal transactions of type `create` even when they are alone in the parent transaction" do test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
|> insert()
|> with_block()
expected = insert(:internal_transaction_create, index: 0, transaction_hash: transaction.hash) expected = insert(:internal_transaction_create, index: 0, transaction_hash: transaction.hash)
actual = Enum.at(Chain.transaction_hash_to_internal_transactions(transaction.hash), 0) actual = Enum.at(Chain.transaction_hash_to_internal_transactions(transaction.hash), 0)
@ -719,8 +658,11 @@ defmodule Explorer.ChainTest do
end end
test "returns the internal transactions in index order" do test "returns the internal transactions in index order" do
block = insert(:block) %Transaction{hash: hash} =
%Transaction{hash: hash} = insert(:transaction, block_hash: block.hash, index: 0) :transaction
|> insert()
|> with_block()
%InternalTransaction{id: first_id} = insert(:internal_transaction, transaction_hash: hash, index: 0) %InternalTransaction{id: first_id} = insert(:internal_transaction, transaction_hash: hash, index: 0)
%InternalTransaction{id: second_id} = insert(:internal_transaction, transaction_hash: hash, index: 1) %InternalTransaction{id: second_id} = insert(:internal_transaction, transaction_hash: hash, index: 1)
@ -746,9 +688,11 @@ defmodule Explorer.ChainTest do
end end
test "with logs" do test "with logs" do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
%Log{id: id} = insert(:log, transaction_hash: transaction.hash) %Log{id: id} = insert(:log, transaction_hash: transaction.hash)
assert %Scrivener.Page{ assert %Scrivener.Page{
@ -760,9 +704,11 @@ defmodule Explorer.ChainTest do
end end
test "with logs can be paginated" do test "with logs can be paginated" do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
logs = Enum.map(0..1, &insert(:log, index: &1, transaction_hash: transaction.hash)) logs = Enum.map(0..1, &insert(:log, index: &1, transaction_hash: transaction.hash))
[%Log{id: first_log_id}, %Log{id: second_log_id}] = logs [%Log{id: first_log_id}, %Log{id: second_log_id}] = logs
@ -785,16 +731,17 @@ defmodule Explorer.ChainTest do
end end
test "with logs necessity_by_association loads associations" do test "with logs necessity_by_association loads associations" do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
insert(:log, transaction_hash: transaction.hash) insert(:log, transaction_hash: transaction.hash)
assert %Scrivener.Page{ assert %Scrivener.Page{
entries: [ entries: [
%Log{ %Log{
address: %Address{}, address: %Address{},
receipt: %Receipt{},
transaction: %Transaction{} transaction: %Transaction{}
} }
], ],
@ -806,7 +753,6 @@ defmodule Explorer.ChainTest do
transaction, transaction,
necessity_by_association: %{ necessity_by_association: %{
address: :optional, address: :optional,
receipt: :optional,
transaction: :optional transaction: :optional
} }
) )
@ -815,7 +761,6 @@ defmodule Explorer.ChainTest do
entries: [ entries: [
%Log{ %Log{
address: %Ecto.Association.NotLoaded{}, address: %Ecto.Association.NotLoaded{},
receipt: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{} transaction: %Ecto.Association.NotLoaded{}
} }
], ],
@ -895,21 +840,13 @@ defmodule Explorer.ChainTest do
end end
test "with block containing transactions", %{block: block, block_reward: block_reward} do test "with block containing transactions", %{block: block, block_reward: block_reward} do
insert( :transaction
:transaction, |> insert(gas_price: 1)
block: block, |> with_block(block, gas_used: 1)
index: 0,
gas_price: 1,
receipt: build(:receipt, gas_used: 1, transaction_index: 0)
)
insert( :transaction
:transaction, |> insert(gas_price: 1)
block: block, |> with_block(block, gas_used: 2)
index: 1,
gas_price: 1,
receipt: build(:receipt, gas_used: 2, transaction_index: 1)
)
expected = expected =
block_reward.reward block_reward.reward

@ -4,7 +4,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
import ExUnit.CaptureLog import ExUnit.CaptureLog
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction}
alias Explorer.Indexer.{BlockFetcher, Sequence} alias Explorer.Indexer.{BlockFetcher, Sequence}
@tag capture_log: true @tag capture_log: true
@ -48,10 +48,13 @@ defmodule Explorer.Indexer.BlockFetcherTest do
setup do setup do
block = insert(:block) block = insert(:block)
Enum.map(0..2, fn index -> Enum.map(0..2, fn _ ->
transaction = insert(:transaction, block_hash: block.hash, index: index) transaction =
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) :transaction
insert(:log, transaction_hash: receipt.transaction_hash) |> insert()
|> with_block(block)
insert(:log, transaction_hash: transaction.hash)
insert(:internal_transaction, transaction_hash: transaction.hash, index: 0) insert(:internal_transaction, transaction_hash: transaction.hash, index: 0)
end) end)
@ -72,7 +75,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
assert log =~ "blocks: 4" assert log =~ "blocks: 4"
assert log =~ "internal transactions: 3" assert log =~ "internal transactions: 3"
assert log =~ "receipts: 6"
assert log =~ "logs: 3" assert log =~ "logs: 3"
assert log =~ "addresses: 31" assert log =~ "addresses: 31"
end end
@ -110,7 +112,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
], ],
internal_transactions: [], internal_transactions: [],
logs: [], logs: [],
receipts: [],
transactions: [] transactions: []
}} = BlockFetcher.import_range({0, 0}, state, sequence) }} = BlockFetcher.import_range({0, 0}, state, sequence)
@ -165,14 +166,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
} }
} }
], ],
receipts: [
%Explorer.Chain.Hash{
byte_count: 32,
bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
}
],
transactions: [ transactions: [
%Explorer.Chain.Hash{ %Explorer.Chain.Hash{
byte_count: 32, byte_count: 32,
@ -187,7 +180,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
assert Repo.aggregate(Address, :count, :hash) == 2 assert Repo.aggregate(Address, :count, :hash) == 2
assert Repo.aggregate(InternalTransaction, :count, :id) == 1 assert Repo.aggregate(InternalTransaction, :count, :id) == 1
assert Repo.aggregate(Log, :count, :id) == 1 assert Repo.aggregate(Log, :count, :id) == 1
assert Repo.aggregate(Receipt, :count, :transaction_hash) == 1
assert Repo.aggregate(Transaction, :count, :hash) == 1 assert Repo.aggregate(Transaction, :count, :hash) == 1
end end
end end

@ -15,7 +15,6 @@ defmodule Explorer.Factory do
Hash, Hash,
InternalTransaction, InternalTransaction,
Log, Log,
Receipt,
Transaction Transaction
} }
@ -64,6 +63,48 @@ defmodule Explorer.Factory do
block_hash block_hash
end end
def with_block(%Transaction{index: nil} = transaction) do
with_block(transaction, insert(:block))
end
def with_block(transactions) when is_list(transactions) do
block = insert(:block)
with_block(transactions, block)
end
def with_block(%Transaction{} = transaction, %Block{} = block) do
with_block(transaction, block, [])
end
def with_block(transactions, %Block{} = block) when is_list(transactions) do
Enum.map(transactions, &with_block(&1, block))
end
def with_block(%Transaction{index: nil} = transaction, collated_params) when is_list(collated_params) do
block = insert(:block)
with_block(transaction, block, collated_params)
end
def with_block(%Transaction{index: nil} = transaction, %Block{hash: block_hash}, collated_params)
when is_list(collated_params) do
next_transaction_index = block_hash_to_next_transaction_index(block_hash)
cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000)
gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000)
status = collated_params[:status] || Enum.random(0..1)
transaction
|> Transaction.changeset(%{
block_hash: block_hash,
cumulative_gas_used: cumulative_gas_used,
gas_used: gas_used,
index: next_transaction_index,
status: status
})
|> Repo.update!()
|> Repo.preload(:block)
end
def data(sequence_name) do def data(sequence_name) do
unpadded = unpadded =
sequence_name sequence_name
@ -149,14 +190,6 @@ defmodule Explorer.Factory do
} }
end end
def receipt_factory do
%Receipt{
cumulative_gas_used: Enum.random(21_000..100_000),
gas_used: Enum.random(21_000..100_000),
status: Enum.random(0..1)
}
end
def transaction_factory do def transaction_factory do
%Transaction{ %Transaction{
from_address_hash: insert(:address).hash, from_address_hash: insert(:address).hash,
@ -188,43 +221,6 @@ defmodule Explorer.Factory do
data(:transaction_input) data(:transaction_input)
end end
@doc """
Validates the pending `transaction`(s) by add it to a `t:Explorer.Chain.Block.t/0` and giving it a `receipt`
"""
def validate(transactions) when is_list(transactions) do
Enum.map(transactions, &validate/1)
end
def validate(%Transaction{hash: hash} = transaction) do
block = insert(:block)
index = 0
block_transaction =
transaction
|> Explorer.Chain.Transaction.changeset(%{block_hash: block.hash, index: index})
|> Repo.update!()
insert(:receipt, transaction_hash: hash, transaction_index: index)
Repo.preload(block_transaction, [:block, :receipt])
end
def with_block(%Transaction{index: nil} = transaction, %Block{hash: block_hash}) do
next_transaction_index = block_hash_to_next_transaction_index(block_hash)
transaction
|> Transaction.changeset(%{block_hash: block_hash, index: next_transaction_index})
|> Repo.update!()
|> Repo.preload(:block)
end
def with_receipt(%Transaction{hash: hash, index: index} = transaction) do
insert(:receipt, transaction_hash: hash, transaction_index: index)
Repo.preload(transaction, :receipt)
end
defmacrop left + right do defmacrop left + right do
quote do quote do
fragment("? + ?", unquote(left), unquote(right)) fragment("? + ?", unquote(left), unquote(right))
@ -253,9 +249,10 @@ defmodule Explorer.Factory do
gas = Enum.random(21_000..100_000) gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas) gas_used = Enum.random(0..gas)
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
%InternalTransaction{ %InternalTransaction{
from_address_hash: insert(:address).hash, from_address_hash: insert(:address).hash,
@ -266,7 +263,7 @@ defmodule Explorer.Factory do
output: %Data{bytes: <<1>>}, output: %Data{bytes: <<1>>},
# caller MUST suppy `index` # caller MUST suppy `index`
trace_address: [], trace_address: [],
transaction_hash: receipt.transaction_hash, transaction_hash: transaction.hash,
type: type, type: type,
value: sequence("internal_transaction_value", &Decimal.new(&1)) value: sequence("internal_transaction_value", &Decimal.new(&1))
} }
@ -276,9 +273,10 @@ defmodule Explorer.Factory do
gas = Enum.random(21_000..100_000) gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas) gas_used = Enum.random(0..gas)
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
%InternalTransaction{ %InternalTransaction{
created_contract_code: data(:internal_transaction_created_contract_code), created_contract_code: data(:internal_transaction_created_contract_code),
@ -289,7 +287,7 @@ defmodule Explorer.Factory do
# caller MUST suppy `index` # caller MUST suppy `index`
init: data(:internal_transaction_init), init: data(:internal_transaction_init),
trace_address: [], trace_address: [],
transaction_hash: receipt.transaction_hash, transaction_hash: transaction.hash,
type: type, type: type,
value: sequence("internal_transaction_value", &Decimal.new(&1)) value: sequence("internal_transaction_value", &Decimal.new(&1))
} }

@ -17,8 +17,7 @@ defmodule ExplorerWeb.AddressTransactionController do
necessity_by_association: %{ necessity_by_association: %{
block: :required, block: :required,
from_address: :optional, from_address: :optional,
to_address: :optional, to_address: :optional
receipt: :required
}, },
pagination: params pagination: params
] ]

@ -15,8 +15,7 @@ defmodule ExplorerWeb.BlockTransactionController do
necessity_by_association: %{ necessity_by_association: %{
block: :required, block: :required,
from_address: :required, from_address: :required,
to_address: :required, to_address: :required
receipt: :required
}, },
pagination: params pagination: params
) )

@ -23,8 +23,7 @@ defmodule ExplorerWeb.TransactionController do
necessity_by_association: %{ necessity_by_association: %{
block: :required, block: :required,
from_address: :optional, from_address: :optional,
to_address: :optional, to_address: :optional
receipt: :required
} }
], ],
options options

@ -12,8 +12,7 @@ defmodule ExplorerWeb.TransactionInternalTransactionController do
necessity_by_association: %{ necessity_by_association: %{
block: :optional, block: :optional,
from_address: :optional, from_address: :optional,
to_address: :optional, to_address: :optional
receipt: :optional
} }
) do ) do
page = page =

@ -12,7 +12,6 @@ defmodule ExplorerWeb.TransactionLogController do
necessity_by_association: %{ necessity_by_association: %{
block: :optional, block: :optional,
from_address: :required, from_address: :required,
receipt: :optional,
to_address: :required to_address: :required
} }
) do ) do

@ -3,7 +3,7 @@ defmodule ExplorerWeb.TransactionView do
alias Cldr.Number alias Cldr.Number
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{InternalTransaction, Receipt, Transaction, Wei} alias Explorer.Chain.{InternalTransaction, Transaction, Wei}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias ExplorerWeb.BlockView alias ExplorerWeb.BlockView
alias ExplorerWeb.ExchangeRates.USD alias ExplorerWeb.ExchangeRates.USD
@ -15,9 +15,9 @@ defmodule ExplorerWeb.TransactionView do
end end
end end
def gas_used(%Transaction{receipt: nil}), do: gettext("Pending") def gas_used(%Transaction{gas_used: nil}), do: gettext("Pending")
def gas_used(%Transaction{receipt: %Receipt{gas_used: gas_used}}) do def gas_used(%Transaction{gas_used: gas_used}) do
Number.to_string!(gas_used) Number.to_string!(gas_used)
end end

@ -23,8 +23,11 @@ defmodule ExplorerWeb.AddressInternalTransactionControllerTest do
test "returns internal transactions for the address", %{conn: conn} do test "returns internal transactions for the address", %{conn: conn} do
address = insert(:address) address = insert(:address)
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0) transaction =
:transaction
|> insert()
|> with_block()
from_internal_transaction = from_internal_transaction =
insert(:internal_transaction, transaction_hash: transaction.hash, from_address_hash: address.hash, index: 1) insert(:internal_transaction, transaction_hash: transaction.hash, from_address_hash: address.hash, index: 1)

@ -25,13 +25,13 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
from_transaction = from_transaction =
:transaction :transaction
|> insert(block_hash: block.hash, from_address_hash: address.hash, index: 0) |> insert(from_address_hash: address.hash)
|> with_receipt() |> with_block(block)
to_transaction = to_transaction =
:transaction :transaction
|> insert(block_hash: block.hash, to_address_hash: address.hash, index: 1) |> insert(to_address_hash: address.hash)
|> with_receipt() |> with_block(block)
conn = get(conn, address_transaction_path(conn, :index, :en, address)) conn = get(conn, address_transaction_path(conn, :index, :en, address))
@ -44,17 +44,10 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
assert Enum.member?(actual_transaction_hashes, to_transaction.hash) assert Enum.member?(actual_transaction_hashes, to_transaction.hash)
end end
test "does not return related transactions without a receipt", %{conn: conn} do test "does not return related transactions without a block", %{conn: conn} do
address = insert(:address) address = insert(:address)
block = insert(:block)
insert( insert(:transaction, from_address_hash: address.hash, to_address_hash: address.hash)
:transaction,
block_hash: block.hash,
from_address_hash: address.hash,
index: 0,
to_address_hash: address.hash
)
conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address)) conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))

@ -44,13 +44,9 @@ defmodule ExplorerWeb.API.RPC.BlockControllerTest do
%{block_range: range} = block_reward = insert(:block_reward) %{block_range: range} = block_reward = insert(:block_reward)
block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) block = insert(:block, number: Enum.random(Range.new(range.from, range.to)))
insert( :transaction
:transaction, |> insert(gas_price: 1)
block: block, |> with_block(block, gas_used: 1)
index: 0,
gas_price: 1,
receipt: build(:receipt, gas_used: 1, transaction_index: 0)
)
expected_reward = expected_reward =
block_reward.reward block_reward.reward

@ -1,8 +1,6 @@
defmodule ExplorerWeb.BlockControllerTest do defmodule ExplorerWeb.BlockControllerTest do
use ExplorerWeb.ConnCase use ExplorerWeb.ConnCase
alias Explorer.Chain.Block
@locale "en" @locale "en"
describe "GET show/2" do describe "GET show/2" do
@ -27,9 +25,11 @@ defmodule ExplorerWeb.BlockControllerTest do
end end
test "returns a block with two transactions", %{conn: conn} do test "returns a block with two transactions", %{conn: conn} do
%Block{hash: hash} = insert(:block) block = insert(:block)
Enum.map(0..1, fn index -> insert(:transaction, block_hash: hash, index: index) end) 2
|> insert_list(:transaction)
|> with_block(block)
conn = get(conn, block_path(conn, :index, @locale)) conn = get(conn, block_path(conn, :index, @locale))

@ -18,8 +18,10 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
test "returns transactions for the block", %{conn: conn} do test "returns transactions for the block", %{conn: conn} do
block = insert(:block) block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) :transaction
|> insert()
|> with_block(block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number))
@ -37,9 +39,9 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
assert Enum.empty?(conn.assigns.page) assert Enum.empty?(conn.assigns.page)
end end
test "does not return related transactions without a receipt", %{conn: conn} do test "does not return related transactions without a block", %{conn: conn} do
block = insert(:block) block = insert(:block)
insert(:transaction, block_hash: block.hash, index: 0) insert(:transaction)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block)) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block))
@ -49,8 +51,10 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
test "does not return related transactions without a to address", %{conn: conn} do test "does not return related transactions without a to address", %{conn: conn} do
block = insert(:block) block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: nil)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) :transaction
|> insert(to_address_hash: nil)
|> with_block(block)
conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block)) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block))

@ -34,9 +34,11 @@ defmodule ExplorerWeb.ChainControllerTest do
end end
test "only returns transactions with an associated block", %{conn: conn} do test "only returns transactions with an associated block", %{conn: conn} do
block = insert(:block, number: 33) associated =
:transaction
|> insert()
|> with_block()
associated = insert(:transaction, block_hash: block.hash, index: 0)
unassociated = insert(:transaction) unassociated = insert(:transaction)
conn = get(conn, "/en") conn = get(conn, "/en")
@ -48,9 +50,10 @@ defmodule ExplorerWeb.ChainControllerTest do
end end
test "returns a transaction", %{conn: conn} do test "returns a transaction", %{conn: conn} do
block = insert(:block, number: 33) transaction =
:transaction
transaction = insert(:transaction, block_hash: block.hash, index: 0) |> insert()
|> with_block()
conn = get(conn, "/en") conn = get(conn, "/en")
@ -77,8 +80,11 @@ defmodule ExplorerWeb.ChainControllerTest do
end end
test "finds a transaction by hash", %{conn: conn} do test "finds a transaction by hash", %{conn: conn} do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
|> insert()
|> with_block()
conn = get(conn, "/en/search?q=#{to_string(transaction.hash)}") conn = get(conn, "/en/search?q=#{to_string(transaction.hash)}")
assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction) assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction)

@ -5,8 +5,9 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
describe "GET index/2" do describe "GET index/2" do
test "returns no transactions that are in a block", %{conn: conn} do test "returns no transactions that are in a block", %{conn: conn} do
block = insert(:block) :transaction
insert(:transaction, block_hash: block.hash, index: 0) |> insert()
|> with_block()
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))
@ -14,10 +15,10 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do
assert Enum.empty?(conn.assigns.transactions) assert Enum.empty?(conn.assigns.transactions)
end end
test "does not count transactions that have a receipt", %{conn: conn} do test "does not count transactions that have a block", %{conn: conn} do
block = insert(:block) :transaction
transaction = insert(:transaction, block_hash: block.hash, index: 0) |> insert()
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> with_block()
conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en))

@ -8,7 +8,7 @@ defmodule ExplorerWeb.TransactionControllerTest do
transaction = transaction =
:transaction :transaction
|> insert() |> insert()
|> validate() |> with_block()
conn = get(conn, "/en/transactions") conn = get(conn, "/en/transactions")
@ -16,9 +16,9 @@ defmodule ExplorerWeb.TransactionControllerTest do
end end
test "returns a count of transactions", %{conn: conn} do test "returns a count of transactions", %{conn: conn} do
block = insert(:block) :transaction
transaction = insert(:transaction, block_hash: block.hash, index: 0) |> insert()
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> with_block()
conn = get(conn, "/en/transactions") conn = get(conn, "/en/transactions")
@ -42,9 +42,10 @@ defmodule ExplorerWeb.TransactionControllerTest do
end end
test "paginates transactions using the last seen transaction", %{conn: conn} do test "paginates transactions using the last seen transaction", %{conn: conn} do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
conn = conn =
get( get(

@ -23,16 +23,19 @@ defmodule ExplorerWeb.TransactionLogControllerTest do
end end
test "returns logs for the transaction", %{conn: conn} do test "returns logs for the transaction", %{conn: conn} do
block = insert(:block) transaction =
transaction = insert(:transaction, block_hash: block.hash, index: 0) :transaction
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) |> insert()
|> with_block()
address = insert(:address) address = insert(:address)
insert(:log, address_hash: address.hash, transaction_hash: transaction.hash) insert(:log, address_hash: address.hash, transaction_hash: transaction.hash)
conn = get(conn, transaction_log_path(conn, :index, :en, transaction)) conn = get(conn, transaction_log_path(conn, :index, :en, transaction))
first_log = List.first(conn.assigns.logs.entries) first_log = List.first(conn.assigns.logs.entries)
assert first_log.transaction_hash == receipt.transaction_hash
assert first_log.transaction_hash == transaction.hash
end end
test "assigns no logs when there are none", %{conn: conn} do test "assigns no logs when there are none", %{conn: conn} do

@ -11,14 +11,13 @@ defmodule ExplorerWeb.ViewingAddressesTest do
from_taft = from_taft =
:transaction :transaction
|> insert(block_hash: block.hash, from_address_hash: taft.hash, index: 0, to_address_hash: lincoln.hash) |> insert(from_address_hash: taft.hash, to_address_hash: lincoln.hash)
|> with_receipt() |> with_block(block)
from_lincoln = from_lincoln =
:transaction :transaction
|> insert(from_address_hash: lincoln.hash, to_address_hash: taft.hash) |> insert(from_address_hash: lincoln.hash, to_address_hash: taft.hash)
|> with_block(block) |> with_block(block)
|> with_receipt()
{:ok, {:ok,
%{ %{

@ -14,20 +14,21 @@ defmodule ExplorerWeb.ViewingTransactionsTest do
gas_used: 123_987 gas_used: 123_987
}) })
for index <- 0..3, do: insert(:transaction, block_hash: block.hash, index: index) 4
|> insert_list(:transaction)
|> with_block()
pending = insert(:transaction, block_hash: nil, gas: 5891, index: nil) pending = insert(:transaction, block_hash: nil, gas: 5891, index: nil)
lincoln = insert(:address) lincoln = insert(:address)
taft = insert(:address) taft = insert(:address)
transaction = transaction =
insert( :transaction
:transaction, |> insert(
block_hash: block.hash,
value: Wei.from(Decimal.new(5656), :ether), value: Wei.from(Decimal.new(5656), :ether),
gas: Decimal.new(1_230_000_000_000_123_123), gas: Decimal.new(1_230_000_000_000_123_123),
gas_price: Decimal.new(7_890_000_000_898_912_300_045), gas_price: Decimal.new(7_890_000_000_898_912_300_045),
index: 4,
input: "0x000012", input: "0x000012",
nonce: 99045, nonce: 99045,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"), inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
@ -35,24 +36,17 @@ defmodule ExplorerWeb.ViewingTransactionsTest do
from_address_hash: taft.hash, from_address_hash: taft.hash,
to_address_hash: lincoln.hash to_address_hash: lincoln.hash
) )
|> with_block(block, gas_used: Decimal.new(1_230_000_000_000_123_000), status: :ok)
receipt = insert(:receipt, status: :ok, transaction_hash: transaction.hash, transaction_index: transaction.index) insert(:log, address_hash: lincoln.hash, index: 0, transaction_hash: transaction.hash)
insert(:log, address_hash: lincoln.hash, index: 0, transaction_hash: receipt.transaction_hash)
# From Lincoln to Taft. # From Lincoln to Taft.
txn_from_lincoln = txn_from_lincoln =
insert( :transaction
:transaction, |> insert(from_address_hash: lincoln.hash, to_address_hash: taft.hash)
block_hash: block.hash, |> with_block(block)
index: 5,
from_address_hash: lincoln.hash,
to_address_hash: taft.hash
)
internal_receipt =
insert(:receipt, transaction_hash: txn_from_lincoln.hash, transaction_index: txn_from_lincoln.index)
internal = insert(:internal_transaction, index: 0, transaction_hash: internal_receipt.transaction_hash) internal = insert(:internal_transaction, index: 0, transaction_hash: transaction.hash)
{:ok, {:ok,
%{ %{

@ -1,7 +1,7 @@
defmodule ExplorerWeb.TransactionViewTest do defmodule ExplorerWeb.TransactionViewTest do
use ExplorerWeb.ConnCase, async: true use ExplorerWeb.ConnCase, async: true
alias Explorer.Chain.{Transaction, Wei} alias Explorer.Chain.Wei
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.Repo alias Explorer.Repo
alias ExplorerWeb.TransactionView alias ExplorerWeb.TransactionView
@ -13,9 +13,9 @@ defmodule ExplorerWeb.TransactionViewTest do
transaction = transaction =
build( build(
:transaction, :transaction,
gas_price: gas_price,
gas: Decimal.new(3_000_000), gas: Decimal.new(3_000_000),
receipt: nil gas_price: gas_price,
gas_used: nil
) )
token = %Token{usd_value: Decimal.new(0.50)} token = %Token{usd_value: Decimal.new(0.50)}
@ -27,8 +27,7 @@ defmodule ExplorerWeb.TransactionViewTest do
test "with fee and exchange_rate" do test "with fee and exchange_rate" do
{:ok, gas_price} = Wei.cast(3_000_000_000) {:ok, gas_price} = Wei.cast(3_000_000_000)
receipt = build(:receipt, gas_used: Decimal.new(1_034_234.0)) transaction = build(:transaction, gas_price: gas_price, gas_used: Decimal.new(1_034_234.0))
transaction = build(:transaction, gas_price: gas_price, receipt: receipt)
token = %Token{usd_value: Decimal.new(0.50)} token = %Token{usd_value: Decimal.new(0.50)}
expected_value = "0.003102702 POA" expected_value = "0.003102702 POA"
@ -41,53 +40,45 @@ defmodule ExplorerWeb.TransactionViewTest do
end end
describe "formatted_status/1" do describe "formatted_status/1" do
test "without receipt" do test "without block" do
transaction = transaction =
:transaction :transaction
|> insert() |> insert()
|> Repo.preload(:receipt) |> Repo.preload(:block)
assert TransactionView.formatted_status(transaction) == "Pending" assert TransactionView.formatted_status(transaction) == "Pending"
end end
test "with receipt with status :error with gas_used < gas" do test "with block with status :error with gas_used < gas" do
gas = 2 gas = 2
block = insert(:block) block = insert(:block)
%Transaction{hash: hash, index: index} = insert(:transaction, block_hash: block.hash, gas: gas, index: 0)
insert(:receipt, gas_used: gas - 1, status: :error, transaction_hash: hash, transaction_index: index)
transaction = transaction =
Transaction :transaction
|> Repo.get!(hash) |> insert(gas: gas)
|> Repo.preload(:receipt) |> with_block(block, gas_used: gas - 1, status: :error)
assert TransactionView.formatted_status(transaction) == "Failed" assert TransactionView.formatted_status(transaction) == "Failed"
end end
test "with receipt with status :error with gas <= gas_used" do test "with block with status :error with gas <= gas_used" do
gas = 2 gas = 2
block = insert(:block)
%Transaction{hash: hash, index: index} = insert(:transaction, block_hash: block.hash, gas: gas, index: 0)
insert(:receipt, gas_used: gas, status: 0, transaction_hash: hash, transaction_index: index)
transaction = transaction =
Transaction :transaction
|> Repo.get!(hash) |> insert(gas: gas)
|> Repo.preload(:receipt) |> with_block(gas_used: gas, status: :error)
assert TransactionView.formatted_status(transaction) == "Out of Gas" assert TransactionView.formatted_status(transaction) == "Out of Gas"
end end
test "with receipt with status :ok" do test "with receipt with status :ok" do
gas = 2 gas = 2
block = insert(:block)
%Transaction{hash: hash, index: index} = insert(:transaction, block_hash: block.hash, gas: gas, index: 0)
insert(:receipt, gas_used: gas - 1, status: :ok, transaction_hash: hash, transaction_index: index)
transaction = transaction =
Transaction :transaction
|> Repo.get!(hash) |> insert(gas: gas)
|> Repo.preload(:receipt) |> with_block(gas_used: gas - 1, status: :ok)
assert TransactionView.formatted_status(transaction) == "Success" assert TransactionView.formatted_status(transaction) == "Success"
end end

@ -1,7 +1,7 @@
{ {
"coverage_options": { "coverage_options": {
"treat_no_relevant_lines_as_covered": true, "treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 92.3 "minimum_coverage": 92.6
}, },
"terminal_options": { "terminal_options": {
"file_column_width": 120 "file_column_width": 120

Loading…
Cancel
Save