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. 8
      apps/explorer/benchmarks/explorer/chain/recent_collated_transactions.exs
  4. 164
      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. 267
      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]
alias Explorer.Chain.Receipt.Status
alias Explorer.Chain.Transaction.Status
alias EthereumJSONRPC
alias EthereumJSONRPC.Logs

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

@ -24,7 +24,6 @@ defmodule Explorer.Chain do
Hash,
InternalTransaction,
Log,
Receipt,
Transaction,
Wei
}
@ -160,7 +159,6 @@ defmodule Explorer.Chain do
@insert_blocks_timeout 60_000
@insert_internal_transactions_timeout 60_000
@insert_logs_timeout 60_000
@insert_receipts_timeout 60_000
@insert_transactions_timeout 60_000
@doc """
@ -266,14 +264,13 @@ defmodule Explorer.Chain do
from(
block in Block,
left_join: transaction in assoc(block, :transactions),
left_join: receipt in assoc(transaction, :receipt),
inner_join: block_reward in Reward,
on: fragment("? <@ ?", block.number, block_reward.block_range),
where: block.number == ^block_number,
group_by: block_reward.reward,
select: %{
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
}
@ -406,28 +403,28 @@ defmodule Explorer.Chain do
...> %Explorer.Chain.Transaction{
...> gas: Decimal.new(3),
...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)},
...> receipt: nil
...> gas_used: nil
...> },
...> :wei
...> )
{: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`
in the `receipt`.
in the `transaction`.
iex> Explorer.Chain.fee(
...> %Explorer.Chain.Transaction{
...> gas: Decimal.new(3),
...> gas_price: %Explorer.Chain.Wei{value: Decimal.new(2)},
...> receipt: %Explorer.Chain.Receipt{gas_used: Decimal.new(2)}
...> gas_used: Decimal.new(2)
...> },
...> :wei
...> )
{:actual, Decimal.new(4)}
"""
@spec fee(%Transaction{receipt: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()}
def fee(%Transaction{gas: gas, gas_price: gas_price, receipt: nil}, unit) do
@spec fee(%Transaction{gas_used: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()}
def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do
fee =
gas_price
|> Wei.to(unit)
@ -436,8 +433,8 @@ defmodule Explorer.Chain do
{:maximum, fee}
end
@spec fee(%Transaction{receipt: Receipt.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()}
def fee(%Transaction{gas_price: gas_price, receipt: %Receipt{gas_used: gas_used}}, unit) do
@spec fee(%Transaction{gas_used: Decimal.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()}
def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do
fee =
gas_price
|> 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` |
| `: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` |
| `: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` |
iex> Explorer.Chain.import_blocks(
@ -635,21 +631,14 @@ defmodule Explorer.Chain do
...> type: "mined"
...> }
...> ],
...> receipts: [
...> %{
...> cumulative_gas_used: 50450,
...> gas_used: 50450,
...> status: :ok,
...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> transaction_index: 0
...> }
...> ],
...> transactions: [
...> %{
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> cumulative_gas_used: 50450,
...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> gas: 4700000,
...> gas_price: 100000000000,
...> gas_used: 50450,
...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> index: 0,
...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
@ -658,6 +647,7 @@ defmodule Explorer.Chain do
...> r: 0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01,
...> s: 0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f,
...> standard_v: 1,
...> status: :ok,
...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> v: 0xbe,
...> 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: [
%Explorer.Chain.Hash{
byte_count: 32,
@ -738,7 +720,6 @@ defmodule Explorer.Chain do
...> blocks: [],
...> logs: [],
...> internal_transactions: [],
...> receipts: [],
...> transactions: [],
...> addresses: []
...> }
@ -749,7 +730,6 @@ defmodule Explorer.Chain do
blocks: [],
internal_transactions: [],
logs: [],
receipts: [],
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
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: [
...> %{
@ -815,20 +795,14 @@ defmodule Explorer.Chain do
...> type: "mined"
...> }
...> ],
...> receipts: [
...> %{
...> cumulative_gas_used: 50450,
...> gas_used: 50450,
...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> transaction_index: 0
...> }
...> ],
...> transactions: [
...> %{
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> cumulative_gas_used: 50450,
...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
...> gas: 4700000,
...> gas_price: 100000000000,
...> gas_used: 50450,
...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
...> index: 0,
...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
@ -851,16 +825,17 @@ defmodule Explorer.Chain do
...> )
iex> internal_transaction_changeset.errors
[call_type: {"can't be blank", [validation: :required]}]
iex> receipt_changeset.errors
[status: {"can't be blank", [validation: :required]}]
iex> transaction_changeset.errors
[
status: {"can't be blank when the transaction is collated into a block", []}
]
## Tree
* `t:Explorer.Chain.Block.t/0`s
* `t:Explorer.Chain.Transaction.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
@ -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}` 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
types. Defaults to `#{@insert_transactions_timeout}` milliseconds.
"""
@ -883,21 +856,18 @@ defmodule Explorer.Chain do
blocks: blocks_params,
logs: logs_params,
internal_transactions: internal_transactions_params,
receipts: receipts_params,
transactions: transactions_params,
addresses: addresses_params
},
options \\ []
)
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(options) do
is_list(transactions_params) and is_list(addresses_params) and is_list(options) do
with {:ok, ecto_schema_module_to_changes_list} <-
ecto_schema_module_to_params_list_to_ecto_schema_module_to_changes_list(%{
Block => blocks_params,
Log => logs_params,
InternalTransaction => internal_transactions_params,
Receipt => receipts_params,
Transaction => transactions_params,
Address => addresses_params
}) do
@ -1030,10 +1000,8 @@ defmodule Explorer.Chain do
@doc """
The number of `t:Explorer.Chain.Log.t/0`.
iex> block = insert(:block)
iex> transaction = insert(:transaction, block_hash: block.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> transaction = :transaction |> insert() |> with_block()
iex> insert(:log, transaction_hash: transaction.hash, index: 0)
iex> Explorer.Chain.log_count()
1
@ -1143,31 +1111,12 @@ defmodule Explorer.Chain do
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 """
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> 8 |> insert_list(:transaction) |> validate()
iex> 8 |> insert_list(:transaction) |> with_block()
iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions()
iex> length(recent_collated_transactions)
10
@ -1181,10 +1130,10 @@ defmodule Explorer.Chain do
returned. This can be used to generate paging for collated transaction.
iex> first_block = insert(:block, number: 1)
iex> first_transaction_in_first_block = insert(:transaction, block_hash: first_block.hash, index: 0)
iex> second_transaction_in_first_block = insert(:transaction, block_hash: first_block.hash, index: 1)
iex> first_transaction_in_first_block = :transaction |> insert() |> with_block(first_block)
iex> second_transaction_in_first_block = :transaction |> insert() |> with_block(first_block)
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(
...> 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).
iex> 2 |> insert_list(:transaction)
iex> :transaction |> insert() |> validate()
iex> :transaction |> insert() |> with_block()
iex> 8 |> insert_list(:transaction)
iex> %Scrivener.Page{entries: recent_pending_transactions} = Explorer.Chain.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
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> :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(
...> 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.
iex> insert(:transaction)
iex> :transaction |> insert() |> validate()
iex> :transaction |> insert() |> with_block()
iex> Explorer.Chain.transaction_count()
2
iex> Explorer.Chain.transaction_count(pending: nil)
@ -1408,14 +1357,14 @@ defmodule Explorer.Chain do
To count only collated transactions, pass `pending: false`.
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)
3
To count only pending transactions, pass `pending: true`.
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)
2
@ -1486,7 +1435,7 @@ defmodule Explorer.Chain do
end
@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
@ -1497,18 +1446,14 @@ defmodule Explorer.Chain do
"""
@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{receipt: %Receipt{status: :ok}}), do: :success
def transaction_to_status(%Transaction{
gas: gas,
receipt: %Receipt{gas_used: gas_used, status: :error}
})
when gas_used >= gas do
def transaction_to_status(%Transaction{status: nil}), do: :pending
def transaction_to_status(%Transaction{status: :ok}), do: :success
def transaction_to_status(%Transaction{gas: gas, gas_used: gas_used, status: :error}) when gas_used >= gas do
:out_of_gas
end
def transaction_to_status(%Transaction{receipt: %Receipt{status: :error}}), do: :failed
def transaction_to_status(%Transaction{status: :error}), do: :failed
@doc """
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,
Log => logs_changes,
InternalTransaction => internal_transactions_changes,
Receipt => receipts_changes,
Transaction => transactions_changes
},
options
@ -1707,13 +1651,6 @@ defmodule Explorer.Chain do
timestamps: timestamps
)
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 _ ->
insert_logs(
logs_changes,
@ -1774,29 +1711,6 @@ defmodule Explorer.Chain do
{:ok, for(log <- logs, do: Map.take(log, [:index, :transaction_hash]))}
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
ecto_schema_module = Keyword.fetch!(options, :for)

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Log do
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
@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.
* `first_topic` - `topics[0]`
* `fourth_topic` - `topics[3]`
* `index` - index of the log entry in all logs for the `receipt` / `transaction`
* `receipt` - receipt for the `transaction` being mined in a block
* `index` - index of the log entry in all logs for the `transaction`
* `second_topic` - `topics[1]`
* `transaction` - transaction for which `receipt` is
* `transaction_hash` - foreign key for `receipt`. **ALWAYS join through `receipts` and not directly to
`transaction` to ensure that any `t:Explorer.Chain.Transaction.t/0` has a receipt before it has logs in that
receipt.**
* `transaction` - transaction for which `log` is
* `transaction_hash` - foreign key for `transaction`.
* `third_topic` - `topics[2]`
* `type` - type of event
"""
@ -31,7 +28,6 @@ defmodule Explorer.Chain.Log do
first_topic: String.t(),
fourth_topic: String.t(),
index: non_neg_integer(),
receipt: %Ecto.Association.NotLoaded{} | Receipt.t(),
second_topic: String.t(),
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.Full.t(),
@ -51,8 +47,7 @@ defmodule Explorer.Chain.Log do
timestamps()
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)
has_one(:transaction, through: [:receipt, :transaction])
belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full)
end
@doc """
@ -94,8 +89,6 @@ defmodule Explorer.Chain.Log do
log
|> cast(attrs, @required_attrs)
|> cast(attrs, @optional_attrs)
|> cast_assoc(:address)
|> cast_assoc(:receipt)
|> validate_required(@required_attrs)
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
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
@typedoc """
@ -73,15 +74,19 @@ defmodule Explorer.Chain.Transaction do
@typedoc """
* `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.
* `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_hash` - foreign key of `from_address`
* `gas` - Gas provided by the sender
* `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
* `index` - index of this transaction in `block`. `nil` when transaction is pending.
* `input`- data sent along with the transaction
* `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction
* `logs` - events that occurred while mining the `transaction`.
* `nonce` - the number of transaction made by the sender prior to this one
* `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
@ -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
the X coordinate of a point R, modulo the curve order n.
* `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_hash` - `to_address` foreign key
* `v` - The V field of the signature.
@ -97,20 +103,23 @@ defmodule Explorer.Chain.Transaction do
@type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
block_hash: Hash.t() | nil,
cumulative_gas_used: Gas.t() | nil,
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_hash: Hash.Truncated.t(),
gas: Gas.t(),
gas_price: wei_per_gas,
gas_used: Gas.t() | nil,
hash: Hash.t(),
index: non_neg_integer() | nil,
input: Data.t(),
internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
logs: %Ecto.Association.NotLoaded{} | [Log.t()],
nonce: non_neg_integer(),
public_key: public_key(),
r: r(),
receipt: %Ecto.Association.NotLoaded{} | Receipt.t(),
s: s(),
standard_v: standard_v(),
status: Status.t() | nil,
to_address: %Ecto.Association.NotLoaded{} | Address.t(),
to_address_hash: Hash.Truncated.t(),
v: v(),
@ -119,8 +128,10 @@ defmodule Explorer.Chain.Transaction do
@primary_key {:hash, Hash.Full, autogenerate: false}
schema "transactions" do
field(:cumulative_gas_used, :decimal)
field(:gas, :decimal)
field(:gas_price, Wei)
field(:gas_used, :decimal)
field(:index, :integer)
field(:input, Data)
field(:nonce, :integer)
@ -128,6 +139,7 @@ defmodule Explorer.Chain.Transaction do
field(:r, :decimal)
field(:s, :decimal)
field(:standard_v, :integer)
field(:status, Status)
field(:v, :integer)
field(:value, Wei)
@ -144,7 +156,7 @@ defmodule Explorer.Chain.Transaction do
)
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(
:to_address,
@ -177,13 +189,16 @@ defmodule Explorer.Chain.Transaction do
iex> changeset.valid?
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(
...> %Transaction{},
...> %{
...> cumulative_gas_used: 0,
...> gas: 4700000,
...> gas_price: 100000000000,
...> gas_used: 4600000,
...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0,
...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
@ -192,14 +207,21 @@ defmodule Explorer.Chain.Transaction do
...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75,
...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3,
...> standard_v: 0x0,
...> status: :ok,
...> v: 0x8d,
...> value: 0
...> }
...> )
iex> changeset.valid?
false
iex> changeset.errors
[index: {"can't be set when the transaction is pending", []}]
iex> Keyword.get_values(changeset.errors, :cumulative_gas_used)
[{"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.
@ -207,8 +229,10 @@ defmodule Explorer.Chain.Transaction do
...> %Transaction{},
...> %{
...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47",
...> cumulative_gas_used: 0,
...> gas: 4700000,
...> gas_price: 100000000000,
...> gas_used: 4600000,
...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0,
...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029",
@ -217,6 +241,7 @@ defmodule Explorer.Chain.Transaction do
...> r: 0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75,
...> s: 0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3,
...> standard_v: 0x0,
...> status: :ok,
...> v: 0x8d,
...> value: 0
...> }
@ -224,7 +249,8 @@ defmodule Explorer.Chain.Transaction do
iex> changeset.valid?
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(
...> %Transaction{},
@ -245,38 +271,82 @@ defmodule Explorer.Chain.Transaction do
...> )
iex> changeset.valid?
false
iex> changeset.errors
[index: {"can't be blank when transaction is collated into a block", []}]
iex> Keyword.get_values(changeset.errors, :cumulative_gas_used)
[{"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
transaction
|> cast(attrs, @required_attrs ++ @optional_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)
|> check_constraint(
:index,
message: "cannot be set when block_hash is nil and must be set when block_hash is not nil",
name: :indexed
)
|> check_pending()
|> check_collated()
|> foreign_key_constraint(:block_hash)
|> unique_constraint(:hash)
end
defp validate_collated(%Changeset{} = changeset) do
case {Changeset.get_field(changeset, :block_hash), Changeset.get_field(changeset, :index)} do
{nil, nil} ->
changeset
@collated_fields ~w(cumulative_gas_used gas_used index status)a
@collated_message "can't be blank when the transaction is collated into a block"
@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
{_block_hash, nil} ->
Changeset.add_error(changeset, :index, "can't be blank when transaction is collated into a block")
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
{nil, _index} ->
Changeset.add_error(changeset, :index, "can't be set when the transaction is pending")
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
defp validate_collated_or_pending(%Changeset{} = changeset, field_validator) when is_function(field_validator, 2) do
Enum.reduce(@collated_fields, changeset, field_validator)
end
defp validate_pending(field, %Changeset{} = changeset) when is_atom(field) do
case Changeset.get_field(changeset, field) do
nil -> changeset
_ -> Changeset.add_error(changeset, field, @pending_message)
end
end
_ ->
changeset
defp validate_collated(field, %Changeset{} = changeset) when is_atom(field) do
case Changeset.get_field(changeset, field) do
nil -> Changeset.add_error(changeset, field, @collated_message)
_ -> changeset
end
end
end

@ -1,4 +1,4 @@
defmodule Explorer.Chain.Receipt.Status do
defmodule Explorer.Chain.Transaction.Status do
@moduledoc """
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
iex> Explorer.Chain.Receipt.Status.cast(:ok)
iex> Explorer.Chain.Transaction.Status.cast(:ok)
{:ok, :ok}
iex> Explorer.Chain.Receipt.Status.cast(:error)
iex> Explorer.Chain.Transaction.Status.cast(:error)
{:ok, :error}
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}
iex> Explorer.Chain.Receipt.Status.cast(1)
iex> Explorer.Chain.Transaction.Status.cast(1)
{:ok, :ok}
iex> Explorer.Chain.Receipt.Status.cast(2)
iex> Explorer.Chain.Transaction.Status.cast(2)
:error
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}
iex> Explorer.Chain.Receipt.Status.cast("0x1")
iex> Explorer.Chain.Transaction.Status.cast("0x1")
{:ok, :ok}
iex> Explorer.Chain.Receipt.Status.cast("0x2")
iex> Explorer.Chain.Transaction.Status.cast("0x2")
:error
"""
@ -58,20 +58,20 @@ defmodule Explorer.Chain.Receipt.Status do
@doc """
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}
iex> Explorer.Chain.Receipt.Status.dump(:error)
iex> Explorer.Chain.Transaction.Status.dump(:error)
{:ok, 0}
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
iex> Explorer.Chain.Receipt.Status.dump(1)
iex> Explorer.Chain.Transaction.Status.dump(1)
:error
iex> Explorer.Chain.Receipt.Status.dump("0x0")
iex> Explorer.Chain.Transaction.Status.dump("0x0")
:error
iex> Explorer.Chain.Receipt.Status.dump("0x1")
iex> Explorer.Chain.Transaction.Status.dump("0x1")
:error
"""
@ -86,11 +86,11 @@ defmodule Explorer.Chain.Receipt.Status do
Only loads integers `0` and `1`.
iex> Explorer.Chain.Receipt.Status.load(0)
iex> Explorer.Chain.Transaction.Status.load(0)
{:ok, :error}
iex> Explorer.Chain.Receipt.Status.load(1)
iex> Explorer.Chain.Transaction.Status.load(1)
{:ok, :ok}
iex> Explorer.Chain.Receipt.Status.load(2)
iex> Explorer.Chain.Transaction.Status.load(2)
:error
"""

@ -137,7 +137,6 @@ defmodule Explorer.Indexer.BlockFetcher do
================================
blocks: #{Chain.block_count()}
internal transactions: #{Chain.internal_transaction_count()}
receipts: #{Chain.receipt_count()}
logs: #{Chain.log_count()}
addresses: #{Chain.address_count()}
"""
@ -259,11 +258,12 @@ defmodule Explorer.Indexer.BlockFetcher do
@doc false
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)},
%{blocks: blocks, transactions: transactions} = result,
%{blocks: blocks, transactions: transactions_without_receipts} = result,
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)},
%{logs: logs, receipts: receipts} = receipt_params,
transactions_with_receipts = put_receipts(transactions_without_receipts, receipts),
{:internal_transactions, {:ok, internal_transactions}} <-
{:internal_transactions, fetch_internal_transactions(state, transaction_hashes)} do
addresses =
@ -271,7 +271,7 @@ defmodule Explorer.Indexer.BlockFetcher do
blocks: blocks,
internal_transactions: internal_transactions,
logs: logs,
transactions: transactions
transactions: transactions_with_receipts
})
insert(state, seq, range, %{
@ -279,8 +279,7 @@ defmodule Explorer.Indexer.BlockFetcher do
blocks: blocks,
internal_transactions: internal_transactions,
logs: logs,
receipts: receipts,
transactions: transactions
transactions: transactions_with_receipts
})
else
{step, {:error, reason}} ->
@ -294,6 +293,18 @@ defmodule Explorer.Indexer.BlockFetcher do
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
send(self(), :catchup_index)
state

@ -3,8 +3,15 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
def change 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_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)
# `null` when a pending transaction
@ -16,6 +23,10 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
add(:r, :numeric, precision: 100, null: false)
add(:s, :numeric, precision: 100, null: false)
add(:standard_v, :smallint, null: false)
# `null` when a pending transaction
add(:status, :integer, null: true)
add(:v, :integer, null: false)
add(:value, :numeric, precision: 100, null: false)
@ -32,8 +43,64 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do
create(
constraint(
:transactions,
:indexed,
check: "(block_hash IS NULL AND index IS NULL) OR (block_hash IS NOT NULL AND index IS NOT NULL)"
:collated_cumalative_gas_used,
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, :updated_at))
create(index(:transactions, :status))
create(unique_index(:transactions, [:block_hash, :index]))
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(
: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
)
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
time = DateTime.utc_now()
last_week = Timex.shift(time, days: -8)
block = insert(:block, timestamp: time)
:transaction
|> insert()
|> with_block(block)
last_week = Timex.shift(time, days: -8)
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()
end
@ -97,8 +104,11 @@ defmodule Explorer.Chain.StatisticsTest do
end
test "returns the last five transactions with blocks" do
block = insert(:block)
Enum.map(0..5, &insert(:transaction, block_hash: block.hash, index: &1))
Enum.map(0..5, fn _ ->
:transaction
|> insert()
|> with_block()
end)
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
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
@ -100,53 +100,6 @@ defmodule Explorer.ChainTest do
} = Chain.address_to_transactions(address)
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
adddress = %Address{hash: to_address_hash} = insert(:address)
transactions = insert_list(2, :transaction, to_address_hash: to_address_hash)
@ -204,8 +157,10 @@ defmodule Explorer.ChainTest do
end
test "with transactions" do
block = insert(:block)
%Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0)
%Transaction{block: block, hash: transaction_hash} =
:transaction
|> insert()
|> with_block()
assert %Scrivener.Page{
entries: [%Transaction{hash: ^transaction_hash}],
@ -214,55 +169,15 @@ defmodule Explorer.ChainTest do
} = Chain.block_to_transactions(block)
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
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
@ -292,8 +207,10 @@ defmodule Explorer.ChainTest do
end
test "with transactions" do
block = insert(:block)
insert(:transaction, block_hash: block.hash, index: 0)
%Transaction{block: block} =
:transaction
|> insert()
|> with_block()
assert Chain.block_to_transaction_count(block) == 1
end
@ -320,17 +237,17 @@ defmodule Explorer.ChainTest do
describe "fee/2" 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)}
end
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")}
end
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")}
end
@ -339,7 +256,7 @@ defmodule Explorer.ChainTest do
%Transaction{
gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)}
gas_used: Decimal.new(2)
},
:wei
) == {:actual, Decimal.new(4)}
@ -350,7 +267,7 @@ defmodule Explorer.ChainTest do
%Transaction{
gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)}
gas_used: Decimal.new(2)
},
:gwei
) == {:actual, Decimal.new("4e-9")}
@ -361,7 +278,7 @@ defmodule Explorer.ChainTest do
%Transaction{
gas: Decimal.new(3),
gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)}
gas_used: Decimal.new(2)
},
:ether
) == {:actual, Decimal.new("4e-18")}
@ -387,32 +304,30 @@ defmodule Explorer.ChainTest do
end
describe "hash_to_transaction/2" do
test "with transaction with receipt required without receipt returns {:error, :not_found}" do
block = insert(:block)
%Transaction{hash: hash_with_receipt, index: index_with_receipt} =
insert(:transaction, block_hash: block.hash, index: 0)
test "with transaction with block required without block returns {:error, :not_found}" do
%Transaction{hash: hash_with_block} =
:transaction
|> insert()
|> 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_receipt}} =
assert {:ok, %Transaction{hash: ^hash_with_block}} =
Chain.hash_to_transaction(
hash_with_receipt,
necessity_by_association: %{receipt: :required}
hash_with_block,
necessity_by_association: %{block: :required}
)
assert {:error, :not_found} =
Chain.hash_to_transaction(
hash_without_receipt,
necessity_by_association: %{receipt: :required}
hash_without_index,
necessity_by_association: %{block: :required}
)
assert {:ok, %Transaction{hash: ^hash_without_receipt}} =
assert {:ok, %Transaction{hash: ^hash_without_index}} =
Chain.hash_to_transaction(
hash_without_receipt,
necessity_by_association: %{receipt: :optional}
hash_without_index,
necessity_by_association: %{block: :optional}
)
end
end
@ -525,7 +440,7 @@ defmodule Explorer.ChainTest do
)
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)
pending_transaction = insert(:transaction)
@ -547,7 +462,11 @@ defmodule Explorer.ChainTest do
)
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} =
insert(
@ -565,7 +484,10 @@ defmodule Explorer.ChainTest do
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} =
insert(
@ -584,7 +506,11 @@ defmodule Explorer.ChainTest do
)
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} =
insert(
@ -611,19 +537,26 @@ defmodule Explorer.ChainTest do
assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result
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)
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)
assert Enum.empty?(Chain.address_to_internal_transactions(address))
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)
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 =
insert(
@ -698,9 +631,12 @@ defmodule Explorer.ChainTest do
).entries
end
test "Excludes internal transactions of type call with no siblings in the transaction" do
block = insert(:block)
%Transaction{hash: hash} = insert(:transaction, block_hash: block.hash, index: 0)
test "excludes internal transaction of type call with no siblings in the transaction" do
%Transaction{hash: hash} =
:transaction
|> insert()
|> with_block()
insert(:internal_transaction_call, transaction_hash: hash, index: 0)
result = Chain.transaction_hash_to_internal_transactions(hash)
@ -708,9 +644,12 @@ defmodule Explorer.ChainTest do
assert Enum.empty?(result)
end
test "Includes internal transactions of type `create` even when they are alone in the parent transaction" do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
transaction =
:transaction
|> insert()
|> with_block()
expected = insert(:internal_transaction_create, index: 0, transaction_hash: transaction.hash)
actual = Enum.at(Chain.transaction_hash_to_internal_transactions(transaction.hash), 0)
@ -719,8 +658,11 @@ defmodule Explorer.ChainTest do
end
test "returns the internal transactions in index order" do
block = insert(:block)
%Transaction{hash: hash} = insert(:transaction, block_hash: block.hash, index: 0)
%Transaction{hash: hash} =
:transaction
|> insert()
|> with_block()
%InternalTransaction{id: first_id} = insert(:internal_transaction, transaction_hash: hash, index: 0)
%InternalTransaction{id: second_id} = insert(:internal_transaction, transaction_hash: hash, index: 1)
@ -746,9 +688,11 @@ defmodule Explorer.ChainTest do
end
test "with logs" do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
transaction =
:transaction
|> insert()
|> with_block()
%Log{id: id} = insert(:log, transaction_hash: transaction.hash)
assert %Scrivener.Page{
@ -760,9 +704,11 @@ defmodule Explorer.ChainTest do
end
test "with logs can be paginated" do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
transaction =
:transaction
|> insert()
|> with_block()
logs = Enum.map(0..1, &insert(:log, index: &1, transaction_hash: transaction.hash))
[%Log{id: first_log_id}, %Log{id: second_log_id}] = logs
@ -785,16 +731,17 @@ defmodule Explorer.ChainTest do
end
test "with logs necessity_by_association loads associations" do
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
transaction =
:transaction
|> insert()
|> with_block()
insert(:log, transaction_hash: transaction.hash)
assert %Scrivener.Page{
entries: [
%Log{
address: %Address{},
receipt: %Receipt{},
transaction: %Transaction{}
}
],
@ -806,7 +753,6 @@ defmodule Explorer.ChainTest do
transaction,
necessity_by_association: %{
address: :optional,
receipt: :optional,
transaction: :optional
}
)
@ -815,7 +761,6 @@ defmodule Explorer.ChainTest do
entries: [
%Log{
address: %Ecto.Association.NotLoaded{},
receipt: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
],
@ -895,21 +840,13 @@ defmodule Explorer.ChainTest do
end
test "with block containing transactions", %{block: block, block_reward: block_reward} do
insert(
:transaction,
block: block,
index: 0,
gas_price: 1,
receipt: build(:receipt, gas_used: 1, transaction_index: 0)
)
insert(
:transaction,
block: block,
index: 1,
gas_price: 1,
receipt: build(:receipt, gas_used: 2, transaction_index: 1)
)
:transaction
|> insert(gas_price: 1)
|> with_block(block, gas_used: 1)
:transaction
|> insert(gas_price: 1)
|> with_block(block, gas_used: 2)
expected =
block_reward.reward

@ -4,7 +4,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
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}
@tag capture_log: true
@ -48,10 +48,13 @@ defmodule Explorer.Indexer.BlockFetcherTest do
setup do
block = insert(:block)
Enum.map(0..2, fn index ->
transaction = insert(:transaction, block_hash: block.hash, index: index)
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
insert(:log, transaction_hash: receipt.transaction_hash)
Enum.map(0..2, fn _ ->
transaction =
:transaction
|> insert()
|> with_block(block)
insert(:log, transaction_hash: transaction.hash)
insert(:internal_transaction, transaction_hash: transaction.hash, index: 0)
end)
@ -72,7 +75,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
assert log =~ "blocks: 4"
assert log =~ "internal transactions: 3"
assert log =~ "receipts: 6"
assert log =~ "logs: 3"
assert log =~ "addresses: 31"
end
@ -110,7 +112,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
],
internal_transactions: [],
logs: [],
receipts: [],
transactions: []
}} = 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: [
%Explorer.Chain.Hash{
byte_count: 32,
@ -187,7 +180,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do
assert Repo.aggregate(Address, :count, :hash) == 2
assert Repo.aggregate(InternalTransaction, :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
end
end

@ -15,7 +15,6 @@ defmodule Explorer.Factory do
Hash,
InternalTransaction,
Log,
Receipt,
Transaction
}
@ -64,6 +63,48 @@ defmodule Explorer.Factory do
block_hash
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
unpadded =
sequence_name
@ -149,14 +190,6 @@ defmodule Explorer.Factory do
}
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
%Transaction{
from_address_hash: insert(:address).hash,
@ -188,43 +221,6 @@ defmodule Explorer.Factory do
data(:transaction_input)
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
quote do
fragment("? + ?", unquote(left), unquote(right))
@ -253,9 +249,10 @@ defmodule Explorer.Factory do
gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas)
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{
from_address_hash: insert(:address).hash,
@ -266,7 +263,7 @@ defmodule Explorer.Factory do
output: %Data{bytes: <<1>>},
# caller MUST suppy `index`
trace_address: [],
transaction_hash: receipt.transaction_hash,
transaction_hash: transaction.hash,
type: type,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}
@ -276,9 +273,10 @@ defmodule Explorer.Factory do
gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas)
block = insert(:block)
transaction = insert(:transaction, block_hash: block.hash, index: 0)
receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
transaction =
:transaction
|> insert()
|> with_block()
%InternalTransaction{
created_contract_code: data(:internal_transaction_created_contract_code),
@ -289,7 +287,7 @@ defmodule Explorer.Factory do
# caller MUST suppy `index`
init: data(:internal_transaction_init),
trace_address: [],
transaction_hash: receipt.transaction_hash,
transaction_hash: transaction.hash,
type: type,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}

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

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

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

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

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

@ -3,7 +3,7 @@ defmodule ExplorerWeb.TransactionView do
alias Cldr.Number
alias Explorer.Chain
alias Explorer.Chain.{InternalTransaction, Receipt, Transaction, Wei}
alias Explorer.Chain.{InternalTransaction, Transaction, Wei}
alias Explorer.ExchangeRates.Token
alias ExplorerWeb.BlockView
alias ExplorerWeb.ExchangeRates.USD
@ -15,9 +15,9 @@ defmodule ExplorerWeb.TransactionView do
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)
end

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

@ -25,13 +25,13 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
from_transaction =
:transaction
|> insert(block_hash: block.hash, from_address_hash: address.hash, index: 0)
|> with_receipt()
|> insert(from_address_hash: address.hash)
|> with_block(block)
to_transaction =
:transaction
|> insert(block_hash: block.hash, to_address_hash: address.hash, index: 1)
|> with_receipt()
|> insert(to_address_hash: address.hash)
|> with_block(block)
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)
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)
block = insert(:block)
insert(
:transaction,
block_hash: block.hash,
from_address_hash: address.hash,
index: 0,
to_address_hash: address.hash
)
insert(:transaction, from_address_hash: address.hash, to_address_hash: address.hash)
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 = insert(:block, number: Enum.random(Range.new(range.from, range.to)))
insert(
:transaction,
block: block,
index: 0,
gas_price: 1,
receipt: build(:receipt, gas_used: 1, transaction_index: 0)
)
:transaction
|> insert(gas_price: 1)
|> with_block(block, gas_used: 1)
expected_reward =
block_reward.reward

@ -1,8 +1,6 @@
defmodule ExplorerWeb.BlockControllerTest do
use ExplorerWeb.ConnCase
alias Explorer.Chain.Block
@locale "en"
describe "GET show/2" do
@ -27,9 +25,11 @@ defmodule ExplorerWeb.BlockControllerTest do
end
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))

@ -18,8 +18,10 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
test "returns transactions for the block", %{conn: conn} do
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))
@ -37,9 +39,9 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do
assert Enum.empty?(conn.assigns.page)
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)
insert(:transaction, block_hash: block.hash, index: 0)
insert(:transaction)
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
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))

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

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

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

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

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

@ -14,20 +14,21 @@ defmodule ExplorerWeb.ViewingTransactionsTest do
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)
lincoln = insert(:address)
taft = insert(:address)
transaction =
insert(
:transaction,
block_hash: block.hash,
:transaction
|> insert(
value: Wei.from(Decimal.new(5656), :ether),
gas: Decimal.new(1_230_000_000_000_123_123),
gas_price: Decimal.new(7_890_000_000_898_912_300_045),
index: 4,
input: "0x000012",
nonce: 99045,
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,
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: receipt.transaction_hash)
insert(:log, address_hash: lincoln.hash, index: 0, transaction_hash: transaction.hash)
# From Lincoln to Taft.
txn_from_lincoln =
insert(
:transaction,
block_hash: block.hash,
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)
:transaction
|> insert(from_address_hash: lincoln.hash, to_address_hash: taft.hash)
|> with_block(block)
internal = insert(:internal_transaction, index: 0, transaction_hash: internal_receipt.transaction_hash)
internal = insert(:internal_transaction, index: 0, transaction_hash: transaction.hash)
{:ok,
%{

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

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

Loading…
Cancel
Save