From 9570cd46de7567b27aa122a9aaf947c23abf0ae2 Mon Sep 17 00:00:00 2001 From: CJ Bryan and Matt Olenick Date: Fri, 2 Feb 2018 17:52:07 -0800 Subject: [PATCH] Use a join table to link Blocks and Transactions. --- lib/explorer/block.ex | 13 +- lib/explorer/block_transaction.ex | 24 ++ lib/explorer/fetcher.ex | 23 +- lib/explorer/forms/block_form.ex | 18 +- lib/explorer/forms/transaction_form.ex | 48 ++-- lib/explorer/from_address.ex | 7 +- .../importers/transaction_importer.ex | 95 +++++++ lib/explorer/to_address.ex | 7 +- lib/explorer/transaction.ex | 14 +- .../controllers/block_controller.ex | 8 +- .../controllers/chain_controller.ex | 19 +- .../controllers/transaction_controller.ex | 16 +- .../templates/transaction/show.html.eex | 2 +- priv/gettext/default.pot | 47 +++- priv/gettext/en/LC_MESSAGES/default.po | 47 +++- ...180202195342_create_block_transactions.exs | 14 ++ ...5933_remove_block_id_from_transactions.exs | 9 + test/explorer/block_transaction_test.exs | 17 ++ test/explorer/fetcher_test.exs | 52 +++- test/explorer/forms/block_form_test.exs | 4 +- test/explorer/forms/transaction_form_test.exs | 56 ++++- .../importers/transaction_importer_test.exs | 235 ++++++++++++++++++ test/explorer/transaction_test.exs | 9 +- .../controllers/chain_controller_test.exs | 26 +- .../transaction_controller_test.exs | 25 +- .../features/contributor_browsing_test.exs | 6 +- .../factories/block_transaction_factory.ex | 9 + test/support/factories/transaction_factory.ex | 21 +- test/support/factory.ex | 1 + ...ction_importer_creates_a_from_address.json | 30 +++ ...saction_importer_creates_a_to_address.json | 30 +++ ...ter_creates_a_to_address_from_creates.json | 30 +++ ...saction_importer_download_transaction.json | 30 +++ ..._download_transaction_with_a_bad_hash.json | 30 +++ ...importer_import_saves_the_transaction.json | 30 +++ ...action_importer_saves_the_association.json | 30 +++ ...ransaction_importer_txn_without_block.json | 30 +++ ...tion_importer_updates_the_association.json | 30 +++ 38 files changed, 989 insertions(+), 153 deletions(-) create mode 100644 lib/explorer/block_transaction.ex create mode 100644 lib/explorer/importers/transaction_importer.ex create mode 100644 priv/repo/migrations/20180202195342_create_block_transactions.exs create mode 100644 priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs create mode 100644 test/explorer/block_transaction_test.exs create mode 100644 test/explorer/importers/transaction_importer_test.exs create mode 100644 test/support/factories/block_transaction_factory.ex create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_creates_a_from_address.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address_from_creates.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_download_transaction.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_download_transaction_with_a_bad_hash.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_import_saves_the_transaction.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_saves_the_association.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json create mode 100644 test/support/fixture/vcr_cassettes/transaction_importer_updates_the_association.json diff --git a/lib/explorer/block.ex b/lib/explorer/block.ex index 3c56533909..d67aaf1b07 100644 --- a/lib/explorer/block.ex +++ b/lib/explorer/block.ex @@ -1,8 +1,9 @@ defmodule Explorer.Block do - use Ecto.Schema + alias Explorer.Block + alias Explorer.BlockTransaction import Ecto.Changeset + use Ecto.Schema import Ecto.Query - alias Explorer.Block @moduledoc false @@ -10,6 +11,8 @@ defmodule Explorer.Block do autogenerate: {Timex.Ecto.DateTime, :autogenerate, []}] schema "blocks" do + has_many :block_transactions, BlockTransaction + has_many :transactions, through: [:block_transactions, :transaction] field :number, :integer field :hash, :string field :parent_hash, :string @@ -22,19 +25,15 @@ defmodule Explorer.Block do field :gas_used, :integer field :timestamp, Timex.Ecto.DateTime timestamps() - - has_many :transactions, Explorer.Transaction end @required_attrs ~w(number hash parent_hash nonce miner difficulty total_difficulty size gas_limit gas_used timestamp)a - @optional_attrs ~w()a @doc false def changeset(%Block{} = block, attrs) do block - |> cast(attrs, @required_attrs, @optional_attrs) - |> cast_assoc(:transactions) + |> cast(attrs, @required_attrs) |> validate_required(@required_attrs) |> update_change(:hash, &String.downcase/1) |> unique_constraint(:hash) diff --git a/lib/explorer/block_transaction.ex b/lib/explorer/block_transaction.ex new file mode 100644 index 0000000000..73c94e8592 --- /dev/null +++ b/lib/explorer/block_transaction.ex @@ -0,0 +1,24 @@ +defmodule Explorer.BlockTransaction do + @moduledoc false + alias Explorer.BlockTransaction + import Ecto.Changeset + use Ecto.Schema + + @timestamps_opts [type: Timex.Ecto.DateTime, + autogenerate: {Timex.Ecto.DateTime, :autogenerate, []}] + + @primary_key false + schema "block_transactions" do + belongs_to :block, Explorer.Block + belongs_to :transaction, Explorer.Transaction, primary_key: true + timestamps() + end + + @required_attrs ~w(block_id transaction_id)a + + def changeset(%BlockTransaction{} = block_transaction, attrs \\ %{}) do + block_transaction + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + end +end diff --git a/lib/explorer/fetcher.ex b/lib/explorer/fetcher.ex index 2bf4112bc5..393ad0fce9 100644 --- a/lib/explorer/fetcher.ex +++ b/lib/explorer/fetcher.ex @@ -2,6 +2,7 @@ defmodule Explorer.Fetcher do @moduledoc false alias Explorer.Address alias Explorer.Block + alias Explorer.BlockTransaction alias Explorer.FromAddress alias Explorer.Repo.NewRelic, as: Repo alias Explorer.ToAddress @@ -52,13 +53,30 @@ defmodule Explorer.Fetcher do def create_transaction(block, changes) do transaction = Repo.get_by(Transaction, hash: changes["hash"]) || %Transaction{} transaction - |> Transaction.changeset(extract_transaction(block, changes)) + |> Transaction.changeset(extract_transaction(changes)) |> Repo.insert_or_update! |> create_from_address(changes["from"]) |> create_to_address(changes["to"] || changes["creates"]) + |> create_block_transaction(block) end - def extract_transaction(block, transaction) do + def create_block_transaction(transaction, block) do + if block do + block_transaction = + Repo.get_by(BlockTransaction, transaction_id: transaction.id) || + %BlockTransaction{} + + changes = %{block_id: block.id, transaction_id: transaction.id} + + block_transaction + |> BlockTransaction.changeset(changes) + |> Repo.insert_or_update! + end + + transaction + end + + def extract_transaction(transaction) do %{ hash: transaction["hash"], value: transaction["value"] |> decode_integer_field, @@ -72,7 +90,6 @@ defmodule Explorer.Fetcher do standard_v: transaction["standardV"], transaction_index: transaction["transactionIndex"], v: transaction["v"], - block_id: block.id, } end diff --git a/lib/explorer/forms/block_form.ex b/lib/explorer/forms/block_form.ex index dd3e70b11f..c0a0ef80bc 100644 --- a/lib/explorer/forms/block_form.ex +++ b/lib/explorer/forms/block_form.ex @@ -1,9 +1,9 @@ defmodule Explorer.BlockForm do - import Ecto.Query - alias Explorer.Transaction - alias Explorer.Repo - @moduledoc false + alias Explorer.Block + alias Explorer.BlockTransaction + alias Explorer.Repo + import Ecto.Query def build(block) do block |> Map.merge(%{ @@ -14,10 +14,12 @@ defmodule Explorer.BlockForm do end def get_transactions_count(block) do - Transaction - |> where([t], t.block_id == ^block.id) - |> Repo.all - |> Enum.count + query = from block_transaction in BlockTransaction, + join: block in Block, + where: block.id == block_transaction.block_id, + where: block.id == ^block.id, + select: count(block_transaction.block_id) + Repo.one(query) end def calculate_age(block) do diff --git a/lib/explorer/forms/transaction_form.ex b/lib/explorer/forms/transaction_form.ex index 60043dbb49..24a43b5c1d 100644 --- a/lib/explorer/forms/transaction_form.ex +++ b/lib/explorer/forms/transaction_form.ex @@ -1,40 +1,56 @@ defmodule Explorer.TransactionForm do - @moduledoc false + alias Cldr.Number alias Explorer.Address alias Explorer.Block + alias Explorer.BlockTransaction alias Explorer.FromAddress alias Explorer.Repo alias Explorer.ToAddress alias Explorer.Transaction import Ecto.Query + @moduledoc "Format a Block and a Transaction for display." + def build(transaction) do + block = with_block(transaction) + transaction |> Map.merge(%{ - block_number: transaction |> block_number, - age: transaction |> block_age, - formatted_timestamp: transaction |> format_timestamp, - cumulative_gas_used: transaction |> cumulative_gas_used, + block_number: block |> block_number, + age: block |> block_age, + formatted_timestamp: block |> format_timestamp, + cumulative_gas_used: block |> cumulative_gas_used, to_address: transaction |> to_address, from_address: transaction |> from_address, - confirmations: transaction |> confirmations, + confirmations: block |> confirmations, }) end - def block_number(transaction) do - transaction.block.number + def with_block(transaction) do + Repo.one( + from block in Block, + join: block_transaction in BlockTransaction, + where: block_transaction.block_id == block.id, + join: transaction in Transaction, + where: transaction.id == block_transaction.transaction_id, + where: transaction.id == ^transaction.id + ) + end + + def block_number(block) do + block && block.number || "" end - def block_age(transaction) do - transaction.block.timestamp |> Timex.from_now + def block_age(block) do + block && block.timestamp |> Timex.from_now || "" end - def format_timestamp(transaction) do - transaction.block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime) + def format_timestamp(block) do + block && block.timestamp |> Timex.format!("%b-%d-%Y %H:%M:%S %p %Z", :strftime) || "" end - def cumulative_gas_used(transaction) do - transaction.block.gas_used + def cumulative_gas_used(block) do + block && block.gas_used |> Number.to_string! || "" end def to_address(transaction) do @@ -59,8 +75,8 @@ defmodule Explorer.TransactionForm do Repo.one(query).hash end - def confirmations(transaction) do + def confirmations(block) do query = from block in Block, select: max(block.number) - Repo.one(query) - transaction.block.number + block && Repo.one(query) - block.number || 0 end end diff --git a/lib/explorer/from_address.ex b/lib/explorer/from_address.ex index c09f051fe4..960d4c8791 100644 --- a/lib/explorer/from_address.ex +++ b/lib/explorer/from_address.ex @@ -1,9 +1,8 @@ defmodule Explorer.FromAddress do - use Ecto.Schema - import Ecto.Changeset - alias Explorer.FromAddress - @moduledoc false + alias Explorer.FromAddress + import Ecto.Changeset + use Ecto.Schema @timestamps_opts [type: Timex.Ecto.DateTime, autogenerate: {Timex.Ecto.DateTime, :autogenerate, []}] diff --git a/lib/explorer/importers/transaction_importer.ex b/lib/explorer/importers/transaction_importer.ex new file mode 100644 index 0000000000..4d4c72be27 --- /dev/null +++ b/lib/explorer/importers/transaction_importer.ex @@ -0,0 +1,95 @@ +defmodule Explorer.TransactionImporter do + @moduledoc "Imports a transaction given a unique hash." + + import Ethereumex.HttpClient, only: [eth_get_transaction_by_hash: 1] + + alias Explorer.Address + alias Explorer.Block + alias Explorer.BlockTransaction + alias Explorer.Repo + alias Explorer.FromAddress + alias Explorer.ToAddress + alias Explorer.Transaction + + def import(hash) do + raw_transaction = download_transaction(hash) + changes = extract_attrs(raw_transaction) + + transaction = Repo.get_by(Transaction, hash: changes.hash) || %Transaction{} + transaction + |> Transaction.changeset(changes) + |> Repo.insert_or_update! + |> create_from_address(raw_transaction["from"]) + |> create_to_address(raw_transaction["to"] || raw_transaction["creates"]) + |> create_block_transaction(raw_transaction["blockHash"]) + end + + def download_transaction(hash) do + {:ok, payload} = eth_get_transaction_by_hash(hash) + payload + end + + def extract_attrs(raw_transaction) do + %{ + hash: raw_transaction["hash"], + value: raw_transaction["value"] |> decode_integer_field, + gas: raw_transaction["gas"] |> decode_integer_field, + gas_price: raw_transaction["gasPrice"] |> decode_integer_field, + input: raw_transaction["input"], + nonce: raw_transaction["nonce"] |> decode_integer_field, + public_key: raw_transaction["publicKey"], + r: raw_transaction["r"], + s: raw_transaction["s"], + standard_v: raw_transaction["standardV"], + transaction_index: raw_transaction["transactionIndex"], + v: raw_transaction["v"], + } + end + + def create_block_transaction(transaction, block_hash) do + block = Repo.get_by(Block, hash: block_hash) + + if block do + block_transaction = + Repo.get_by(BlockTransaction, transaction_id: transaction.id) || + %BlockTransaction{} + + changes = %{block_id: block.id, transaction_id: transaction.id} + + block_transaction + |>BlockTransaction.changeset(changes) + |> Repo.insert_or_update! + end + + transaction + end + + def create_from_address(transaction, hash) do + address = Address.find_or_create_by_hash(hash) + changes = %{transaction_id: transaction.id, address_id: address.id} + + from_address = Repo.get_by(FromAddress, changes) || %FromAddress{} + from_address + |> FromAddress.changeset(changes) + |> Repo.insert_or_update! + + transaction + end + + def create_to_address(transaction, hash) do + address = Address.find_or_create_by_hash(hash) + changes = %{transaction_id: transaction.id, address_id: address.id} + + to_address = Repo.get_by(ToAddress, changes) || %ToAddress{} + to_address + |> ToAddress.changeset(changes) + |> Repo.insert_or_update! + + transaction + end + + def decode_integer_field(hex) do + {"0x", base_16} = String.split_at(hex, 2) + String.to_integer(base_16, 16) + end +end diff --git a/lib/explorer/to_address.ex b/lib/explorer/to_address.ex index f20460fd6f..66ddc5e453 100644 --- a/lib/explorer/to_address.ex +++ b/lib/explorer/to_address.ex @@ -1,9 +1,8 @@ defmodule Explorer.ToAddress do - use Ecto.Schema - import Ecto.Changeset - alias Explorer.ToAddress - @moduledoc false + alias Explorer.ToAddress + import Ecto.Changeset + use Ecto.Schema @timestamps_opts [type: Timex.Ecto.DateTime, autogenerate: {Timex.Ecto.DateTime, :autogenerate, []}] diff --git a/lib/explorer/transaction.ex b/lib/explorer/transaction.ex index 6c5a32236d..aefcd419f9 100644 --- a/lib/explorer/transaction.ex +++ b/lib/explorer/transaction.ex @@ -1,14 +1,16 @@ defmodule Explorer.Transaction do @moduledoc false - - use Ecto.Schema - import Ecto.Changeset + alias Explorer.BlockTransaction alias Explorer.Transaction + import Ecto.Changeset + use Ecto.Schema @timestamps_opts [type: Timex.Ecto.DateTime, autogenerate: {Timex.Ecto.DateTime, :autogenerate, []}] schema "transactions" do + has_one :block_transaction, BlockTransaction + has_one :block, through: [:block_transaction, :block] field :hash, :string field :value, :decimal field :gas, :decimal @@ -22,10 +24,6 @@ defmodule Explorer.Transaction do field :transaction_index, :string field :v, :string timestamps() - - belongs_to :block, Explorer.Block - - many_to_many :to_address, Explorer.Address, join_through: "to_addresses", unique: true end @required_attrs ~w(hash value gas gas_price input nonce public_key r s @@ -35,7 +33,7 @@ defmodule Explorer.Transaction do @doc false def changeset(%Transaction{} = transaction, attrs \\ %{}) do transaction - |> cast(attrs, [:block_id | @required_attrs], @optional_attrs) + |> cast(attrs, @required_attrs, @optional_attrs) |> validate_required(@required_attrs) |> foreign_key_constraint(:block_id) |> update_change(:hash, &String.downcase/1) diff --git a/lib/explorer_web/controllers/block_controller.ex b/lib/explorer_web/controllers/block_controller.ex index 98c6580d85..06bc0170a0 100644 --- a/lib/explorer_web/controllers/block_controller.ex +++ b/lib/explorer_web/controllers/block_controller.ex @@ -8,9 +8,11 @@ defmodule ExplorerWeb.BlockController do alias Explorer.Repo.NewRelic, as: Repo def index(conn, params) do - blocks = from b in Block, - order_by: [desc: b.number], - preload: :transactions + blocks = from block in Block, + left_join: block_transaction in assoc(block, :block_transactions), + left_join: transactions in assoc(block_transaction, :transaction), + preload: [transactions: transactions], + order_by: [desc: block.number] render(conn, "index.html", blocks: Repo.paginate(blocks, params)) end diff --git a/lib/explorer_web/controllers/chain_controller.ex b/lib/explorer_web/controllers/chain_controller.ex index 43b25e6e04..f23db58563 100644 --- a/lib/explorer_web/controllers/chain_controller.ex +++ b/lib/explorer_web/controllers/chain_controller.ex @@ -4,8 +4,10 @@ defmodule ExplorerWeb.ChainController do import Ecto.Query alias Explorer.Block + alias Explorer.BlockForm alias Explorer.Repo.NewRelic, as: Repo alias Explorer.Transaction + alias Explorer.TransactionForm def show(conn, _params) do blocks = from b in Block, @@ -13,17 +15,20 @@ defmodule ExplorerWeb.ChainController do preload: :transactions, limit: 5 - transactions = from t in Transaction, - join: b in Block, on: b.id == t.block_id, - order_by: [desc: b.number], - preload: :block, - limit: 5 + transactions = from transaction in Transaction, + left_join: block_transaction in assoc(transaction, :block_transaction), + left_join: block in assoc(block_transaction, :block), + preload: [block_transaction: block_transaction, block: block], + limit: 5, + order_by: [desc: block.number] render( conn, "show.html", - blocks: Repo.all(blocks), - transactions: Repo.all(transactions) + blocks: blocks |> Repo.all |> Enum.map(&BlockForm.build/1), + transactions: transactions + |> Repo.all + |> Enum.map(&TransactionForm.build/1) ) end end diff --git a/lib/explorer_web/controllers/transaction_controller.ex b/lib/explorer_web/controllers/transaction_controller.ex index f32b5ec537..ea8f957bfa 100644 --- a/lib/explorer_web/controllers/transaction_controller.ex +++ b/lib/explorer_web/controllers/transaction_controller.ex @@ -3,18 +3,20 @@ defmodule ExplorerWeb.TransactionController do import Ecto.Query - alias Explorer.Block alias Explorer.Repo.NewRelic, as: Repo alias Explorer.Transaction alias Explorer.TransactionForm def index(conn, params) do - transactions = from t in Transaction, - join: b in Block, on: b.id == t.block_id, - order_by: [desc: b.number], - preload: :block + query = from transaction in Transaction, + left_join: block_transaction in assoc(transaction, :block_transaction), + left_join: block in assoc(block_transaction, :block), + preload: [block_transaction: block_transaction, block: block], + order_by: [asc: block.inserted_at] - render(conn, "index.html", transactions: Repo.paginate(transactions, params)) + transactions = Repo.paginate(query, params) + + render(conn, "index.html", transactions: transactions) end def show(conn, params) do @@ -22,8 +24,8 @@ defmodule ExplorerWeb.TransactionController do |> where(hash: ^params["id"]) |> first |> Repo.one - |> Repo.preload(:block) |> TransactionForm.build + render(conn, "show.html", transaction: transaction) end end diff --git a/lib/explorer_web/templates/transaction/show.html.eex b/lib/explorer_web/templates/transaction/show.html.eex index 16f0e5be91..dff848ee0b 100644 --- a/lib/explorer_web/templates/transaction/show.html.eex +++ b/lib/explorer_web/templates/transaction/show.html.eex @@ -63,7 +63,7 @@
<%= gettext "Cumulative Gas Used" %>
-
<%= @transaction.cumulative_gas_used |> Cldr.Number.to_string! %>
+
<%= @transaction.cumulative_gas_used %>
<%= gettext "Nonce" %>
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 096747c43e..19b8a591e4 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -1,14 +1,18 @@ -#: lib/explorer_web/templates/page/index.html.eex:11 -#: lib/explorer_web/templates/page/index.html.eex:39 +#: lib/explorer_web/templates/block/index.html.eex:12 +#: lib/explorer_web/templates/chain/show.html.eex:11 +#: lib/explorer_web/templates/chain/show.html.eex:39 +#: lib/explorer_web/templates/transaction/index.html.eex:13 #: lib/explorer_web/templates/transaction/show.html.eex:33 msgid "Age" msgstr "" -#: lib/explorer_web/templates/page/index.html.eex:38 +#: lib/explorer_web/templates/chain/show.html.eex:38 +#: lib/explorer_web/templates/transaction/index.html.eex:12 msgid "Block" msgstr "" -#: lib/explorer_web/templates/page/index.html.eex:4 +#: lib/explorer_web/templates/chain/show.html.eex:4 +#: lib/explorer_web/templates/layout/_header.html.eex:16 msgid "Blocks" msgstr "" @@ -16,17 +20,20 @@ msgstr "" msgid "Copyright %{year} POA" msgstr "" +#: lib/explorer_web/templates/block/index.html.eex:14 #: lib/explorer_web/templates/block/show.html.eex:53 -#: lib/explorer_web/templates/page/index.html.eex:13 +#: lib/explorer_web/templates/chain/show.html.eex:13 msgid "Gas Used" msgstr "" #: lib/explorer_web/templates/block/show.html.eex:25 -#: lib/explorer_web/templates/page/index.html.eex:37 +#: lib/explorer_web/templates/chain/show.html.eex:37 +#: lib/explorer_web/templates/transaction/index.html.eex:11 msgid "Hash" msgstr "" -#: lib/explorer_web/templates/page/index.html.eex:10 +#: lib/explorer_web/templates/block/index.html.eex:11 +#: lib/explorer_web/templates/chain/show.html.eex:10 msgid "Height" msgstr "" @@ -35,13 +42,16 @@ msgstr "" msgid "POA Network Explorer" msgstr "" +#: lib/explorer_web/templates/block/index.html.eex:13 #: lib/explorer_web/templates/block/show.html.eex:21 -#: lib/explorer_web/templates/page/index.html.eex:12 -#: lib/explorer_web/templates/page/index.html.eex:31 +#: lib/explorer_web/templates/chain/show.html.eex:12 +#: lib/explorer_web/templates/chain/show.html.eex:31 +#: lib/explorer_web/templates/layout/_header.html.eex:12 msgid "Transactions" msgstr "" -#: lib/explorer_web/templates/page/index.html.eex:40 +#: lib/explorer_web/templates/chain/show.html.eex:40 +#: lib/explorer_web/templates/transaction/index.html.eex:14 #: lib/explorer_web/templates/transaction/show.html.eex:37 #: lib/explorer_web/templates/transaction/show.html.eex:49 msgid "Value" @@ -55,6 +65,7 @@ msgstr "" msgid "Difficulty" msgstr "" +#: lib/explorer_web/templates/block/index.html.eex:15 #: lib/explorer_web/templates/block/show.html.eex:57 #: lib/explorer_web/templates/transaction/show.html.eex:57 msgid "Gas Limit" @@ -105,6 +116,7 @@ msgstr "" msgid "Gas" msgstr "" +#: lib/explorer_web/templates/block/index.html.eex:16 #: lib/explorer_web/templates/transaction/show.html.eex:61 msgid "Gas Price" msgstr "" @@ -155,7 +167,20 @@ msgid "Balance" msgstr "" #: lib/explorer_web/templates/address/show.html.eex:11 -#: lib/explorer_web/templates/page/index.html.eex:53 +#: lib/explorer_web/templates/chain/show.html.eex:53 +#: lib/explorer_web/templates/transaction/index.html.eex:25 #: lib/explorer_web/templates/transaction/show.html.eex:38 msgid "POA" msgstr "" + +#: lib/explorer_web/templates/block/index.html.eex:24 +msgid "%{count} transactions" +msgstr "" + +#: lib/explorer_web/templates/block/index.html.eex:3 +msgid "Showing #%{start_block} to #%{end_block}" +msgstr "" + +#: lib/explorer_web/templates/transaction/index.html.eex:3 +msgid "Showing %{count} Transactions" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index df3366f810..d579d0c9d3 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -10,17 +10,21 @@ msgid "" msgstr "" "Language: en\n" -#: lib/explorer_web/templates/page/index.html.eex:11 -#: lib/explorer_web/templates/page/index.html.eex:39 +#: lib/explorer_web/templates/block/index.html.eex:12 +#: lib/explorer_web/templates/chain/show.html.eex:11 +#: lib/explorer_web/templates/chain/show.html.eex:39 +#: lib/explorer_web/templates/transaction/index.html.eex:13 #: lib/explorer_web/templates/transaction/show.html.eex:33 msgid "Age" msgstr "Age" -#: lib/explorer_web/templates/page/index.html.eex:38 +#: lib/explorer_web/templates/chain/show.html.eex:38 +#: lib/explorer_web/templates/transaction/index.html.eex:12 msgid "Block" msgstr "Block" -#: lib/explorer_web/templates/page/index.html.eex:4 +#: lib/explorer_web/templates/chain/show.html.eex:4 +#: lib/explorer_web/templates/layout/_header.html.eex:16 msgid "Blocks" msgstr "Blocks" @@ -28,17 +32,20 @@ msgstr "Blocks" msgid "Copyright %{year} POA" msgstr "%{year} POA Network Ltd. All rights reserved" +#: lib/explorer_web/templates/block/index.html.eex:14 #: lib/explorer_web/templates/block/show.html.eex:53 -#: lib/explorer_web/templates/page/index.html.eex:13 +#: lib/explorer_web/templates/chain/show.html.eex:13 msgid "Gas Used" msgstr "Gas Used" #: lib/explorer_web/templates/block/show.html.eex:25 -#: lib/explorer_web/templates/page/index.html.eex:37 +#: lib/explorer_web/templates/chain/show.html.eex:37 +#: lib/explorer_web/templates/transaction/index.html.eex:11 msgid "Hash" msgstr "Hash" -#: lib/explorer_web/templates/page/index.html.eex:10 +#: lib/explorer_web/templates/block/index.html.eex:11 +#: lib/explorer_web/templates/chain/show.html.eex:10 msgid "Height" msgstr "Height" @@ -47,13 +54,16 @@ msgstr "Height" msgid "POA Network Explorer" msgstr "POA Network Explorer" +#: lib/explorer_web/templates/block/index.html.eex:13 #: lib/explorer_web/templates/block/show.html.eex:21 -#: lib/explorer_web/templates/page/index.html.eex:12 -#: lib/explorer_web/templates/page/index.html.eex:31 +#: lib/explorer_web/templates/chain/show.html.eex:12 +#: lib/explorer_web/templates/chain/show.html.eex:31 +#: lib/explorer_web/templates/layout/_header.html.eex:12 msgid "Transactions" msgstr "Transactions" -#: lib/explorer_web/templates/page/index.html.eex:40 +#: lib/explorer_web/templates/chain/show.html.eex:40 +#: lib/explorer_web/templates/transaction/index.html.eex:14 #: lib/explorer_web/templates/transaction/show.html.eex:37 #: lib/explorer_web/templates/transaction/show.html.eex:49 msgid "Value" @@ -67,6 +77,7 @@ msgstr "Block #%{number} Details" msgid "Difficulty" msgstr "Difficulty" +#: lib/explorer_web/templates/block/index.html.eex:15 #: lib/explorer_web/templates/block/show.html.eex:57 #: lib/explorer_web/templates/transaction/show.html.eex:57 msgid "Gas Limit" @@ -117,6 +128,7 @@ msgstr "Cumulative Gas Used" msgid "Gas" msgstr "Gas" +#: lib/explorer_web/templates/block/index.html.eex:16 #: lib/explorer_web/templates/transaction/show.html.eex:61 msgid "Gas Price" msgstr "Gas Price" @@ -167,7 +179,20 @@ msgid "Balance" msgstr "Balance" #: lib/explorer_web/templates/address/show.html.eex:11 -#: lib/explorer_web/templates/page/index.html.eex:53 +#: lib/explorer_web/templates/chain/show.html.eex:53 +#: lib/explorer_web/templates/transaction/index.html.eex:25 #: lib/explorer_web/templates/transaction/show.html.eex:38 msgid "POA" msgstr "POA" + +#: lib/explorer_web/templates/block/index.html.eex:24 +msgid "%{count} transactions" +msgstr "%{count} transactions" + +#: lib/explorer_web/templates/block/index.html.eex:3 +msgid "Showing #%{start_block} to #%{end_block}" +msgstr "Showing #%{start_block} to #%{end_block}" + +#: lib/explorer_web/templates/transaction/index.html.eex:3 +msgid "Showing %{count} Transactions" +msgstr "Showing %{count} Transactions" diff --git a/priv/repo/migrations/20180202195342_create_block_transactions.exs b/priv/repo/migrations/20180202195342_create_block_transactions.exs new file mode 100644 index 0000000000..e95111f038 --- /dev/null +++ b/priv/repo/migrations/20180202195342_create_block_transactions.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.CreateBlockTransactions do + use Ecto.Migration + + def change do + create table(:block_transactions, primary_key: false) do + add :block_id, references(:blocks) + add :transaction_id, references(:transactions), primary_key: true + timestamps null: false + end + + create unique_index(:block_transactions, :transaction_id) + create unique_index(:block_transactions, [:block_id, :transaction_id]) + end +end diff --git a/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs b/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs new file mode 100644 index 0000000000..aab2d83479 --- /dev/null +++ b/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.RemoveBlockIdFromTransactions do + use Ecto.Migration + + def change do + alter table(:transactions) do + remove :block_id + end + end +end diff --git a/test/explorer/block_transaction_test.exs b/test/explorer/block_transaction_test.exs new file mode 100644 index 0000000000..f9da5e6c8e --- /dev/null +++ b/test/explorer/block_transaction_test.exs @@ -0,0 +1,17 @@ +defmodule Explorer.BlockTransactionTest do + use Explorer.DataCase + alias Explorer.BlockTransaction + + describe "changeset/2" do + test "with empty attributes" do + changeset = BlockTransaction.changeset(%BlockTransaction{}, %{}) + refute(changeset.valid?) + end + + test "with valid attributes" do + attrs = %{block_id: 4, transaction_id: 3} + changeset = BlockTransaction.changeset(%BlockTransaction{}, attrs) + assert(changeset.valid?) + end + end +end diff --git a/test/explorer/fetcher_test.exs b/test/explorer/fetcher_test.exs index fd187cf413..80e65037da 100644 --- a/test/explorer/fetcher_test.exs +++ b/test/explorer/fetcher_test.exs @@ -8,6 +8,7 @@ defmodule Explorer.FetcherTest do alias Explorer.Address alias Explorer.ToAddress alias Explorer.FromAddress + alias Explorer.BlockTransaction @raw_block %{ "difficulty" => "0xfffffffffffffffffffffffffffffffe", @@ -69,7 +70,6 @@ defmodule Explorer.FetcherTest do standard_v: "0x11", transaction_index: "0x12", v: "0x13", - block_id: 100, } describe "fetch/1" do @@ -143,9 +143,12 @@ defmodule Explorer.FetcherTest do describe "extract_transactions/2" do test "that it creates a list of transactions" do - block = insert(:block, %{id: 100}) - transactions = Fetcher.extract_transactions(block, [@raw_transaction]) - assert List.first(transactions).block_id == 100 + raw_transactions = [%{@raw_transaction | "hash" => "0xlimon"}, %{@raw_transaction | "hash" => "0xpepino"}] + block = insert(:block) + Fetcher.extract_transactions(block, raw_transactions) + transaction_hashes = Repo.all(from transaction in Transaction, select: transaction.hash) + + assert(transaction_hashes == ["0xlimon", "0xpepino"]) end end @@ -158,6 +161,41 @@ defmodule Explorer.FetcherTest do assert last_transaction.hash == "0xab1" end + test "that it creates a block transaction when there is a block" do + block = insert(:block) + transaction_attrs = %{@raw_transaction | "hash" => "0xab1"} + Fetcher.create_transaction(block, transaction_attrs) + + transaction = Transaction |> Repo.get_by(hash: "0xab1") + block_transaction = BlockTransaction |> Repo.get_by(block_id: block.id, transaction_id: transaction.id) + assert block_transaction + end + + test "that it updates the block transaction when it is called multiple times" do + first_block = insert(:block) + second_block = insert(:block) + transaction_attrs = %{@raw_transaction | "hash" => "0xsabzi"} + Fetcher.create_transaction(first_block, transaction_attrs) + + transaction = Transaction |> Repo.get_by(hash: "0xsabzi") + block_transaction = BlockTransaction |> Repo.get_by(transaction_id: transaction.id) + assert block_transaction.block_id == first_block.id + + Fetcher.create_transaction(second_block, transaction_attrs) + block_transaction = BlockTransaction |> Repo.get_by(transaction_id: transaction.id) + assert block_transaction.block_id == second_block.id + refute block_transaction.block_id == first_block.id + end + + test "that it does not create a block transaction when there is no block" do + transaction_attrs = %{@raw_transaction | "hash" => "0xab1"} + Fetcher.create_transaction(nil, transaction_attrs) + + transaction = Transaction |> Repo.get_by(hash: "0xab1") + block_transaction = BlockTransaction |> Repo.get_by(transaction_id: transaction.id) + refute block_transaction + end + test "that it creates a 'to address'" do block = insert(:block) transaction_attrs = %{@raw_transaction | "to" => "0xSmoothiesRGr8"} @@ -238,14 +276,14 @@ defmodule Explorer.FetcherTest do end end - describe "extract_transaction/2" do + describe "extract_transaction/1" do test "that it extracts the transaction" do - assert Fetcher.extract_transaction(%{id: 100}, @raw_transaction) == @processed_transaction + assert Fetcher.extract_transaction(@raw_transaction) == @processed_transaction end test "when the transaction value is zero it returns a decimal" do transaction = %{@raw_transaction | "value" => "0x0"} - assert Fetcher.extract_transaction(%{id: 100}, transaction).value == 0 + assert Fetcher.extract_transaction(transaction).value == 0 end end diff --git a/test/explorer/forms/block_form_test.exs b/test/explorer/forms/block_form_test.exs index 0d8e5ef8c6..be7de6bb7d 100644 --- a/test/explorer/forms/block_form_test.exs +++ b/test/explorer/forms/block_form_test.exs @@ -5,13 +5,13 @@ defmodule Explorer.BlockFormTest do describe "build/1" do test "that it has a number" do block = insert(:block, number: 311) - insert_list(2, :transaction, block: block) + insert_list(2, :transaction) |> list_with_block(block) assert BlockForm.build(block).number == 311 end test "that it returns a count of transactions" do block = insert(:block, number: 311) - insert_list(2, :transaction, block: block) + insert_list(2, :transaction) |> list_with_block(block) assert BlockForm.build(block).transactions_count == 2 end diff --git a/test/explorer/forms/transaction_form_test.exs b/test/explorer/forms/transaction_form_test.exs index 2d9b9d92dd..1ffe85c439 100644 --- a/test/explorer/forms/transaction_form_test.exs +++ b/test/explorer/forms/transaction_form_test.exs @@ -11,31 +11,55 @@ defmodule Explorer.TransactionFormTest do gas_used: 99523, timestamp: Timex.parse!(date, "%b-%d-%Y %H:%M:%S %p %Z", :strftime), }) - transaction = insert(:transaction, block: block) |> with_addresses(%{to: "0xsleepypuppy", from: "0xilovefrogs"}) + transaction = + insert(:transaction) + |> with_block(block) + |> with_addresses(%{to: "0xsleepypuppy", from: "0xilovefrogs"}) + form = TransactionForm.build(transaction) {:ok, %{form: form}} end - test "that it has a block number", %{form: form} do + test "that it has a block number when it has a block", %{form: form} do assert form.block_number == 1 end - test "that it returns the block's age" do + test "shows a blank block number when the transaction is pending" do + transaction = insert(:transaction) |> with_addresses + assert TransactionForm.build(transaction).block_number == "" + end + + test "that it returns the block's age when has a block" do block = insert(:block, %{ number: 1, gas_used: 99523, timestamp: Timex.now |> Timex.shift(hours: -2), }) - transaction = insert(:transaction, block: block) |> with_addresses(%{to: "0xsiskelnebert", from: "0xleonardmaltin"}) + transaction = insert(:transaction) |> with_block(block) |> with_addresses(%{to: "0xsiskelnebert", from: "0xleonardmaltin"}) assert TransactionForm.build(transaction).age == "2 hours ago" end - test "formats the block's timestamp", %{form: form} do + test "that it has an empty age when it is pending" do + transaction = insert(:transaction) |> with_addresses + assert TransactionForm.build(transaction).age == "" + end + + test "formats the timestamp when it has a block", %{form: form} do assert form.formatted_timestamp == "Feb-02-2010 10:48:56 AM Etc/UTC" end + test "formats the timestamp when the transaction is pending" do + transaction = insert(:transaction) |> with_addresses + assert TransactionForm.build(transaction).formatted_timestamp == "" + end + test "that it returns the cumulative gas used for validating the block", %{form: form} do - assert form.cumulative_gas_used == 99523 + assert form.cumulative_gas_used == "99,523" + end + + test "shows the cumulative gas used for a pending transaction" do + transaction = insert(:transaction) |> with_addresses + assert TransactionForm.build(transaction).cumulative_gas_used == "" end test "that it returns a 'to address'", %{form: form} do @@ -49,13 +73,29 @@ defmodule Explorer.TransactionFormTest do test "that it returns confirmations", %{form: form} do assert form.confirmations == 23 end + + test "shows confirmations when the transaction is pending" do + transaction = insert(:transaction) |> with_addresses + assert TransactionForm.build(transaction).confirmations == 0 + end + end + + describe "cumulative_gas_used/1" do + test "when there is a block" do + block = insert(:block, %{gas_used: 1_000}) + assert TransactionForm.cumulative_gas_used(block) == "1,000" + end + + test "when there is not a block" do + assert TransactionForm.cumulative_gas_used(nil) == "" + end end describe "confirmations/1" do test "when there is only one block" do block = insert(:block, %{number: 1}) - transaction = insert(:transaction, %{block: block}) - assert TransactionForm.confirmations(transaction) == 0 + insert(:transaction) |> with_block(block) |> with_addresses + assert TransactionForm.confirmations(block) == 0 end end end diff --git a/test/explorer/importers/transaction_importer_test.exs b/test/explorer/importers/transaction_importer_test.exs new file mode 100644 index 0000000000..5e82ad90bf --- /dev/null +++ b/test/explorer/importers/transaction_importer_test.exs @@ -0,0 +1,235 @@ +defmodule Explorer.TransactionImporterTest do + use Explorer.DataCase + + alias Explorer.Address + alias Explorer.BlockTransaction + alias Explorer.FromAddress + alias Explorer.ToAddress + alias Explorer.Transaction + alias Explorer.TransactionImporter + + @raw_transaction %{ + "creates" => nil, + "hash" => "pepino", + "value" => "0xde0b6b3a7640000", + "from" => "0x34d0ef2c", + "gas" => "0x21000", + "gasPrice" => "0x10000", + "input" => "0x5c8eff12", + "nonce" => "0x31337", + "publicKey" => "0xb39af9c", + "r" => "0x9", + "s" => "0x10", + "to" => "0x7a33b7d", + "standardV" => "0x11", + "transactionIndex" => "0x12", + "v" => "0x13", + } + + @processed_transaction %{ + hash: "pepino", + value: 1000000000000000000, + gas: 135168, + gas_price: 65536, + input: "0x5c8eff12", + nonce: 201527, + public_key: "0xb39af9c", + r: "0x9", + s: "0x10", + standard_v: "0x11", + transaction_index: "0x12", + v: "0x13", + } + + describe "import/1" do + test "imports and saves a transaction to the database" do + use_cassette "transaction_importer_import_saves_the_transaction" do + TransactionImporter.import("0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291") + transaction = Transaction |> order_by(desc: :inserted_at) |> Repo.one + + assert transaction.hash == "0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291" + end + end + + test "when it has previously been saved it updates the transaction" do + use_cassette "transaction_importer_updates_the_association" do + insert(:transaction, hash: "0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23", gas: 5) + TransactionImporter.import("0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23") + transaction = Transaction |> order_by(desc: :inserted_at) |> Repo.one + + assert transaction.gas == Decimal.new(231855) + end + end + + test "when it has a block hash that's saved in the database it saves the association" do + use_cassette "transaction_importer_saves_the_association" do + block = insert(:block, hash: "0xfce13392435a8e7dab44c07d482212efb9dc39a9bea1915a9ead308b55a617f9") + TransactionImporter.import("0x64d851139325479c3bb7ccc6e6ab4cde5bc927dce6810190fe5d770a4c1ac333") + transaction = Transaction |> Repo.get_by(hash: "0x64d851139325479c3bb7ccc6e6ab4cde5bc927dce6810190fe5d770a4c1ac333") + block_transaction = BlockTransaction |> Repo.get_by(transaction_id: transaction.id) + + assert block_transaction.block_id == block.id + end + end + + test "when there is no block it does not save a block transaction" do + use_cassette "transaction_importer_txn_without_block" do + TransactionImporter.import("0xc6aa189827c14880f012a65292f7add7b5310094f8773a3d85b66303039b9dcf") + transaction = Transaction |> Repo.get_by(hash: "0xc6aa189827c14880f012a65292f7add7b5310094f8773a3d85b66303039b9dcf") + block_transaction = BlockTransaction |> Repo.get_by(transaction_id: transaction.id) + + refute block_transaction + end + end + + test "it creates a from address" do + use_cassette "transaction_importer_creates_a_from_address" do + TransactionImporter.import("0xc445f5410912458c480d992dd93355ae3dad64d9f65db25a3cf43a9c609a2e0d") + + transaction = Transaction |> Repo.get_by(hash: "0xc445f5410912458c480d992dd93355ae3dad64d9f65db25a3cf43a9c609a2e0d") + address = Address |> Repo.get_by(hash: "0xa5b4b372112ab8dbbb48c8d0edd89227e24ec785") + from_address = FromAddress |> Repo.get_by(transaction_id: transaction.id, address_id: address.id) + + assert from_address + end + end + + test "it creates a to address" do + use_cassette "transaction_importer_creates_a_to_address" do + TransactionImporter.import("0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c") + + transaction = Transaction |> Repo.get_by(hash: "0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c") + address = Address |> Repo.get_by(hash: "0x24e5b8528fe83257d5fe3497ef616026713347f8") + to_address = ToAddress |> Repo.get_by(transaction_id: transaction.id, address_id: address.id) + + assert(to_address) + end + end + + test "it creates a to address using creates when to is nil" do + use_cassette "transaction_importer_creates_a_to_address_from_creates" do + TransactionImporter.import("0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c") + + transaction = Transaction |> Repo.get_by(hash: "0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c") + address = Address |> Repo.get_by(hash: "0x24e5b8528fe83257d5fe3497ef616026713347f8") + to_address = ToAddress |> Repo.get_by(transaction_id: transaction.id, address_id: address.id) + + assert(to_address) + end + end + end + + describe "download_transaction/1" do + test "downloads a transaction" do + use_cassette "transaction_importer_download_transaction" do + raw_transaction = TransactionImporter.download_transaction("0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23") + assert(raw_transaction["from"] == "0xbe96ef1d056c97323e210fd0dd818aa027e57143") + end + end + + test "when it has an invalid hash" do + use_cassette "transaction_importer_download_transaction_with_a_bad_hash" do + assert_raise MatchError, fn -> + TransactionImporter.download_transaction("0xdecafisbadzzzz") + end + end + end + end + + describe "extract_attrs/1" do + test "returns a changeset-friendly list of transaction attributes" do + transaction_attrs = TransactionImporter.extract_attrs(@raw_transaction) + assert transaction_attrs == @processed_transaction + end + end + + describe "create_block_transaction/2" do + test "inserts a block transaction" do + block = insert(:block) + transaction = insert(:transaction) + TransactionImporter.create_block_transaction(transaction, block.hash) + block_transaction = + BlockTransaction + |> Repo.get_by(transaction_id: transaction.id, block_id: block.id) + + assert block_transaction + end + + test "updates an already existing block transaction" do + block = insert(:block) + transaction = insert(:transaction) + the_seventies = Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}") + block_transaction = insert(:block_transaction, %{block_id: block.id, transaction_id: transaction.id, inserted_at: the_seventies, updated_at: the_seventies}) + update_block = insert(:block) + TransactionImporter.create_block_transaction(transaction, update_block.hash) + updated_block_transaction = + BlockTransaction + |> Repo.get_by(transaction_id: transaction.id) + + refute block_transaction.block_id == updated_block_transaction.block_id + refute block_transaction.updated_at == updated_block_transaction.updated_at + end + end + + describe "create_from_address/2" do + test "that it creates a new address when one does not exist" do + transaction = insert(:transaction) + TransactionImporter.create_from_address(transaction, "0xbb8") + last_address = Address |> order_by(desc: :inserted_at) |> Repo.one + + assert last_address.hash == "0xbb8" + end + + test "that it joins transaction and from address" do + transaction = insert(:transaction) + TransactionImporter.create_from_address(transaction, "0xFreshPrince") + address = Address |> order_by(desc: :inserted_at) |> Repo.one + from_address = FromAddress |> order_by(desc: :inserted_at) |> Repo.one + + assert from_address.transaction_id == transaction.id + assert from_address.address_id == address.id + end + + test "when the address already exists it does not insert a new address" do + transaction = insert(:transaction) + insert(:address, hash: "0xbb8") + TransactionImporter.create_from_address(transaction, "0xbb8") + + assert Address |> Repo.all |> length == 1 + end + end + + describe "create_to_address/2" do + test "that it creates a new address when one does not exist" do + transaction = insert(:transaction) + TransactionImporter.create_to_address(transaction, "0xFreshPrince") + last_address = Address |> order_by(desc: :inserted_at) |> Repo.one + + assert last_address.hash == "0xfreshprince" + end + + test "that it joins transaction and address" do + transaction = insert(:transaction) + TransactionImporter.create_to_address(transaction, "0xFreshPrince") + address = Address |> order_by(desc: :inserted_at) |> Repo.one + to_address = ToAddress |> order_by(desc: :inserted_at) |> Repo.one + + assert to_address.transaction_id == transaction.id + assert to_address.address_id == address.id + end + + test "when the address already exists it does not insert a new address" do + transaction = insert(:transaction) + insert(:address, hash: "bigmouthbillybass") + TransactionImporter.create_to_address(transaction, "bigmouthbillybass") + + assert Address |> Repo.all |> length == 1 + end + end + + describe "decode_integer_field/1" do + test "returns the integer value of a hex value" do + assert(TransactionImporter.decode_integer_field("0x7f2fb") == 520955) + end + end +end diff --git a/test/explorer/transaction_test.exs b/test/explorer/transaction_test.exs index 76028c4d0c..1249f4be07 100644 --- a/test/explorer/transaction_test.exs +++ b/test/explorer/transaction_test.exs @@ -5,17 +5,10 @@ defmodule Explorer.TransactionTest do describe "changeset/2" do test "with valid attributes" do - block = insert(:block) - changeset = Transaction.changeset(%Transaction{}, %{hash: "0x0", block_id: block.id, value: 1, gas: 21000, gas_price: 10000, input: "0x5c8eff12", nonce: "31337", public_key: "0xb39af9c", r: "0x9", s: "0x10", standard_v: "0x11", transaction_index: "0x12", v: "0x13"}) + changeset = Transaction.changeset(%Transaction{}, %{hash: "0x0", value: 1, gas: 21000, gas_price: 10000, input: "0x5c8eff12", nonce: "31337", public_key: "0xb39af9c", r: "0x9", s: "0x10", standard_v: "0x11", transaction_index: "0x12", v: "0x13"}) assert changeset.valid? end - test "with a block that does not exist in the database" do - {:error, changeset} = Transaction.changeset(%Transaction{}, %{hash: "0x0", block_id: 0, value: 1, gas: 21000, gas_price: 10000, input: "0x5c8eff12", nonce: "31337", public_key: "0xb39af9c", r: "0x9", s: "0x10", standard_v: "0x11", transaction_index: "0x12", v: "0x13"}) |> Repo.insert - refute changeset.valid? - assert [block_id: {"does not exist", []}] = changeset.errors - end - test "with invalid attributes" do changeset = Transaction.changeset(%Transaction{}, %{racecar: "yellow ham"}) refute changeset.valid? diff --git a/test/explorer_web/controllers/chain_controller_test.exs b/test/explorer_web/controllers/chain_controller_test.exs index 449b66684f..5679f26103 100644 --- a/test/explorer_web/controllers/chain_controller_test.exs +++ b/test/explorer_web/controllers/chain_controller_test.exs @@ -1,15 +1,6 @@ defmodule ExplorerWeb.ChainControllerTest do use ExplorerWeb.ConnCase - def build_transaction(block \\ nil) do - block = block || insert(:block) - transaction = insert(:transaction, block: block) - to_address = insert(:address) - from_address = insert(:address) - insert(:to_address, transaction: transaction, address: to_address) - insert(:from_address, transaction: transaction, address: from_address) - end - describe "GET index/2 without a locale" do test "redirects to the en locale", %{conn: conn} do conn = get conn, "/" @@ -39,25 +30,12 @@ defmodule ExplorerWeb.ChainControllerTest do test "returns a transaction", %{conn: conn} do block = insert(:block, number: 33) - insert(:transaction, hash: "0xDECAFBAD", block: block) |> with_addresses(%{to: "0xsleepypuppy", from: "0xilovefrogs"}) + insert(:transaction, hash: "0xDECAFBAD") |> with_block(block) |> with_addresses(%{to: "0xsleepypuppy", from: "0xilovefrogs"}) conn = get conn, "/en" assert(List.first(conn.assigns.transactions).hash == "0xDECAFBAD") - assert(List.first(conn.assigns.transactions).block.number == 33) - end - - test "returns only the five most recent transactions", %{conn: conn} do - block_mined_today = insert(:block, number: 2) - insert(:transaction, hash: "0xStuff", block: block_mined_today) |> with_addresses - - block_mined_last_week = insert(:block, number: 1) - for _ <- 0..4, do: insert(:transaction, %{block: block_mined_last_week}) |> with_addresses - - conn = get conn, "/en" - - assert Enum.count(conn.assigns.transactions) == 5 - assert List.first(conn.assigns.transactions).hash == "0xStuff" + assert(List.first(conn.assigns.transactions).block_number == 33) end end end diff --git a/test/explorer_web/controllers/transaction_controller_test.exs b/test/explorer_web/controllers/transaction_controller_test.exs index 5796fd5dca..2c75cbb9b4 100644 --- a/test/explorer_web/controllers/transaction_controller_test.exs +++ b/test/explorer_web/controllers/transaction_controller_test.exs @@ -1,19 +1,28 @@ defmodule ExplorerWeb.TransactionControllerTest do use ExplorerWeb.ConnCase + describe "GET index/2" do + test "returns all transactions", %{conn: conn} do + transaction_ids = insert_list(4, :transaction) |> list_with_block |> Enum.map(fn (transaction) -> transaction.id end) + conn = get(conn, "/en/transactions") + assert conn.assigns.transactions |> Enum.map(fn (transaction) -> transaction.id end) == transaction_ids + end + end + describe "GET show/3" do - test "returns a transaction", %{conn: conn} do - transaction = insert(:transaction, hash: "0x8") |> with_addresses + test "when there is an associated block, it returns a transaction with block data", %{conn: conn} do + block = insert(:block, %{number: 777}) + transaction = insert(:transaction, hash: "0x8") |> with_block(block) |> with_addresses conn = get(conn, "/en/transactions/0x8") assert conn.assigns.transaction.id == transaction.id + assert conn.assigns.transaction.block_number == block.number end - end - describe "GET index/2" do - test "returns all blocks", %{conn: conn} do - transaction_ids = insert_list(4, :transaction) |> Enum.map(fn (transaction) -> transaction.id end) - conn = get(conn, "/en/transactions") - assert conn.assigns.transactions |> Enum.reverse |> Enum.map(fn (transaction) -> transaction.id end) == transaction_ids + test "returns a transaction without associated block data", %{conn: conn} do + transaction = insert(:transaction, hash: "0x8") |> with_addresses + conn = get(conn, "/en/transactions/0x8") + assert conn.assigns.transaction.id == transaction.id + assert conn.assigns.transaction.block_number == "" end end end diff --git a/test/explorer_web/features/contributor_browsing_test.exs b/test/explorer_web/features/contributor_browsing_test.exs index b3106ef7d2..b14284db9b 100644 --- a/test/explorer_web/features/contributor_browsing_test.exs +++ b/test/explorer_web/features/contributor_browsing_test.exs @@ -27,7 +27,7 @@ defmodule ExplorerWeb.UserListTest do gas_used: 1010101, gas_limit: 5030101 }) - for _ <- 0..2, do: insert(:transaction, %{block: fifth_block}) |> with_addresses + for _ <- 0..2, do: insert(:transaction) |> with_block(fifth_block) |> with_addresses session |> visit("/en") @@ -56,7 +56,7 @@ defmodule ExplorerWeb.UserListTest do timestamp: Timex.now |> Timex.shift(hours: -2), gas_used: 123987, }) - for _ <- 0..3, do: insert(:transaction, %{block: block}) |> with_addresses + for _ <- 0..3, do: insert(:transaction) |> with_block(block) |> with_addresses insert(:transaction, %{ hash: "0xSk8", @@ -65,8 +65,8 @@ defmodule ExplorerWeb.UserListTest do gas_price: 7890000000898912300045, input: "0x00012", nonce: 99045, - block: block, }) + |> with_block(block) |> with_addresses(%{to: "0xabelincoln", from: "0xhowardtaft"}) session diff --git a/test/support/factories/block_transaction_factory.ex b/test/support/factories/block_transaction_factory.ex new file mode 100644 index 0000000000..5d7ca72918 --- /dev/null +++ b/test/support/factories/block_transaction_factory.ex @@ -0,0 +1,9 @@ +defmodule Explorer.BlockTransactionFactory do + defmacro __using__(_opts) do + quote do + def block_transaction_factory do + %Explorer.BlockTransaction{} + end + end + end +end diff --git a/test/support/factories/transaction_factory.ex b/test/support/factories/transaction_factory.ex index 5d63eedff1..c6b9b63a29 100644 --- a/test/support/factories/transaction_factory.ex +++ b/test/support/factories/transaction_factory.ex @@ -1,9 +1,12 @@ defmodule Explorer.TransactionFactory do defmacro __using__(_opts) do quote do + alias Explorer.Address + alias Explorer.BlockTransaction + alias Explorer.Repo + def transaction_factory do %Explorer.Transaction{ - block: build(:block), hash: sequence("0x"), value: Enum.random(1..100_000), gas: Enum.random(21_000..100_000), @@ -20,12 +23,24 @@ defmodule Explorer.TransactionFactory do end def with_addresses(transaction, %{to: to, from: from} \\ %{to: nil, from: nil}) do - to_address = if to, do: Explorer.Repo.get_by(Explorer.Address, %{hash: to}) || insert(:address, hash: to), else: insert(:address) - from_address = if from, do: Explorer.Repo.get_by(Explorer.Address, %{hash: from}) ||insert(:address, hash: from), else: insert(:address) + to_address = if to, do: Repo.get_by(Address, %{hash: to}) || insert(:address, hash: to), else: insert(:address) + from_address = if from, do: Repo.get_by(Address, %{hash: from}) ||insert(:address, hash: from), else: insert(:address) insert(:to_address, %{transaction_id: transaction.id, address_id: to_address.id}) insert(:from_address, %{transaction_id: transaction.id, address_id: from_address.id}) transaction end + + def with_block(transaction, block \\ nil) do + block = block || insert(:block) + insert(:block_transaction, %{block_id: block.id, transaction_id: transaction.id}) + # block_transaction_changeset = BlockTransaction.changeset(%BlockTransaction{}, %{block_id: block.id, transaction_id: transaction.id}) + # Repo.insert(block_transaction_changeset) + transaction + end + + def list_with_block(transactions, block \\ nil) do + Enum.map(transactions, fn(transaction) -> with_block(transaction, block) end) + end end end end diff --git a/test/support/factory.ex b/test/support/factory.ex index cd940603ea..cd062eeb7f 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -3,6 +3,7 @@ defmodule Explorer.Factory do use ExMachina.Ecto, repo: Explorer.Repo use Explorer.BlockFactory use Explorer.TransactionFactory + use Explorer.BlockTransactionFactory use Explorer.AddressFactory use Explorer.ToAddressFactory use Explorer.FromAddressFactory diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_from_address.json b/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_from_address.json new file mode 100644 index 0000000000..c3a0716495 --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_from_address.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0xc445f5410912458c480d992dd93355ae3dad64d9f65db25a3cf43a9c609a2e0d\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":2}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0x30996f4f3e53a2b22766f535877b6840b08518027268b3385e46dbcd4ebe633d\",\"blockNumber\":\"0xc197a\",\"chainId\":null,\"condition\":null,\"creates\":\"0x60d5233e8a1798c7b7a8a1f9083ae31f770073d7\",\"from\":\"0xa5b4b372112ab8dbbb48c8d0edd89227e24ec785\",\"gas\":\"0x3782f\",\"gasPrice\":\"0x3b9aca00\",\"hash\":\"0xc445f5410912458c480d992dd93355ae3dad64d9f65db25a3cf43a9c609a2e0d\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0xa3cd\",\"publicKey\":\"0x3366f04816d42836375b8f28d5b0d341f8a47fb46a59a754ca924c7397d335a7b89a0f94f7c4d36f29ae6f6acdd3f2c0bdf12d9314799c407cf5b6bc4b370d4f\",\"r\":\"0xbfabcb7ca06c8b1f78cd4a2e70ae645bea6e5b512febbb93663d152541e26923\",\"raw\":\"0xf9014582a3cd843b9aca008303782f8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba0bfabcb7ca06c8b1f78cd4a2e70ae645bea6e5b512febbb93663d152541e26923a035bb5cf4b0c14240954bc73254cd761a964f7f8ceb45a79fb2a7464fd16383a1\",\"s\":\"0x35bb5cf4b0c14240954bc73254cd761a964f7f8ceb45a79fb2a7464fd16383a1\",\"standardV\":\"0x0\",\"to\":null,\"transactionIndex\":\"0x5\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":2}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:33:25 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d291677b65b9ebf5fbc09dec36364c58f1517888004; expires=Wed, 06-Feb-19 03:33:24 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": "3e8b0c3ebff39607-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address.json b/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address.json new file mode 100644 index 0000000000..1c42192fcd --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":5}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0x30996f4f3e53a2b22766f535877b6840b08518027268b3385e46dbcd4ebe633d\",\"blockNumber\":\"0xc197a\",\"chainId\":null,\"condition\":null,\"creates\":null,\"from\":\"0xb2867180771b196518651c174c9240d5e8bd0ecd\",\"gas\":\"0x3755b\",\"gasPrice\":\"0x3b9aca00\",\"hash\":\"0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0xa4e9\",\"publicKey\":\"0xbf0cf158b32b838553af2c4f9e3393e6fb4cb0a97a7e4025d3fe3a552e3cfa720779da708ec4a5a89c9a6ca7f16d1b49605a9b8bdb28bd2a45818418743d153c\",\"r\":\"0xb40a4357f5b6316df0bd0f054a94c09b443fc16ebed9c9c95932c460776dc7bf\",\"raw\":\"0xf9014582a4e9843b9aca008303755b8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba0b40a4357f5b6316df0bd0f054a94c09b443fc16ebed9c9c95932c460776dc7bfa01672c0bcc52ae7348a226ab8232118f6a74197c99ec715d7186c16b0837766bf\",\"s\":\"0x1672c0bcc52ae7348a226ab8232118f6a74197c99ec715d7186c16b0837766bf\",\"standardV\":\"0x0\",\"to\":\"0x24e5b8528fe83257d5fe3497ef616026713347f8\",\"transactionIndex\":\"0x2\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":5}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:33:26 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d9137691f355994dc6bb3a8c2e4033e621517888006; expires=Wed, 06-Feb-19 03:33:26 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": "3e8b0c46aac89607-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address_from_creates.json b/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address_from_creates.json new file mode 100644 index 0000000000..11db281292 --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_creates_a_to_address_from_creates.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":5}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0x30996f4f3e53a2b22766f535877b6840b08518027268b3385e46dbcd4ebe633d\",\"blockNumber\":\"0xc197a\",\"chainId\":null,\"condition\":null,\"creates\":\"0x24e5b8528fe83257d5fe3497ef616026713347f8\",\"from\":\"0xb2867180771b196518651c174c9240d5e8bd0ecd\",\"gas\":\"0x3755b\",\"gasPrice\":\"0x3b9aca00\",\"hash\":\"0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0xa4e9\",\"publicKey\":\"0xbf0cf158b32b838553af2c4f9e3393e6fb4cb0a97a7e4025d3fe3a552e3cfa720779da708ec4a5a89c9a6ca7f16d1b49605a9b8bdb28bd2a45818418743d153c\",\"r\":\"0xb40a4357f5b6316df0bd0f054a94c09b443fc16ebed9c9c95932c460776dc7bf\",\"raw\":\"0xf9014582a4e9843b9aca008303755b8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba0b40a4357f5b6316df0bd0f054a94c09b443fc16ebed9c9c95932c460776dc7bfa01672c0bcc52ae7348a226ab8232118f6a74197c99ec715d7186c16b0837766bf\",\"s\":\"0x1672c0bcc52ae7348a226ab8232118f6a74197c99ec715d7186c16b0837766bf\",\"standardV\":\"0x0\",\"to\":null,\"transactionIndex\":\"0x2\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":5}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:33:26 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d9137691f355994dc6bb3a8c2e4033e621517888006; expires=Wed, 06-Feb-19 03:33:26 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": "3e8b0c46aac89607-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_download_transaction.json b/test/support/fixture/vcr_cassettes/transaction_importer_download_transaction.json new file mode 100644 index 0000000000..ad7bea2f74 --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_download_transaction.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":0}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0x8663df28453934be1e0fc59995d8b5295e83e4db689ade8b0244525f8f7c118a\",\"blockNumber\":\"0xc10ab\",\"chainId\":null,\"condition\":null,\"creates\":\"0xac4ff5b5c29338d0046e40f22dbec4d9ef32ed11\",\"from\":\"0xbe96ef1d056c97323e210fd0dd818aa027e57143\",\"gas\":\"0x389af\",\"gasPrice\":\"0x77359400\",\"hash\":\"0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0x9601\",\"publicKey\":\"0x565e1e48534a6764a50baead6ec60f573d3294cab28b766c928fd6eb8b9c89268bf3452b57bbe8d0c699ac8ed9227344ee72e42f41577fb51777d66520ccdc9c\",\"r\":\"0x81c2771372d8e71d4569bbb0a830f5bb6cdba285178f68e0917e65a682a1cd46\",\"raw\":\"0xf901458296018477359400830389af8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba081c2771372d8e71d4569bbb0a830f5bb6cdba285178f68e0917e65a682a1cd46a00a1718c45816d32ddbe5efcbe2e41cffe537bc703d80b9632b75ee5a9d445dcd\",\"s\":\"0xa1718c45816d32ddbe5efcbe2e41cffe537bc703d80b9632b75ee5a9d445dcd\",\"standardV\":\"0x0\",\"to\":null,\"transactionIndex\":\"0x11\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":0}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:37:56 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d5ef068dc553a6d30310731eac55ffcf11517888276; expires=Wed, 06-Feb-19 03:37:56 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": "3e8b12de6e6992b8-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_download_transaction_with_a_bad_hash.json b/test/support/fixture/vcr_cassettes/transaction_importer_download_transaction_with_a_bad_hash.json new file mode 100644 index 0000000000..22a21f15bd --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_download_transaction_with_a_bad_hash.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0xdecafisbadzzzz\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":0}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: invalid length 14, expected a 0x-prefixed, padded, hex-encoded hash with length 64.\"},\"id\":0}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:36:54 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d53dce2a5d0bec9953b3957e3b0384b2d1517888214; expires=Wed, 06-Feb-19 03:36:54 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": "3e8b115c59f89342-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_import_saves_the_transaction.json b/test/support/fixture/vcr_cassettes/transaction_importer_import_saves_the_transaction.json new file mode 100644 index 0000000000..d57e8ebb95 --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_import_saves_the_transaction.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":5}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0x8663df28453934be1e0fc59995d8b5295e83e4db689ade8b0244525f8f7c118a\",\"blockNumber\":\"0xc10ab\",\"chainId\":null,\"condition\":null,\"creates\":\"0x353fe3ffbf77edef7f9c352c47965a38c07e837c\",\"from\":\"0x8a35723e64a47764b9acfec5e647b39a4e5d635c\",\"gas\":\"0x381ae\",\"gasPrice\":\"0x77359400\",\"hash\":\"0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0x9b62\",\"publicKey\":\"0xbd2c35eb20bfab52e22b8b9fbcfc3f5f8a927f7d6b800960026921929eeb0976a304639c785ce1a509900922d6ef8fd1222b7b48cdcb38c97b49c0f37c59c0f7\",\"r\":\"0x380bb4c084a241ffbf69ad1f52cd8689aeb986db451bf85d715b4d9afdd89b0d\",\"raw\":\"0xf90145829b628477359400830381ae8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba0380bb4c084a241ffbf69ad1f52cd8689aeb986db451bf85d715b4d9afdd89b0da01b1b1cca94b24cabba08bcc5d4fb99f2fdcb4f1f76d80091c061771040af3432\",\"s\":\"0x1b1b1cca94b24cabba08bcc5d4fb99f2fdcb4f1f76d80091c061771040af3432\",\"standardV\":\"0x0\",\"to\":null,\"transactionIndex\":\"0x0\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":5}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:41:44 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=db1f077d751ae0ba2508cbbe43d8047de1517888504; expires=Wed, 06-Feb-19 03:41:44 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": "3e8b1871fa3d6d42-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_saves_the_association.json b/test/support/fixture/vcr_cassettes/transaction_importer_saves_the_association.json new file mode 100644 index 0000000000..d8dddce89a --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_saves_the_association.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0x64d851139325479c3bb7ccc6e6ab4cde5bc927dce6810190fe5d770a4c1ac333\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":1}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0xfce13392435a8e7dab44c07d482212efb9dc39a9bea1915a9ead308b55a617f9\",\"blockNumber\":\"0xc10d9\",\"chainId\":\"0x4d\",\"condition\":null,\"creates\":null,\"from\":\"0x82e4e61e7f5139ff0a4157a5bc687ef42294c248\",\"gas\":\"0x5208\",\"gasPrice\":\"0x3b9aca00\",\"hash\":\"0x64d851139325479c3bb7ccc6e6ab4cde5bc927dce6810190fe5d770a4c1ac333\",\"input\":\"0x\",\"nonce\":\"0x23c\",\"publicKey\":\"0x750f765a7790aa361dde0ff1d4c9dadd0e1c62fe79dee9c3d6b953abe99ce79755568215a2524d0efe0d5a48d8082e86f317434b62964b0c2e3e3446ebe08099\",\"r\":\"0xb5c079b9b6b131c1f726dc90f829a9dedf7e4d72a787ff90224c630104768afb\",\"raw\":\"0xf86f82023c843b9aca008252089496604b744e9c5a72680d33e7faf9629e19c97eaa8902a18aeae7e44a70008081bea0b5c079b9b6b131c1f726dc90f829a9dedf7e4d72a787ff90224c630104768afba0609d0d6ab60888059000f1c8b3d75c7193462c3dac83f91cf378ad5c5242ec1b\",\"s\":\"0x609d0d6ab60888059000f1c8b3d75c7193462c3dac83f91cf378ad5c5242ec1b\",\"standardV\":\"0x1\",\"to\":\"0x96604b744e9c5a72680d33e7faf9629e19c97eaa\",\"transactionIndex\":\"0x9\",\"v\":\"0xbe\",\"value\":\"0x2a18aeae7e44a7000\"},\"id\":1}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:41:43 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=df89a2810de64c37a8a65bfffe6c4a1b41517888503; expires=Wed, 06-Feb-19 03:41: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": "3e8b18694f146d42-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json b/test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json new file mode 100644 index 0000000000..3b88b1f475 --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0xc6aa189827c14880f012a65292f7add7b5310094f8773a3d85b66303039b9dcf\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":0}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"null\",\"blockNumber\":\"0xc13ac\",\"chainId\":null,\"condition\":null,\"creates\":\"0xee0ea6a585b7002840cb1aab374f9ea159772196\",\"from\":\"0x23359b2330a82cca0618ffbc84cde3b760e69443\",\"gas\":\"0x3683b\",\"gasPrice\":\"0x12a05f200\",\"hash\":\"0xc6aa189827c14880f012a65292f7add7b5310094f8773a3d85b66303039b9dcf\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0x963d\",\"publicKey\":\"0x86189842eca4e1195fe49edf3c14438e3fcc1f8d9672f7cc943eee2b5f3143058dc79769a0d5b279a26169a37274eb05d5fe9e303dac3f25873a45cc13cc08a5\",\"r\":\"0x4a6668eea531cfefafc7299d83dbc975752d068b4afcff77a17de9faa6a92dd7\",\"raw\":\"0xf9014582963d85012a05f2008303683b8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba04a6668eea531cfefafc7299d83dbc975752d068b4afcff77a17de9faa6a92dd79fcdf765f678470f08279278653caa4cbf55c42c570b996f2fb037b815111d36\",\"s\":\"0xcdf765f678470f08279278653caa4cbf55c42c570b996f2fb037b815111d36\",\"standardV\":\"0x0\",\"to\":null,\"transactionIndex\":\"0x5\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":0}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 01:25:56 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d01ad5566804036879ac82a9f9bd4bf1f1517880356; expires=Wed, 06-Feb-19 01:25:56 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": "3e8a5183b8bf9607-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] diff --git a/test/support/fixture/vcr_cassettes/transaction_importer_updates_the_association.json b/test/support/fixture/vcr_cassettes/transaction_importer_updates_the_association.json new file mode 100644 index 0000000000..07b65010a3 --- /dev/null +++ b/test/support/fixture/vcr_cassettes/transaction_importer_updates_the_association.json @@ -0,0 +1,30 @@ +[ + { + "request": { + "body": "{\"params\":[\"0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23\"],\"method\":\"eth_getTransactionByHash\",\"jsonrpc\":\"2.0\",\"id\":0}", + "headers": { + "Content-Type": "application/json" + }, + "method": "post", + "options": [], + "request_body": "", + "url": "https://sokol.poa.network:443" + }, + "response": { + "binary": false, + "body": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockHash\":\"0x8663df28453934be1e0fc59995d8b5295e83e4db689ade8b0244525f8f7c118a\",\"blockNumber\":\"0xc10ab\",\"chainId\":null,\"condition\":null,\"creates\":\"0xac4ff5b5c29338d0046e40f22dbec4d9ef32ed11\",\"from\":\"0xbe96ef1d056c97323e210fd0dd818aa027e57143\",\"gas\":\"0x389af\",\"gasPrice\":\"0x77359400\",\"hash\":\"0x170baac4eca26076953370dd603c68eab340c0135b19b585010d3158a5dbbf23\",\"input\":\"0x6060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f4010029\",\"nonce\":\"0x9601\",\"publicKey\":\"0x565e1e48534a6764a50baead6ec60f573d3294cab28b766c928fd6eb8b9c89268bf3452b57bbe8d0c699ac8ed9227344ee72e42f41577fb51777d66520ccdc9c\",\"r\":\"0x81c2771372d8e71d4569bbb0a830f5bb6cdba285178f68e0917e65a682a1cd46\",\"raw\":\"0xf901458296018477359400830389af8080b8f26060604052341561000f57600080fd5b7fb94ae47ec9f4248692e2ecf9740b67ab493f3dcc8452bedc7d9cd911c28d1ca5426040518082815260200191505060405180910390a1609e806100546000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063557ed1ba146044575b600080fd5b3415604e57600080fd5b6054606a565b6040518082815260200191505060405180910390f35b6000429050905600a165627a7a7230582053883c0c39da080adc15a91094921659c200b3bb60aed9e49b79b0274da3f40100291ba081c2771372d8e71d4569bbb0a830f5bb6cdba285178f68e0917e65a682a1cd46a00a1718c45816d32ddbe5efcbe2e41cffe537bc703d80b9632b75ee5a9d445dcd\",\"s\":\"0xa1718c45816d32ddbe5efcbe2e41cffe537bc703d80b9632b75ee5a9d445dcd\",\"standardV\":\"0x0\",\"to\":null,\"transactionIndex\":\"0x11\",\"v\":\"0x1b\",\"value\":\"0x0\"},\"id\":0}\n", + "headers": { + "Date": "Tue, 06 Feb 2018 03:41:42 GMT", + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Set-Cookie": "__cfduid=d9d4f6391b8873087c5766cadd87dbbf51517888502; expires=Wed, 06-Feb-19 03:41:42 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": "3e8b1862cd286d42-SJC" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file