Display Internal Transactions on the Transaction detail page.

pull/42/head
CJ Bryan, Derek Barnes, Desmond Bowe and Matt Olenick 7 years ago
parent 94f31a979d
commit 2ba3cc821e
  1. 1
      assets/css/components/_all.scss
  2. 42
      assets/css/components/_internal_transaction.scss
  3. 2
      config/test.exs
  4. 13
      lib/explorer/ethereumex_extensions.ex
  5. 67
      lib/explorer/importers/internal_transaction_importer.ex
  6. 44
      lib/explorer/internal_transaction.ex
  7. 32
      lib/explorer/services/transaction.ex
  8. 5
      lib/explorer/transaction.ex
  9. 13
      lib/explorer_web/controllers/internal_transaction_controller.ex
  10. 2
      lib/explorer_web/controllers/transaction_log_controller.ex
  11. 1
      lib/explorer_web/router.ex
  12. 43
      lib/explorer_web/templates/internal_transaction/index.html.eex
  13. 3
      lib/explorer_web/templates/transaction/show.html.eex
  14. 8
      lib/explorer_web/templates/transaction_log/index.html.eex
  15. 4
      lib/explorer_web/views/internal_transaction_view.ex
  16. 5
      lib/explorer_web/views/transaction_view.ex
  17. 24
      priv/repo/migrations/20180221001948_create_internal_transactions.exs
  18. 14
      test/explorer/ethereumex_extensions_test.exs
  19. 78
      test/explorer/importers/internal_transaction_importer_test.exs
  20. 24
      test/explorer/internal_transaction_test.exs
  21. 16
      test/explorer/services/transaction_test.exs
  22. 22
      test/explorer_web/controllers/internal_transaction_controller_test.exs
  23. 6
      test/explorer_web/features/contributor_browsing_test.exs
  24. 21
      test/support/factories/internal_transaction_factory.ex
  25. 1
      test/support/factory.ex
  26. 4
      test/support/fixture/vcr_cassettes/block_importer_download_block_1_downloads_the_block.json
  27. 4
      test/support/fixture/vcr_cassettes/block_importer_import_1_duplicate_block.json
  28. 4
      test/support/fixture/vcr_cassettes/block_importer_import_1_pending.json
  29. 4
      test/support/fixture/vcr_cassettes/block_importer_import_1_saves_the_block.json
  30. 30
      test/support/fixture/vcr_cassettes/ethereumex_extensions_trace_transaction_1.json
  31. 4
      test/support/fixture/vcr_cassettes/import_block_perform_1_duplicate.json
  32. 4
      test/support/fixture/vcr_cassettes/import_block_perform_1_earliest.json
  33. 4
      test/support/fixture/vcr_cassettes/import_block_perform_1_integer.json
  34. 4
      test/support/fixture/vcr_cassettes/import_block_perform_1_latest.json
  35. 4
      test/support/fixture/vcr_cassettes/import_block_perform_1_string.json
  36. 4
      test/support/fixture/vcr_cassettes/import_receipt_perform_1.json
  37. 30
      test/support/fixture/vcr_cassettes/internal_transaction_importer_import_1.json
  38. 1
      test/support/fixture/vcr_cassettes/internal_transaction_importer_import_1_from_core-trace.json
  39. 30
      test/support/fixture/vcr_cassettes/internal_transaction_importer_import_1_with_contract_creation.json
  40. 4
      test/support/fixture/vcr_cassettes/transaction_importer_creates_a_from_address.json
  41. 4
      test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address.json
  42. 4
      test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address_from_creates.json
  43. 4
      test/support/fixture/vcr_cassettes/transaction_importer_download_transaction.json
  44. 4
      test/support/fixture/vcr_cassettes/transaction_importer_download_transaction_with_a_bad_hash.json
  45. 4
      test/support/fixture/vcr_cassettes/transaction_importer_import_1_failed.json
  46. 4
      test/support/fixture/vcr_cassettes/transaction_importer_import_1_out_of_gas.json
  47. 4
      test/support/fixture/vcr_cassettes/transaction_importer_import_1_pending.json
  48. 4
      test/support/fixture/vcr_cassettes/transaction_importer_import_1_receipt.json
  49. 4
      test/support/fixture/vcr_cassettes/transaction_importer_import_saves_the_transaction.json
  50. 4
      test/support/fixture/vcr_cassettes/transaction_importer_saves_the_association.json
  51. 4
      test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json
  52. 4
      test/support/fixture/vcr_cassettes/transaction_importer_updates_the_association.json

@ -8,6 +8,7 @@
@import "container";
@import "footer";
@import "header";
@import "internal_transaction";
@import "pagination";
@import "transaction";
@import "transaction_log";

@ -0,0 +1,42 @@
.internal-transaction {
@extend %paper;
&__container {
padding: explorer-size(-1) explorer-size(0);
& + & { padding-top: 0; }
&--title { padding-top: explorer-size(0); }
}
&__header { @extend %section-header; }
&__heading { @extend %section-header__heading; }
&__subheading { @extend %section-header__subheading; }
&__tabs { @extend %section-tabs; }
&__tab {
@extend %section-tabs__tab;
&--active { @extend %section-tabs__tab--active; }
}
&__attributes { padding: explorer-size(-1) explorer-size(1); }
&__link { color: explorer-color("blue", "500"); }
&__table {
@extend %table;
@include explorer-typography("body1");
color: explorer-color("slate", "900");
}
&__to-address, &__from-address {
max-width: explorer-size(1);
text-overflow: ellipsis;
overflow: hidden;
}
&__column-header { @include explorer-typography("body1"); }
}
@media (min-width: $explorer-breakpoint-lg) {
&__to-address, &__from-address {
max-width: explorer-size(6);
}
}

@ -26,4 +26,4 @@ config :wallaby,
# Configure ethereumex
config :ethereumex,
url: "https://sokol.poa.network"
url: "https://sokol-trace.poa.network"

@ -0,0 +1,13 @@
defmodule Explorer.EthereumexExtensions do
@moduledoc """
Downloads the trace for a Transaction from a node.
"""
alias Ethereumex.HttpClient
def trace_transaction(hash) do
params = [hash, ["trace"]]
{:ok, trace} = HttpClient.request("trace_replayTransaction", params, [])
trace
end
end

@ -0,0 +1,67 @@
defmodule Explorer.InternalTransactionImporter do
@moduledoc "Imports a transaction's internal transactions given its hash."
import Ecto.Query
alias Explorer.EthereumexExtensions
alias Explorer.InternalTransaction
alias Explorer.Repo
alias Explorer.Transaction
def import(hash) do
transaction = find_transaction(hash)
hash
|> download_trace
|> extract_attrs
|> persist_internal_transactions(transaction)
end
defp download_trace(hash) do
EthereumexExtensions.trace_transaction(hash)
end
defp find_transaction(hash) do
query = from t in Transaction,
where: fragment("lower(?)", t.hash) == ^String.downcase(hash),
limit: 1
Repo.one!(query)
end
defp extract_attrs(attrs) do
trace = attrs["trace"]
trace |> Enum.with_index() |> Enum.map(&extract_trace/1)
end
def extract_trace({trace, index}) do
%{
index: index,
call_type: trace["action"]["callType"] || trace["type"],
to_address_id: address_id(trace["action"]["to"] || trace["result"]["address"]),
from_address_id: address_id(trace["action"]["from"]),
trace_address: trace["traceAddress"],
value: trace["action"]["value"] |> decode_integer_field,
gas: trace["action"]["gas"] |> decode_integer_field,
gas_used: trace["result"]["gasUsed"] |> decode_integer_field,
input: trace["action"]["input"],
output: trace["result"]["output"],
}
end
defp persist_internal_transactions(traces, transaction) do
Enum.map(traces, fn(trace) ->
trace = Map.merge(trace, %{transaction_id: transaction.id})
%InternalTransaction{}
|> InternalTransaction.changeset(trace)
|> Repo.insert()
end)
end
defp decode_integer_field(hex) do
{"0x", base_16} = String.split_at(hex, 2)
String.to_integer(base_16, 16)
end
defp address_id(hash) do
Explorer.Address.find_or_create_by_hash(hash).id
end
end

@ -0,0 +1,44 @@
defmodule Explorer.InternalTransaction do
@moduledoc "Models internal transactions."
use Ecto.Schema
import Ecto.Changeset
alias Explorer.InternalTransaction
alias Explorer.Transaction
alias Explorer.Address
@timestamps_opts [type: Timex.Ecto.DateTime,
autogenerate: {Timex.Ecto.DateTime, :autogenerate, []}]
schema "internal_transactions" do
belongs_to :transaction, Transaction
belongs_to :from_address, Address
belongs_to :to_address, Address
field :index, :integer
field :call_type, :string
field :trace_address, {:array, :integer}
field :value, :decimal
field :gas, :decimal
field :gas_used, :decimal
field :input, :string
field :output, :string
timestamps()
end
@required_attrs ~w(index call_type trace_address value gas gas_used
transaction_id from_address_id to_address_id)a
@optional_attrs ~w(input output)
def changeset(%InternalTransaction{} = internal_transaction, attrs \\ %{}) do
internal_transaction
|> cast(attrs, @required_attrs ++ @optional_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:transaction_id)
|> foreign_key_constraint(:to_address_id)
|> foreign_key_constraint(:from_address_id)
end
def null, do: %InternalTransaction{}
end

@ -0,0 +1,32 @@
defmodule Explorer.Transaction.Service do
alias Explorer.InternalTransaction
alias Explorer.Repo.NewRelic, as: Repo
alias Explorer.Transaction.Service.Query
def internal_transactions_from_transaction_hash(hash) do
InternalTransaction
|> Query.for_transaction(hash)
|> Query.join_from_and_to_addresses()
|> Repo.all()
end
defmodule Query do
import Ecto.Query, only: [from: 2]
def for_transaction(query, hash) do
from(child in query,
inner_join: transaction in assoc(child, :transaction),
where: fragment("lower(?)", transaction.hash) == ^String.downcase(hash)
)
end
def join_from_and_to_addresses(query) do
from(q in query,
inner_join: to_address in assoc(q, :to_address),
inner_join: from_address in assoc(q, :from_address),
preload: [:to_address, :from_address])
end
end
end

@ -7,6 +7,7 @@ defmodule Explorer.Transaction do
alias Explorer.BlockTransaction
alias Explorer.FromAddress
alias Explorer.InternalTransaction
alias Explorer.Receipt
alias Explorer.ToAddress
alias Explorer.Transaction
@ -22,6 +23,7 @@ defmodule Explorer.Transaction do
has_one :to_address, through: [:to_address_join, :address]
has_one :from_address_join, FromAddress
has_one :from_address, through: [:from_address_join, :address]
has_many :internal_transactions, InternalTransaction
field :hash, :string
field :value, :decimal
field :gas, :decimal
@ -39,12 +41,11 @@ defmodule Explorer.Transaction do
@required_attrs ~w(hash value gas gas_price input nonce public_key r s
standard_v transaction_index v)a
@optional_attrs ~w()a
@doc false
def changeset(%Transaction{} = transaction, attrs \\ %{}) do
transaction
|> cast(attrs, @required_attrs, @optional_attrs)
|> cast(attrs, @required_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:block_id)
|> update_change(:hash, &String.downcase/1)

@ -0,0 +1,13 @@
defmodule ExplorerWeb.InternalTransactionController do
use ExplorerWeb, :controller
alias Explorer.Transaction.Service, as: Transaction
def index(conn, %{"transaction_id" => transaction_id}) do
hash = String.downcase(transaction_id)
internal_transactions = Transaction.internal_transactions_from_transaction_hash(hash)
render(conn, internal_transactions: internal_transactions, transaction_hash: hash)
end
end

@ -12,6 +12,6 @@ defmodule ExplorerWeb.TransactionLogController do
join: transaction in assoc(log, :transaction),
preload: [:address],
where: fragment("lower(?)", transaction.hash) == ^hash
render(conn, "index.html", logs: Repo.paginate(logs), transaction_id: hash)
render(conn, "index.html", logs: Repo.paginate(logs), transaction_hash: hash)
end
end

@ -67,6 +67,7 @@ defmodule ExplorerWeb.Router do
resources "/pending_transactions", PendingTransactionController, only: [:index]
resources "/transactions", TransactionController, only: [:index, :show] do
resources "/logs", TransactionLogController, only: [:index], as: :log
resources "/internal", InternalTransactionController, only: [:index]
end
resources "/addresses", AddressController, only: [:show] do
resources "/transactions", AddressTransactionToController,

@ -0,0 +1,43 @@
<section class="container__section transaction">
<div class="transaction__header">
<h1 class="transaction__heading"><%= gettext "Internal Transactions" %></h1>
<h3 class="transaction__subheading"><%= @transaction_hash %></h3>
</div>
<div class="transaction__container">
<div class="transaction__tabs">
<h2 class="transaction__tab"><%= link(gettext("Overview"), to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction_hash), class: "transaction__link") %></h2>
<h2 class="transaction__tab transaction__tab--active"><%= link(gettext("Internal Transactions"), to: transaction_internal_transaction_path(@conn, :index, @conn.assigns.locale, @transaction_hash), class: "transaction__link transaction__link--active") %></h2>
<h2 class="transaction__tab"><%= link(gettext("Logs"), to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction_hash), class: "transaction__link") %></h2>
</div>
<div class="internal-transaction__container">
<table class="internal-transaction__table">
<thead>
<th class="internal-transaction__column-header"><%= gettext "Type" %></th>
<th class="internal-transaction__column-header"><%= gettext "From" %></th>
<th class="internal-transaction__column-header"><%= gettext "To" %></th>
<th class="internal-transaction__column-header"><%= gettext "Value" %></th>
<th class="internal-transaction__column-header"><%= gettext "Gas Limit" %></th>
</thead>
<%= for transaction <- @internal_transactions do %>
<tgroup>
<tr>
<td><%= transaction.call_type %></td>
<td class="internal-transaction__to-address">
<%= link(transaction.to_address.hash,
to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash),
class: "transaction-log__link") %>
</td>
<td class="internal-transaction__from-address">
<%= link(transaction.from_address.hash,
to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash),
class: "transaction-log__link") %>
</td>
<td><%= transaction.value %></td>
<td><%= ExplorerWeb.TransactionView.format_gas_limit(transaction.gas) %></td>
</tr>
</tgroup>
<% end %>
</table>
</div>
</div>
</section>

@ -6,6 +6,7 @@
<div class="transaction__container">
<div class="transaction__tabs">
<h2 class="transaction__tab transaction__tab--active"><%= link(gettext("Overview"), to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction.hash), class: "transaction__link transaction__link--active") %></h2>
<h2 class="transaction__tab"><%= link(gettext("Internal Transactions"), to: transaction_internal_transaction_path(@conn, :index, @conn.assigns.locale, @transaction.hash), class: "transaction__link") %></h2>
<h2 class="transaction__tab"><%= link(gettext("Logs"), to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction.hash), class: "transaction__link") %></h2>
</div>
<div class="transaction__attributes">
@ -77,7 +78,7 @@
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Gas Limit" %></dt>
<dd class="transaction__item-value"><%= @transaction.gas |> Cldr.Number.to_string! %></dd>
<dd class="transaction__item-value"><%= format_gas_limit(@transaction.gas) %></dd>
</div>
<div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Gas Price" %></dt>

@ -1,13 +1,15 @@
<section class="container__section">
<div class="transaction-log__header">
<h1 class="transaction-log__heading"><%= gettext "Transaction Logs" %></h1>
<h3 class="transaction-log__subheading"><%= @transaction_id %></h3>
<h3 class="transaction-log__subheading"><%= @transaction_hash %></h3>
</div>
<div class="transaction-log">
<div class="transaction-log__tabs">
<h2 class="transaction-log__tab"><%= link(gettext("Overview"), to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction_id), class: "transaction-log__link") %></h2>
<h2 class="transaction-log__tab transaction-log__tab--active"><%= link(gettext("Logs"), to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction_id), class: "transaction-log__link transaction-log__link--active") %></h2>
<h2 class="transaction-log__tab"><%= link(gettext("Overview"), to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction_hash), class: "transaction-log__link") %></h2>
<h2 class="transaction__tab"><%= link(gettext("Internal Transactions"), to: transaction_internal_transaction_path(@conn, :index, @conn.assigns.locale, @transaction_hash), class: "transaction__link") %></h2>
<h2 class="transaction-log__tab transaction-log__tab--active"><%= link(gettext("Logs"), to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction_hash), class: "transaction-log__link transaction-log__link--active") %></h2>
</div>
<div class="transaction-log__container">
<table class="transaction-log__table">
<thead>

@ -0,0 +1,4 @@
defmodule ExplorerWeb.InternalTransactionView do
use ExplorerWeb, :view
@dialyzer :no_match
end

@ -1,4 +1,9 @@
defmodule ExplorerWeb.TransactionView do
use ExplorerWeb, :view
@dialyzer :no_match
def format_gas_limit(gas) do
gas
|> Cldr.Number.to_string!
end
end

@ -0,0 +1,24 @@
defmodule Explorer.Repo.Migrations.CreateInternalTransactions do
use Ecto.Migration
def change do
create table(:internal_transactions) do
add :transaction_id, references(:transactions), null: false
add :to_address_id, references(:addresses), null: false
add :from_address_id, references(:addresses), null: false
add :index, :integer, null: false
add :call_type, :string, null: false
add :trace_address, {:array, :integer}, null: false
add :value, :numeric, precision: 100, null: false
add :gas, :numeric, precision: 100, null: false
add :gas_used, :numeric, precision: 100, null: false
add :input, :string
add :output, :string
timestamps null: false
end
create index(:internal_transactions, :transaction_id)
create index(:internal_transactions, :to_address_id)
create index(:internal_transactions, :from_address_id)
end
end

@ -0,0 +1,14 @@
defmodule Explorer.EthereumexExtensionsTest do
use Explorer.DataCase
alias Explorer.EthereumexExtensions
describe "trace_transaction/1" do
test "returns a transaction trace" do
use_cassette "ethereumex_extensions_trace_transaction_1" do
hash = "0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68"
result = EthereumexExtensions.trace_transaction(hash)
assert(is_list(result["trace"]))
end
end
end
end

@ -0,0 +1,78 @@
defmodule Explorer.InternalTransactionImporterTest do
use Explorer.DataCase
alias Explorer.InternalTransaction
alias Explorer.InternalTransactionImporter
describe "import/1" do
test "imports and saves an internal transaction to the database" do
use_cassette "internal_transaction_importer_import_1" do
transaction = insert(:transaction, hash: "0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68")
InternalTransactionImporter.import(transaction.hash)
internal_transactions = InternalTransaction |> Repo.all()
assert length(internal_transactions) == 2
end
end
test "imports internal transactions with ordered indexes" do
use_cassette "internal_transaction_importer_import_1" do
transaction = insert(:transaction, hash: "0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68")
InternalTransactionImporter.import(transaction.hash)
last_internal_transaction = InternalTransaction |> order_by(desc: :index) |> limit(1) |> Repo.one()
assert last_internal_transaction.index == 1
end
end
test "imports an internal transaction that creates a contract" do
use_cassette "internal_transaction_importer_import_1_with_contract_creation" do
transaction = insert(:transaction, hash: "0x27d64b8e8564d2852c88767e967b88405c99341509cd3a3504fd67a65277116d")
InternalTransactionImporter.import(transaction.hash)
last_internal_transaction = InternalTransaction |> order_by(desc: :index) |> limit(1) |> Repo.one()
assert last_internal_transaction.call_type == "create"
end
end
test "import fails if a transaction with the hash doesn't exist" do
hash = "0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68"
assert_raise Ecto.NoResultsError, fn -> InternalTransactionImporter.import(hash) end
end
end
describe "extract_trace" do
test "maps attributes to database record attributes when the trace is a call" do
trace = %{
"action" => %{
"callType" => "call",
"from" => "0xba9f067abbc4315ece8eb33e7a3d01030bb368ef",
"gas" => "0x4821f",
"input" => "0xd1f276d3",
"to" => "0xe213402e637565bb9de0651827517e7554693f53",
"value" => "0x0",
},
"result" => %{
"gasUsed" => "0x4e4",
"output" => "0x000000000000000000000000ba9f067abbc4315ece8eb33e7a3d01030bb368ef"
},
"subtraces" => 0,
"traceAddress" => [2, 0],
"type" => "call"
}
to_address = insert(:address, hash: "0xe213402e637565bb9de0651827517e7554693f53")
from_address = insert(:address, hash: "0xba9f067abbc4315ece8eb33e7a3d01030bb368ef")
assert(InternalTransactionImporter.extract_trace({trace, 2}) == %{
index: 2,
to_address_id: to_address.id,
from_address_id: from_address.id,
call_type: "call",
trace_address: [2, 0],
value: 0,
gas: 295455,
gas_used: 1252,
input: "0xd1f276d3",
output: "0x000000000000000000000000ba9f067abbc4315ece8eb33e7a3d01030bb368ef",
})
end
end
end

@ -0,0 +1,24 @@
defmodule Explorer.InternalTransactionTest do
use Explorer.DataCase
alias Explorer.InternalTransaction
describe "changeset/2" do
test "with valid attributes" do
transaction = insert(:transaction)
changeset = InternalTransaction.changeset(%InternalTransaction{}, %{transaction_id: transaction.id, index: 0, call_type: "call", trace_address: [0, 1], value: 100, gas: 100, gas_used: 100, input: "pintos", output: "refried", to_address_id: 1, from_address_id: 2})
assert changeset.valid?
end
test "with invalid attributes" do
changeset = InternalTransaction.changeset(%InternalTransaction{}, %{falala: "falafel"})
refute changeset.valid?
end
test "that a valid changeset is persistable" do
transaction = insert(:transaction)
changeset = InternalTransaction.changeset(%InternalTransaction{}, %{transaction: transaction, index: 0, call_type: "call", trace_address: [0, 1], value: 100, gas: 100, gas_used: 100, input: "thin-mints", output: "munchos"})
assert Repo.insert(changeset)
end
end
end

@ -0,0 +1,16 @@
defmodule Explorer.Transaction.ServiceTest do
use Explorer.DataCase
alias Explorer.Transaction.Service
describe "internal_transactions_from_transaction_hash/1" do
test "it returns all internal transactions for a given hash" do
transaction = insert(:transaction)
internal_transaction = insert(:internal_transaction, transaction_id: transaction.id)
result = hd(Service.internal_transactions_from_transaction_hash(transaction.hash))
assert result.id == internal_transaction.id
end
end
end

@ -0,0 +1,22 @@
defmodule ExplorerWeb.InternalTransactionControllerTest do
use ExplorerWeb.ConnCase
import ExplorerWeb.Router.Helpers, only: [transaction_internal_transaction_path: 4]
describe "GET index/2" do
test "returns internal transactions for the transaction", %{conn: conn} do
transaction = insert(:transaction)
internal_transaction = insert(:internal_transaction, transaction_id: transaction.id)
path =
transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash)
conn = get(conn, path)
first_internal_transaction = List.first(conn.assigns.internal_transactions)
assert conn.assigns.transaction_hash == transaction.hash
assert first_internal_transaction.id == internal_transaction.id
end
end
end

@ -98,6 +98,8 @@ defmodule ExplorerWeb.UserListTest do
insert(:to_address, address: taft, transaction: txn_from_lincoln)
insert(:receipt, transaction: txn_from_lincoln)
internal = insert(:internal_transaction, transaction_id: transaction.id)
Credit.refresh
Debit.refresh
@ -132,6 +134,10 @@ defmodule ExplorerWeb.UserListTest do
|> assert_has(css(".transaction__item", text: "48 years ago"))
|> assert_has(css(".transaction__item", text: "38 years ago"))
|> click(link("Internal Transactions"))
|> assert_has(css(".internal-transaction__table", text: internal.call_type))
|> visit("/en/transactions/0xSk8")
|> click(link("Logs"))
|> assert_has(css(".transaction-log__link", text: "0xlincoln"))

@ -0,0 +1,21 @@
defmodule Explorer.InternalTransactionFactory do
defmacro __using__(_opts) do
quote do
def internal_transaction_factory do
%Explorer.InternalTransaction{
index: Enum.random(0..9),
call_type: Enum.random(["call", "creates", "calldelegate"]),
trace_address: [Enum.random(0..4), Enum.random(0..4)],
from_address_id: insert(:address).id,
to_address_id: insert(:address).id,
transaction_id: insert(:transaction).id,
value: Enum.random(1..100_000),
gas: Enum.random(1..100_000),
gas_used: Enum.random(1..100_000),
input: sequence("0x"),
output: sequence("0x"),
}
end
end
end
end

@ -5,6 +5,7 @@ defmodule Explorer.Factory do
use Explorer.BlockFactory
use Explorer.BlockTransactionFactory
use Explorer.FromAddressFactory
use Explorer.InternalTransactionFactory
use Explorer.LogFactory
use Explorer.ToAddressFactory
use Explorer.TransactionFactory

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -0,0 +1,30 @@
[
{
"request": {
"body": "{\"params\":[\"0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68\",[\"trace\"]],\"method\":\"trace_replayTransaction\",\"jsonrpc\":\"2.0\",\"id\":0}",
"headers": {
"Content-Type": "application/json"
},
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
"body": "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x82e4e61e7f5139ff0a4157a5bc687ef42294c248\",\"gas\":\"0x63dc\",\"input\":\"0x\",\"to\":\"0x0e8a835861fe709b6100f83628debbc353342734\",\"value\":\"0xf95b28cd2c38000\"},\"result\":{\"gasUsed\":\"0x24a0\",\"output\":\"0x\"},\"subtraces\":1,\"traceAddress\":[],\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x0e8a835861fe709b6100f83628debbc353342734\",\"gas\":\"0x8fc\",\"input\":\"0x\",\"to\":\"0x0039f22efb07a647557c7c5d17854cfd6d489ef3\",\"value\":\"0xf95b28cd2c38000\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[0],\"type\":\"call\"}],\"vmTrace\":null},\"id\":0}\n",
"headers": {
"Date": "Tue, 20 Feb 2018 22:24:43 GMT",
"Content-Type": "application/json",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Set-Cookie": "__cfduid=da335ec956341b09d9fbfd8d02718b03d1519165483; expires=Wed, 20-Feb-19 22:24:43 GMT; path=/; domain=.poa.network; HttpOnly; Secure",
"Expect-CT": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"",
"Server": "cloudflare",
"CF-RAY": "3f04e0b09f386c04-SJC"
},
"status_code": 200,
"type": "ok"
}
}
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -0,0 +1,30 @@
[
{
"request": {
"body": "{\"params\":[\"0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68\",[\"trace\"]],\"method\":\"trace_replayTransaction\",\"jsonrpc\":\"2.0\",\"id\":0}",
"headers": {
"Content-Type": "application/json"
},
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
"body": "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x82e4e61e7f5139ff0a4157a5bc687ef42294c248\",\"gas\":\"0x63dc\",\"input\":\"0x\",\"to\":\"0x0e8a835861fe709b6100f83628debbc353342734\",\"value\":\"0xf95b28cd2c38000\"},\"result\":{\"gasUsed\":\"0x24a0\",\"output\":\"0x\"},\"subtraces\":1,\"traceAddress\":[],\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x0e8a835861fe709b6100f83628debbc353342734\",\"gas\":\"0x8fc\",\"input\":\"0x\",\"to\":\"0x0039f22efb07a647557c7c5d17854cfd6d489ef3\",\"value\":\"0xf95b28cd2c38000\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[0],\"type\":\"call\"}],\"vmTrace\":null},\"id\":0}\n",
"headers": {
"Date": "Wed, 21 Feb 2018 20:18:17 GMT",
"Content-Type": "application/json",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Set-Cookie": "__cfduid=d8935c4b5e8d5f4d7472de76debcc87921519244296; expires=Thu, 21-Feb-19 20:18:16 GMT; path=/; domain=.poa.network; HttpOnly; Secure",
"Expect-CT": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"",
"Server": "cloudflare",
"CF-RAY": "3f0c64d82a9493fc-SJC"
},
"status_code": 200,
"type": "ok"
}
}
]

@ -0,0 +1,30 @@
[
{
"request": {
"body": "{\"params\":[\"0x27d64b8e8564d2852c88767e967b88405c99341509cd3a3504fd67a65277116d\",[\"trace\"]],\"method\":\"trace_replayTransaction\",\"jsonrpc\":\"2.0\",\"id\":0}",
"headers": {
"Content-Type": "application/json"
},
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
"body": "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"stateDiff\":null,\"trace\":[{\"action\":{\"from\":\"0x4349bb5579700d2a515cfef9cbdb7bc7f881cdd0\",\"gas\":\"0x273a9\",\"init\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"value\":\"0x0\"},\"result\":{\"address\":\"0xa8bf4dc1f90efdfd9479d7c633612dfe48589535\",\"code\":\"0x606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"gasUsed\":\"0x7fe0\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"create\"}],\"vmTrace\":null},\"id\":0}\n",
"headers": {
"Date": "Thu, 22 Feb 2018 23:21:11 GMT",
"Content-Type": "application/json",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Set-Cookie": "__cfduid=d0ba210a5363b8aa64b7a5482d937d6ac1519341670; expires=Fri, 22-Feb-19 23:21:10 GMT; path=/; domain=.poa.network; HttpOnly; Secure",
"Expect-CT": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"",
"Server": "cloudflare",
"CF-RAY": "3f15ae22cfb36bfe-SJC"
},
"status_code": 200,
"type": "ok"
}
}
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

@ -8,7 +8,7 @@
"method": "post",
"options": [],
"request_body": "",
"url": "https://sokol.poa.network"
"url": "https://sokol-trace.poa.network"
},
"response": {
"binary": false,
@ -27,4 +27,4 @@
"type": "ok"
}
}
]
]

Loading…
Cancel
Save